FMA 性能与朴素计算的比较
FMA performance compared to naive calculation
我正在尝试比较 FMA 性能(math.h
中的 fma()
)与浮点计算中的朴素乘法和加法。测试很简单。我将为大迭代次数迭代相同的计算。为了精确检查,我必须完成两件事。
- 其他计算不应计入计算时间。
- 朴素的乘法和加法不应优化为 FMA
- 不应优化迭代。即迭代应该完全按照我的意图进行。
为了实现上述目标,我做了以下工作:
- 函数是内联的,只包含必需的计算。
- 使用 g++
-O0
选项不优化乘法。 (但是当我查看转储文件时,它似乎为两者生成了几乎相同的代码)
- 二手
volatile
.
但结果显示几乎没有区别,甚至比简单的乘法和加法更慢 fma()
。 是我想要的结果(即它们在速度方面并没有真正的不同)还是我做错了什么?
规格
- Ubuntu 14.04.2
- G++ 4.8.2
- 英特尔(R) 酷睿(TM) i7-4770(3.4GHz,8MB 三级缓存)
我的代码
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <chrono>
using namespace std;
using namespace chrono;
inline double rand_gen() {
return static_cast<double>(rand()) / RAND_MAX;
}
volatile double a, b, c;
inline void pure_fma_func() {
fma(a, b, c);
}
inline void non_fma_func() {
a * b + c;
}
int main() {
int n = 100000000;
a = rand_gen();
b = rand_gen();
c = rand_gen();
auto t1 = system_clock::now();
for (int i = 0; i < n; i++) {
non_fma_func();
}
auto t2 = system_clock::now();
for (int i = 0; i < n; i++) {
pure_fma_func();
}
auto t3 = system_clock::now();
cout << "non fma" << endl;
cout << duration_cast<microseconds>(t2 - t1).count() / 1000.0 << "ms" << endl;
cout << "fma" << endl;
cout << duration_cast<microseconds>(t3 - t2).count() / 1000.0 << "ms" << endl;
}
是的,你做的事情完全错了。至少有两个东西。但让我们保持简单。
Used g++ -O0 option not to optimize the multiplication
这会使您的整个结果完全不相关。有趣的事实:在任何一种情况下,函数调用的成本都可能超过计算的成本。
从根本上说,未启用优化的基准测试结果完全没有意义。您不能只是关闭它们并希望获得最好的结果。它们绝对必须启用。
其次,FMA 与常规乘加法是一个复杂的情况 - 延迟与吞吐量以及乘加法可以成为赢家的其他问题。
简而言之,你的基准测试根本不是基准测试,它只是一堆产生无意义垃圾的随机指令。
如果你想要一个准确的基准,你必须完全准确地再现实际使用环境。包括周边代码、编译器优化、整个 shebang。
我正在尝试比较 FMA 性能(math.h
中的 fma()
)与浮点计算中的朴素乘法和加法。测试很简单。我将为大迭代次数迭代相同的计算。为了精确检查,我必须完成两件事。
- 其他计算不应计入计算时间。
- 朴素的乘法和加法不应优化为 FMA
- 不应优化迭代。即迭代应该完全按照我的意图进行。
为了实现上述目标,我做了以下工作:
- 函数是内联的,只包含必需的计算。
- 使用 g++
-O0
选项不优化乘法。 (但是当我查看转储文件时,它似乎为两者生成了几乎相同的代码) - 二手
volatile
.
但结果显示几乎没有区别,甚至比简单的乘法和加法更慢 fma()
。 是我想要的结果(即它们在速度方面并没有真正的不同)还是我做错了什么?
规格
- Ubuntu 14.04.2
- G++ 4.8.2
- 英特尔(R) 酷睿(TM) i7-4770(3.4GHz,8MB 三级缓存)
我的代码
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <chrono>
using namespace std;
using namespace chrono;
inline double rand_gen() {
return static_cast<double>(rand()) / RAND_MAX;
}
volatile double a, b, c;
inline void pure_fma_func() {
fma(a, b, c);
}
inline void non_fma_func() {
a * b + c;
}
int main() {
int n = 100000000;
a = rand_gen();
b = rand_gen();
c = rand_gen();
auto t1 = system_clock::now();
for (int i = 0; i < n; i++) {
non_fma_func();
}
auto t2 = system_clock::now();
for (int i = 0; i < n; i++) {
pure_fma_func();
}
auto t3 = system_clock::now();
cout << "non fma" << endl;
cout << duration_cast<microseconds>(t2 - t1).count() / 1000.0 << "ms" << endl;
cout << "fma" << endl;
cout << duration_cast<microseconds>(t3 - t2).count() / 1000.0 << "ms" << endl;
}
是的,你做的事情完全错了。至少有两个东西。但让我们保持简单。
Used g++ -O0 option not to optimize the multiplication
这会使您的整个结果完全不相关。有趣的事实:在任何一种情况下,函数调用的成本都可能超过计算的成本。
从根本上说,未启用优化的基准测试结果完全没有意义。您不能只是关闭它们并希望获得最好的结果。它们绝对必须启用。
其次,FMA 与常规乘加法是一个复杂的情况 - 延迟与吞吐量以及乘加法可以成为赢家的其他问题。
简而言之,你的基准测试根本不是基准测试,它只是一堆产生无意义垃圾的随机指令。
如果你想要一个准确的基准,你必须完全准确地再现实际使用环境。包括周边代码、编译器优化、整个 shebang。