Какой метод управления циклом очистки кода для совокупного (одиночного) значения упакован в два значения с использованием SIMD? - PullRequest
0 голосов
/ 10 января 2019

Допустим, я управляю __m128d переменной с именем v_phase, которая рассчитывается как

index 0 : load prev phase
index 1 : phase += newValue
index 2 : phase += newValue
index 3 : phase += newValue
index 4 : phase += newValue
...

Это основной код:

__m128d v_phase;

// load prev cumulated mPhase to v_phase (as mPhase, mPhase + nextValue)

for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex += 2, pValue += 2) {
    // function with phase

    // update pValue increment (its not linear)

    // phase increment: v_phase += newValue
}

// cleanup code
if (blockSize % 2 == 0) {
    mPhase = v_phase.m128d_f64[0];
}

Дело в том, что если blockSize является четным, он работает нормально: он будет суммировать в последней итерации цикла еще два значения фазы и принимать v_phase.m128d_f64[0] (то есть первое из нового два сложения).

Но что, если blockSize нечетен? Мне просто понадобится v_phase.m128d_f64[1] последней итерации без суммы еще двух значений фазы .

Я мог бы использовать sampleIndex < blockSize - 1, но это переместит логику // function with phase в пределах // cleanup code (что мне не очень нравится).

Если в цикле есть то, чего я буду избегать (предсказание Бранка; поскольку я использую SIMD, я оптимизирую код, это замедлится).

Любые советы?

Вот более «полный» пример:

double phase = mPhase;

__m128d v_pB = _mm_setr_pd(0.0, pB[0]);
v_pB = _mm_mul_pd(v_pB, v_radiansPerSampleBp0);
__m128d v_pC = _mm_setr_pd(0.0, pC[0]);
v_pC = _mm_mul_pd(v_pC, v_radiansPerSample);

__m128d v_pB_prev = _mm_setr_pd(0.0, 0.0);
v_pB_prev = _mm_mul_pd(v_pB_prev, v_radiansPerSampleBp0);
__m128d v_pC_prev = _mm_setr_pd(0.0, 0.0);
v_pC_prev = _mm_mul_pd(v_pC_prev, v_radiansPerSample);

__m128d v_phaseAcc1;
__m128d v_phaseAcc2;
__m128d v_phase = _mm_set1_pd(phase);

// phase
v_phaseAcc1 = _mm_add_pd(v_pB, v_pC);
v_phaseAcc1 = _mm_max_pd(v_phaseAcc1, v_boundLower);
v_phaseAcc1 = _mm_min_pd(v_phaseAcc1, v_boundUpper);
v_phaseAcc2 = _mm_add_pd(v_pB_prev, v_pC_prev);
v_phaseAcc2 = _mm_max_pd(v_phaseAcc2, v_boundLower);
v_phaseAcc2 = _mm_min_pd(v_phaseAcc2, v_boundUpper);
v_phase = _mm_add_pd(v_phase, v_phaseAcc1);
v_phase = _mm_add_pd(v_phase, v_phaseAcc2);

for (int sampleIndex = 0; sampleIndex < blockSize; sampleIndex += 2, pB += 2, pC += 2) {
    // code that will use v_phase

    // phase increment
    v_pB = _mm_loadu_pd(pB + 1);
    v_pB = _mm_mul_pd(v_pB, v_radiansPerSampleBp0);
    v_pC = _mm_loadu_pd(pC + 1);
    v_pC = _mm_mul_pd(v_pC, v_radiansPerSample);

    v_pB_prev = _mm_load_pd(pB);
    v_pB_prev = _mm_mul_pd(v_pB_prev, v_radiansPerSampleBp0);
    v_pC_prev = _mm_load_pd(pC);
    v_pC_prev = _mm_mul_pd(v_pC_prev, v_radiansPerSample);

    v_phaseAcc1 = _mm_add_pd(v_pB, v_pC);
    v_phaseAcc1 = _mm_max_pd(v_phaseAcc1, v_boundLower);
    v_phaseAcc1 = _mm_min_pd(v_phaseAcc1, v_boundUpper);
    v_phaseAcc2 = _mm_add_pd(v_pB_prev, v_pC_prev);
    v_phaseAcc2 = _mm_max_pd(v_phaseAcc2, v_boundLower);
    v_phaseAcc2 = _mm_min_pd(v_phaseAcc2, v_boundUpper);
    v_phase = _mm_add_pd(v_phase, v_phaseAcc1);
    v_phase = _mm_add_pd(v_phase, v_phaseAcc2);
}

// cleanup code
if (blockSize % 2 == 0) {
    mPhase = v_phase.m128d_f64[0];
}
else {
    ??? if odd?
}

1 Ответ

0 голосов
/ 10 января 2019

Вы можете также вывести предыдущий v_phase из вашего цикла, в дополнение к последнему. То есть перед обновлением v_phase сохраните предыдущее:

__m128d prev_v_phase;
for (...) {
    ...
    prev_v_phase = v_phase;
    v_phase = _mm_add_pd(v_phase, v_phaseAcc1);
    v_phase = _mm_add_pd(v_phase, v_phaseAcc2);
}

// cleanup code
if (blockSize % 2 == 0) {
    mPhase = v_phase.m128d_f64[0];
}
else {
    mPhase = prev_v_phase.m128d_f64[1];
}

Это не удастся, если цикл вообще не выполнит никаких итераций (тогда prev_v_phase будет неинициализирован), но это тот случай, когда производительность не важна, поэтому ее легко обрабатывать.

...