|
昨日のカウンタ: 今日のカウンタ: |
せっかくAndroidで画像のモノクロ化の速度比較をしたのだから、ついでのiOSでもやっておこうか。それぐらいの気持で速度を測り始めたのですが、小手先の技は通用するかな、最適化意識したらどうなるのかな、という方向で調べてたら、ちょっと時間がかかってしまいました。
iOS側では画像とそれをバイト列としてラスターデータの間の変換処理は時間に含めていません。インスタンス変数のsourceDataにNSDataとして画像のバイト列をあらかじめ格納しておきます。そしてsourceDataからNSData* targetDataにモノクロ化後のデータを入れるまでの処理を計測の対象とします。
説明の前に先に計測した時間を示します。Androidの時と同じく1024×768の画像を100回モノクロ化しています。NDKの時と全く同じアルゴリズムのものが表の一番上の「uchar」の行です。最適化オプションは配布ビルド時のデフォルトである-Osとしています。*1
機種名 | iPhone 5s (-Os) | iPhone 5 (-Os) | iPhone 4 (-Os) |
---|---|---|---|
uchar | 307 | 621 | 2529 |
uint32 | 341 | 657 | 2422 |
uint64 | 231 | 767 | 2539 |
uint16x8 | 165 | 1191 | 3347 |
float | 447 | 1151 | 6159 |
今回はどういう風に書いたら最適化が効きやすいのかな、CPUアーキテクチャを意識してコード書いたら最適化はどうなるのかなといった視点でいくつかパターンを用意してみました。uchar, uint32, uint64, uint16×8は全て整数演算で、floatだけは参考情報として浮動小数点演算で計算させてみました。
整数演算の中ではucharが一番無難なコード、uint32がARMv7までの32ビットで速くならないかなと意識したコード、uint64が64ビットCPUで速くならないかなと意識したコード、uint16x8が16ビット×8個(128ビット)のNEONの計算を素に書いたコードとなります。各々を簡単に説明してみましょう。
static const unsigned int LoopCount = 100; static const unsigned int r_lum_i = 76; static const unsigned int g_lum_i = 150; static const unsigned int b_lum_i = 30; // (中略) - (void)mono_uchar { unsigned char const *inp = (unsigned char*)sourceData.bytes; unsigned char *outp = (unsigned char*)targetData.mutableBytes; int length4 = bitmapHeight * bitmapWidth * 4; int m; int r, g, b; for (int j = 0; j < LoopCount; j ++) for (int i = 0; i < length4; i += 4) { r = inp[i + 0]; g = inp[i + 1]; b = inp[i + 2]; m = (r * r_lum_i + g * g_lum_i + b * b_lum_i) >> 8; outp[i + 3] = 0xff; outp[i + 0] = outp[i + 1] = outp[i + 2] = m ; } }
int length = bitmapHeight * bitmapWidth; uint32_t s; uint m; for (int j = 0; j < LoopCount; j ++) for (int i = 0; i < length; i ++) { s = inp[i]; m = (((s >> 16) & 0xff) * b_lum_i + ((s >> 8) & 0xff) * g_lum_i + ((s >> 0) & 0xff) * r_lum_i) >> 8; outp[i] = 0xff000000 | m | (m << 8) | (m << 16); }上の最適化-Osの場合では、そんなに速くなってないですね。iPhone4では一番速いとなってますが、誤差の範囲ですし。
const int length_2 = bitmapWidth * bitmapHeight >> 1; uint64_t s, m; for (uint j = 0; j < LoopCount; j ++) for (uint i = 0; i < length_2; i ++) { s = inp[i]; m = ((((s >> 16) & 0xff000000ff) * b_lum_i) + (((s >> 8) & 0xff000000ff) * g_lum_i) + (((s >> 0) & 0xff000000ff) * r_lum_i)) & 0xff000000ff00; outp[i] = 0xff000000ff000000 | (m << 8) | m | (m >> 8); }iPhone5やiPhone4ではCPUとして64ビットレジスタがありませんし、遅くなるのは仕方ありません。(むしろ思ったほど遅くなってませんでした。) iPhone5sではucharやuint32より明確に速くなってるのでちょっと嬉しいですね。
const uint length_2 = bitmapWidth * bitmapHeight >> 1; const uint16x8_t lum_i = vmovl_u8(vcreate_u8((0x0001000000010000 * b_lum_i) | (0x0000010000000100 * g_lum_i) | (0x0000000100000001 * r_lum_i))); uint16x8_t sv, mv; uint64_t m; for (uint j = 0; j < LoopCount; j ++) for (uint i = 0; i < length_2; i ++) { sv = vmovl_u8(vld1_u8(inp + i * 8)); mv = vmulq_u16(sv, lum_i); mv = vshrq_n_u16(mv, 8); m = (uint64_t)vmovn_u16(mv); m = ((m >> 16) + (m >> 8) + m) & 0xff000000ff; outp[i] = 0xff000000ff000000 | (m * 0x010101); }で、結果はというと、iPhone5sでの-Osでは最速です。うーん。こんな小手先の技が通用しちゃうんですね。ちょっと残念。
uint32_t s; float r, g, b; uint m; for (int j = 0; j < LoopCount; j ++) for (int i = 0; i < length; i ++) { s = inp[i]; r = (float)(s & 0x0000ff); g = (float)(s & 0x00ff00); b = (float)(s & 0xff0000); m = (uint)(r * (r_lum_f / 0x0000ff * 255) + g * (g_lum_f / 0x00ff00 * 255) + b * (b_lum_f / 0xff0000 * 255)); outp[i] = 0xff000000L | (m << 16) | (m << 8) | m; }計算結果を見るとどの機種でも、整数演算(uchar, uint32, uint64, uint16x8)のどれよりも遅くなっていますが、仕方ないところでしょうか。