【发布时间】:2018-04-25 12:07:28
【问题描述】:
当人们试图在各种库中执行严格的基准测试时,我有时会看到这样的代码:
auto std_start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j)
volatile const auto __attribute__((unused)) c = std_set.count(i + j);
auto std_stop = std::chrono::steady_clock::now();
这里使用volatile是为了防止优化器注意到被测代码的结果被丢弃,然后丢弃整个计算。
当被测代码没有返回值时,说它是void do_something(int),那么有时我会看到这样的代码:
auto std_start = std::chrono::steady_clock::now();
for (int i = 0; i < 10000; ++i)
for (int j = 0; j < 10000; ++j)
static_cast<volatile void> (do_something(i + j));
auto std_stop = std::chrono::steady_clock::now();
volatile 的用法正确吗? volatile void 是什么?从编译器和标准的角度来看是什么意思?
[dcl.type.cv] 的标准 (N4296) 中写道:
7 [注意:volatile 是对实现的提示,以避免涉及对象的激进优化 因为对象的值可能会通过实现无法检测到的方式进行更改。此外, 对于某些实现,易失性可能表明需要特殊的硬件指令才能访问 物体。详细语义见 1.9。一般来说, volatile 的语义旨在 在 C++ 中与在 C 中相同。 — 尾注 ]
在 1.9 节中,它指定了很多关于执行模型的指导,但就 volatile 而言,它是关于“访问volatile 对象”。我不清楚执行已转换为volatile void 的语句 意味着什么,假设我正确理解了代码,以及如果产生任何优化障碍究竟会怎样。
【问题讨论】:
-
这实际上似乎只是一个写得很糟糕的测试。测试应避免使用
volatile或其他技巧来混淆编译器,以防止与实际代码相比可能改变测试结果。 -
volatile在微基准测试中所做的完全取决于编译器。如果强制转换为volatile void得到一个特定的编译器来计算一个值,但没有花费一条指令将它实际存储到内存中,那么这可能是您在重复循环中想要的,该循环正在测试 just 只是函数的吞吐量你想测试。 -
将一次迭代的输出馈送到下一次迭代的输入可以让您测试延迟。计算它对 other 周围代码的影响是另一回事。 (例如,如果
do_something()涉及 FP 划分或 SQRT,它的吞吐量可能很差,但 low impact on surrounding code that doesn't need the divide unit 也可能。
标签: c++ benchmarking void volatile microbenchmark