gpt4 book ai didi

objective-c - C 对比 vDSP 对比 NEON - NEON 怎么会和 C 一样慢?

转载 作者:搜寻专家 更新时间:2023-10-30 19:52:30 30 4
gpt4 key购买 nike

NEON 怎么会和 C 一样慢?

我一直在尝试构建一个快速直方图函数,通过为输入值分配一个值(这是它们最接近的范围阈值),将输入值分入范围。这是应用于图像的东西,因此它必须很快(假设图像数组为 640x480,因此有 300,000 个元素)。直方图范围数字是 (0,25,50,75,100) 的倍数。输入将是 float ,最终输出显然是整数

我通过打开一个新的空项目(没有应用程序委托(delegate))并仅使用 main.m 文件在 xCode 上测试了以下版本。我删除了除 Accelerate 之外的所有链接库。

这是 C 实现:旧版本有很多 if then 但这是最​​终优化的逻辑。花了 11 秒和 300 毫秒。

int main(int argc, char *argv[])
{
NSLog(@"starting");

int sizeOfArray=300000;

float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
int* outputArray=(int*) malloc(sizeof(int)*sizeOfArray);

for (int i=0; i<sizeOfArray; ++i)
{
inputArray[i]=88.5;
}

//Assume range is [0,25,50,75,100]
int lcd=25;

for (int j=0; j<1000; ++j)// just to get some good time interval
{
for (int i=0; i<sizeOfArray; ++i)
{
//a 60.5 would give a 50. An 88.5 would give 100
outputArray[i]=roundf(inputArray[i]/lcd)*lcd;
}
}
NSLog(@"done");
}

这是 vDSP 实现。即使有一些繁琐的整数来回 float ,也只用了 6 秒!将近 50% 的改进!

//vDSP implementation
int main(int argc, char *argv[])
{
NSLog(@"starting");

int sizeOfArray=300000;

float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
float* outputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);//vDSP requires matching of input output
int* outputArray=(int*) malloc(sizeof(int)*sizeOfArray); //rounded value to the nearest integere
float* finalOutputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);
int* finalOutputArray=(int*) malloc(sizeof(int)*sizeOfArray); //to compare apples to apples scenarios output


for (int i=0; i<sizeOfArray; ++i)
{
inputArray[i]=37.0; //this will produce an final number of 25. On the other hand 37.5 would produce 50.
}


for (int j=0; j<1000; ++j)// just to get some good time interval
{
//Assume range is [0,25,50,75,100]
float lcd=25.0f;

//divide by lcd
vDSP_vsdiv(inputArray, 1, &lcd, outputArrayF, 1,sizeOfArray);

//Round to nearest integer
vDSP_vfixr32(outputArrayF, 1,outputArray, 1, sizeOfArray);

// MUST convert int to float (cannot just cast) then multiply by scalar - This step has the effect of rounding the number to the nearest lcd.
vDSP_vflt32(outputArray, 1, outputArrayF, 1, sizeOfArray);
vDSP_vsmul(outputArrayF, 1, &lcd, finalOutputArrayF, 1, sizeOfArray);
vDSP_vfix32(finalOutputArrayF, 1, finalOutputArray, 1, sizeOfArray);
}
NSLog(@"done");
}

这是 Neon 的实现。这是我的第一次,所以玩得开心!它比 vDSP 慢,需要 9 秒和 300 毫秒,这对我来说没有意义。要么 vDSP 比 NEON 优化得更好,要么我做错了什么。

//NEON implementation
int main(int argc, char *argv[])
{
NSLog(@"starting");

int sizeOfArray=300000;

float* inputArray=(float*) malloc(sizeof(float)*sizeOfArray);
float* finalOutputArrayF=(float*) malloc(sizeof(float)*sizeOfArray);

for (int i=0; i<sizeOfArray; ++i)
{
inputArray[i]=37.0; //this will produce an final number of 25. On the other hand 37.5 would produce 50.
}



for (int j=0; j<1000; ++j)// just to get some good time interval
{
float32x4_t c0,c1,c2,c3;
float32x4_t e0,e1,e2,e3;
float32x4_t f0,f1,f2,f3;

//ranges of histogram buckets
float32x4_t buckets0=vdupq_n_f32(0);
float32x4_t buckets1=vdupq_n_f32(25);
float32x4_t buckets2=vdupq_n_f32(50);
float32x4_t buckets3=vdupq_n_f32(75);
float32x4_t buckets4=vdupq_n_f32(100);

//midpoints of ranges
float32x4_t thresholds1=vdupq_n_f32(12.5);
float32x4_t thresholds2=vdupq_n_f32(37.5);
float32x4_t thresholds3=vdupq_n_f32(62.5);
float32x4_t thresholds4=vdupq_n_f32(87.5);


for (int i=0; i<sizeOfArray;i+=16)
{
c0= vld1q_f32(&inputArray[i]);//load
c1= vld1q_f32(&inputArray[i+4]);//load
c2= vld1q_f32(&inputArray[i+8]);//load
c3= vld1q_f32(&inputArray[i+12]);//load


f0=buckets0;
f1=buckets0;
f2=buckets0;
f3=buckets0;

//register0
e0=vcgtq_f32(c0,thresholds1);
f0=vbslq_f32(e0, buckets1, f0);

e0=vcgtq_f32(c0,thresholds2);
f0=vbslq_f32(e0, buckets2, f0);

e0=vcgtq_f32(c0,thresholds3);
f0=vbslq_f32(e0, buckets3, f0);

e0=vcgtq_f32(c0,thresholds4);
f0=vbslq_f32(e0, buckets4, f0);



//register1
e1=vcgtq_f32(c1,thresholds1);
f1=vbslq_f32(e1, buckets1, f1);

e1=vcgtq_f32(c1,thresholds2);
f1=vbslq_f32(e1, buckets2, f1);

e1=vcgtq_f32(c1,thresholds3);
f1=vbslq_f32(e1, buckets3, f1);

e1=vcgtq_f32(c1,thresholds4);
f1=vbslq_f32(e1, buckets4, f1);


//register2
e2=vcgtq_f32(c2,thresholds1);
f2=vbslq_f32(e2, buckets1, f2);

e2=vcgtq_f32(c2,thresholds2);
f2=vbslq_f32(e2, buckets2, f2);

e2=vcgtq_f32(c2,thresholds3);
f2=vbslq_f32(e2, buckets3, f2);

e2=vcgtq_f32(c2,thresholds4);
f2=vbslq_f32(e2, buckets4, f2);


//register3
e3=vcgtq_f32(c3,thresholds1);
f3=vbslq_f32(e3, buckets1, f3);

e3=vcgtq_f32(c3,thresholds2);
f3=vbslq_f32(e3, buckets2, f3);

e3=vcgtq_f32(c3,thresholds3);
f3=vbslq_f32(e3, buckets3, f3);

e3=vcgtq_f32(c3,thresholds4);
f3=vbslq_f32(e3, buckets4, f3);


vst1q_f32(&finalOutputArrayF[i], f0);
vst1q_f32(&finalOutputArrayF[i+4], f1);
vst1q_f32(&finalOutputArrayF[i+8], f2);
vst1q_f32(&finalOutputArrayF[i+12], f3);
}
}
NSLog(@"done");
}

PS:这是我第一次在这种规模上进行基准测试,所以我尽量保持简单(大循环、设置代码常量、使用 NSlog 打印开始/结束时间、仅加速框架链接)。如果这些假设中的任何一个对结果有重大影响,请提出批评。

谢谢

最佳答案

首先,这本身并不是“NEON”。这是内在的。在 clang 或 gcc 下使用内部函数几乎不可能获得良好的 NEON 性能。如果您认为需要内部函数,则应该手写汇编程序。

vDSP 并不比 NEON“优化得更好”。 iOS 上的 vDSP 使用 NEON 处理器。 vDSP 对 NEON 的使用比您对 NEON 的使用优化得更好。

我还没有仔细研究您的内在代码,但最有可能(实际上几乎可以肯定)的问题是您正在创建等待状态。用汇编程序编写(内在函数只是戴着焊接手套编写的汇编程序),与用 C 语言编写完全不同。您不会循环相同。你不比较相同。你需要一种新的思维方式。在汇编中,您一次可以做不止一件事(因为您有不同的逻辑单元),但是您绝对必须以所有这些事情可以并行运行的方式来安排事情。良好的 assembly 使所有这些管道充满。如果您可以阅读您的代码并且它非常有意义,那么它可能是垃圾汇编代码。如果您从不重复自己,那可能是垃圾汇编代码。您需要仔细考虑进入哪个寄存器的内容以及有多少个周期,直到您被允许读取它。

如果它像音译 C 一样简单,那么编译器会为您完成。当你说“我要用 NEON 写这个”时,你是在说“我认为我可以写出比编译器更好的 NEON”,因为编译器也使用它。也就是说,通常可以编写比编译器(尤其是 gcc 和 clang)更好的 NEON。

如果您准备好进入那个世界(这是一个非常酷的世界),您需要阅读一些资料。以下是我推荐的一些地方:

所说的一切...总是总是总是从重新考虑你的算法开始。通常答案不是如何快速计算循环,而是如何不那么频繁地调用循环。

关于objective-c - C 对比 vDSP 对比 NEON - NEON 怎么会和 C 一样慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14926351/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com