NEON が C のように遅いのはなぜでしょうか?
私は、入力値に値を割り当てることにより、入力値を範囲にバケット化する高速ヒストグラム関数を構築しようとしています。これは、最も近い範囲のしきい値です。これは画像に適用されるものなので、高速である必要があります (画像配列が 640x480 で 300,000 要素であると仮定します)。ヒストグラム範囲の数値は (0,25,50,75,100) の倍数です。入力は浮動小数点数で、最終出力は明らかに整数になります
新しい空のプロジェクト (アプリ デリゲートなし) を開き、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 の実装です。面倒な整数への浮動小数点演算を行ったり来たりしても、わずか 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 を使用した開始/終了時間の出力、リンクされたフレームワークの高速化のみ)。これらの仮定のいずれかが結果に大きな影響を与えている場合は、批判してください。
ありがとう