你将如何优化这个向量化的谐波和?
how would you optimize this vectorized sum of harmonics?
我正在使用矢量化(仅 SSE2 max 作为 SIMD)将大量谐波加在一起,每个谐波具有不同 phase/magnitude。
这是我的实际尝试:
float output = 0.0f;
simd::float_4 freqFundamentalNormalized = freq * (1.0f / sampleRate);
simd::float_4 harmonicIndex{1.0f, 2.0f, 3.0f, 4.0f};
simd::float_4 harmonicIncrement{4.0f, 4.0f, 4.0f, 4.0f};
// harmonics
const int numHarmonicsV4 = numHarmonics / 4;
const int numHarmonicsRemainder = numHarmonics - (numHarmonicsV4 * 4);
// v4
for (int i = 0; i < numHarmonicsV4; i++) {
// signal
simd::float_4 sineOutput4 = simd::sin(mPhases4[i] * g2PIf) * mMagnitudes4[i];
for (int v = 0; v < 4; v++) {
output += sineOutput4[v];
}
// increments
mPhases4[i] += harmonicIndex * freqFundamentalNormalized;
mPhases4[i] -= simd::floor(mPhases4[i]);
harmonicIndex += harmonicIncrement;
}
// remainder
if (numHarmonicsRemainder > 0) {
// signal
simd::float_4 sineOutput4 = simd::sin(mPhases4[numHarmonicsV4] * g2PIf) * mMagnitudes4[numHarmonicsV4];
for (int v = 0; v < numHarmonicsRemainder; v++) {
output += sineOutput4[v];
}
// increments
mPhases4[numHarmonicsV4] += harmonicIndex * freqFundamentalNormalized;
mPhases4[numHarmonicsV4] -= simd::floor(mPhases4[numHarmonicsV4]);
}
但是:
- 我想我可以进一步优化它,也许用一些数学技巧,或者以一些增量保存
- 我不喜欢为
V4
重复一次“相同的代码”,为 remainder
重复一次(如果谐波数不是 %4):有没有办法把最后一个 V4 的一种“掩码”(例如)将幅度设置为 0? (因此它在同一个块中执行相同的操作,但不会求和到最终输出)。
问题的第二部分是最简单的。任何幅度为 0 的谐波都不会影响正弦输出,因此您只需将 mMagnitude
填充为 4 的倍数即可。
正如 Damien 指出的那样,sin(x)
很昂贵。但是根据 Euler,exp(x)=cos(x) + i sin(x)
和 exp(x+dx)==exp(x)*exp(dx)
。每一步只是一个复杂的乘法。
首先,确保 simd::sin
的实施速度很快。请参阅 XMVectorSin
,尤其是 XMVectorSinEst
in DirectXMath library 以了解如何制作快速的示例,或从那里 copy-paste,或包括库,它是 header-only。指令集可与预处理器宏切换,为获得最佳性能,它需要 SSE 4.1 和 FMA3,但仅适用于 SSE2。
如评论中所述,在循环的所有迭代完成后,您应该只进行一次水平添加。到那时,累积到 SIMD 向量中。
非常小,可能会被编译器优化,但是,您仍然不应该像现在这样访问 mPhases4
。在循环体开始时将值加载到向量中,计算输出、递增、计算小数部分,并在每次迭代中仅存储一次更新值。
我正在使用矢量化(仅 SSE2 max 作为 SIMD)将大量谐波加在一起,每个谐波具有不同 phase/magnitude。
这是我的实际尝试:
float output = 0.0f;
simd::float_4 freqFundamentalNormalized = freq * (1.0f / sampleRate);
simd::float_4 harmonicIndex{1.0f, 2.0f, 3.0f, 4.0f};
simd::float_4 harmonicIncrement{4.0f, 4.0f, 4.0f, 4.0f};
// harmonics
const int numHarmonicsV4 = numHarmonics / 4;
const int numHarmonicsRemainder = numHarmonics - (numHarmonicsV4 * 4);
// v4
for (int i = 0; i < numHarmonicsV4; i++) {
// signal
simd::float_4 sineOutput4 = simd::sin(mPhases4[i] * g2PIf) * mMagnitudes4[i];
for (int v = 0; v < 4; v++) {
output += sineOutput4[v];
}
// increments
mPhases4[i] += harmonicIndex * freqFundamentalNormalized;
mPhases4[i] -= simd::floor(mPhases4[i]);
harmonicIndex += harmonicIncrement;
}
// remainder
if (numHarmonicsRemainder > 0) {
// signal
simd::float_4 sineOutput4 = simd::sin(mPhases4[numHarmonicsV4] * g2PIf) * mMagnitudes4[numHarmonicsV4];
for (int v = 0; v < numHarmonicsRemainder; v++) {
output += sineOutput4[v];
}
// increments
mPhases4[numHarmonicsV4] += harmonicIndex * freqFundamentalNormalized;
mPhases4[numHarmonicsV4] -= simd::floor(mPhases4[numHarmonicsV4]);
}
但是:
- 我想我可以进一步优化它,也许用一些数学技巧,或者以一些增量保存
- 我不喜欢为
V4
重复一次“相同的代码”,为remainder
重复一次(如果谐波数不是 %4):有没有办法把最后一个 V4 的一种“掩码”(例如)将幅度设置为 0? (因此它在同一个块中执行相同的操作,但不会求和到最终输出)。
问题的第二部分是最简单的。任何幅度为 0 的谐波都不会影响正弦输出,因此您只需将 mMagnitude
填充为 4 的倍数即可。
正如 Damien 指出的那样,sin(x)
很昂贵。但是根据 Euler,exp(x)=cos(x) + i sin(x)
和 exp(x+dx)==exp(x)*exp(dx)
。每一步只是一个复杂的乘法。
首先,确保 simd::sin
的实施速度很快。请参阅 XMVectorSin
,尤其是 XMVectorSinEst
in DirectXMath library 以了解如何制作快速的示例,或从那里 copy-paste,或包括库,它是 header-only。指令集可与预处理器宏切换,为获得最佳性能,它需要 SSE 4.1 和 FMA3,但仅适用于 SSE2。
如评论中所述,在循环的所有迭代完成后,您应该只进行一次水平添加。到那时,累积到 SIMD 向量中。
非常小,可能会被编译器优化,但是,您仍然不应该像现在这样访问 mPhases4
。在循环体开始时将值加载到向量中,计算输出、递增、计算小数部分,并在每次迭代中仅存储一次更新值。