8

ARM Neon命令を使用して、いくつかのSSE2コード(高速コーナー検出器スコア計算)を移植しようとしていました。コードは一見非常に単純ですが、結果は何らかの理由で異なります。重要なのは、違いが非常に大きい場合もあれば、2つまたは3つの値が異なる場合もあるということです。なぜそれが起こり得るのか誰かが説明できれば素晴らしいと思います。

これがコードです

オリジナルのSSE2:

__m128i q0 = _mm_set1_epi16(-1000), q1 = _mm_set1_epi16(1000);
for( k = 0; k < 16; k += 8 )
{
    __m128i v0 = _mm_loadu_si128((__m128i*)(d+k+1));
    __m128i v1 = _mm_loadu_si128((__m128i*)(d+k+2));
    __m128i a = _mm_min_epi16(v0, v1);
    __m128i b = _mm_max_epi16(v0, v1);
    v0 = _mm_loadu_si128((__m128i*)(d+k+3));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k+4));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k+5));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k+6));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k+7));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k+8));
    a = _mm_min_epi16(a, v0);
    b = _mm_max_epi16(b, v0);
    v0 = _mm_loadu_si128((__m128i*)(d+k));
    q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
    q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
    v0 = _mm_loadu_si128((__m128i*)(d+k+9));
    q0 = _mm_max_epi16(q0, _mm_min_epi16(a, v0));
    q1 = _mm_min_epi16(q1, _mm_max_epi16(b, v0));
}
q0 = _mm_max_epi16(q0, _mm_sub_epi16(_mm_setzero_si128(), q1));
q0 = _mm_max_epi16(q0, _mm_unpackhi_epi64(q0, q0));
q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 4));
q0 = _mm_max_epi16(q0, _mm_srli_si128(q0, 2));
threshold = (short)_mm_cvtsi128_si32(q0) - 1;

動作しているARMNEONコードは次のとおりです。

int16x8_t q0 = vdupq_n_s16(-1000), q1 = vdupq_n_s16(1000);
int16x8_t zero = vdupq_n_s16(0);
for( k = 0; k < 16; k += 8 )
{
    int16x8_t v0 = vld1q_s16((const int16_t*)(d+k+1));
    int16x8_t v1 = vld1q_s16((const int16_t*)(d+k+2));
    int16x8_t a = vminq_s16(v0, v1);
    int16x8_t b = vmaxq_s16(v0, v1);
    v0 = vld1q_s16((const int16_t*)(d+k+3));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k+4));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k+5));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k+6));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k+7));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k+8));
    a = vminq_s16(a, v0);
    b = vmaxq_s16(b, v0);
    v0 = vld1q_s16((const int16_t*)(d+k));
    q0 = vmaxq_s16(q0, vminq_s16(a, v0));
    q1 = vminq_s16(q1, vmaxq_s16(b, v0));
    v0 = vld1q_s16((const int16_t*)(d+k+9));
    q0 = vmaxq_s16(q0, vminq_s16(a, v0));
    q1 = vminq_s16(q1, vmaxq_s16(b, v0));
}
q0 = vmaxq_s16(q0, vsubq_s16(zero, q1));
// first mistake it produce wrong result
//q0 = vmaxq_s16(q0, vzipq_s16(q0, q0).val[1]);
// may be someone knows faster/better way?
int16x4_t a_hi = vget_high_s16(q0);
q1 = vcombine_s16(a_hi, a_hi);
q0 = vmaxq_s16(q0, q1);

// this is _mm_srli_si128(q0, 4)
q1 = vextq_s16(q0, zero, 2);

q0 = vmaxq_s16(q0, q1);

// this is _mm_srli_si128(q0, 2)
q1 = vextq_s16(q0, zero, 1);
q0 = vmaxq_s16(q0, q1);

// read the result
int16_t __attribute__ ((aligned (16))) x[8];
vst1q_s16(x, q0);

threshold = x[0] - 1;

このコードは正しいように思われるので、誰かがそれを便利だと思うなら...高速コーナー検出器は現時点で最速です。そして、そのような小さな最適化により、モバイルでさらに高速に実行される可能性があります。

4

0 に答える 0