【发布时间】:2019-02-08 20:19:39
【问题描述】:
首先,我是 SSE 的新手。我决定加速我的代码,但它似乎比我的本机代码运行得更慢。
这是一个计算平方和的示例。在我的 Intel i7-6700HQ 上,本地代码需要 0.43 秒,SSE 需要 0.52。那么,瓶颈在哪里呢?
inline float squared_sum(const float x, const float y)
{
return x * x + y * y;
}
#define USE_SIMD
void calculations()
{
high_resolution_clock::time_point t1, t2;
int result_v = 0;
t1 = high_resolution_clock::now();
alignas(16) float data_x[4];
alignas(16) float data_y[4];
alignas(16) float result[4];
__m128 v_x, v_y, v_res;
for (int y = 0; y < 5120; y++)
{
data_y[0] = y;
data_y[1] = y + 1;
data_y[2] = y + 2;
data_y[3] = y + 3;
for (int x = 0; x < 5120; x++)
{
data_x[0] = x;
data_x[1] = x + 1;
data_x[2] = x + 2;
data_x[3] = x + 3;
#ifdef USE_SIMD
v_x = _mm_load_ps(data_x);
v_y = _mm_load_ps(data_y);
v_x = _mm_mul_ps(v_x, v_x);
v_y = _mm_mul_ps(v_y, v_y);
v_res = _mm_add_ps(v_x, v_y);
_mm_store_ps(result, v_res);
#else
result[0] = squared_sum(data_x[0], data_y[0]);
result[1] = squared_sum(data_x[1], data_y[1]);
result[2] = squared_sum(data_x[2], data_y[2]);
result[3] = squared_sum(data_x[3], data_y[3]);
#endif
result_v += (int)(result[0] + result[1] + result[2] + result[3]);
}
}
t2 = high_resolution_clock::now();
duration<double> time_span1 = duration_cast<duration<double>>(t2 - t1);
std::cout << "Exec time:\t" << time_span1.count() << " s\n";
}
更新:根据 cmets 修复代码。
我使用的是 Visual Studio 2017。针对 x64 编译。
- 优化:最大优化(优先速度)(/O2);
- 内联函数扩展:任何合适的 (/Ob2);
- 偏爱大小或速度:偏爱快速代码 (/Ot);
- 省略帧指针:是 (/Oy)
结论
编译器生成已经优化的代码,因此现在很难进一步加速。为了进一步加速代码,您可以做的一件事是并行化。
感谢您的回答。它们基本相同,所以我接受 Søren V. Poulsen 的回答,因为这是第一个。
【问题讨论】:
-
编译器is already using SIMD。 (将来,请让您的代码准备好复制/粘贴。这意味着修复像
tbb这样的类型而不是std和##endif而不是#endif。) -
@RaymondChen:我推测
tbb是英特尔“线程构建模块”使用的命名空间,而不是错误输入的std。 -
这并非不可能,但会非常困难。一种方法是优化算法,而不是尝试优化算法的步骤。例如,
result向量始终为{ x*x + y+y, (x+1)*(x+1) + (y+1)*(y+1), (x+2)*(x+2) + (y+2)*(y+2), (x+3)*(x+3)+(y+3)*(y+3) }。y部分在循环中不会改变,因此您不必继续计算它,x部分可以降低强度。实际上,结果总是一样的,所以你可以预先计算整个事情并将result_v设置为最终答案,并且根本不运行循环。 -
@user1554270 确保您的性能测试实际上类似于您的实际用例。如果您动态生成数据或从内存中读取数据,则会产生显着差异。此外,编译器有时足够聪明,可以在编译时评估整个循环(特别是如果您在任何地方都使用整数)。
-
@chtz:是的,但是即使没有
-ffast-math,GCC 也会与 FMA 签订合同(这是正确的技术术语,不是崩溃,哎呀)。这是合法的within ISO C 中的一个表达式(我假设是 C++),但在表达式之间不是严格合法的(例如嵌套内在函数或在单独的语句中分配给 tmp 变量。)How to use Fused Multiply-Add (FMA) instructions with SSE/AVX。默认情况下,允许编译器打开#pragma STDC FP_CONTRACT ON。但是 GCC 仍然在这里违反规则。不过,这通常是件好事。