C++ 谷歌基准测试结果中显示的时间毫无意义
我在我的处理器上基准测试了一些示例函数,每个核心运行在2GHz。以下是正在进行基准测试的功能。此外,可在 结果表明,前两个函数的执行时间分别为C++ 谷歌基准测试结果中显示的时间毫无意义,c++,benchmarking,google-benchmark,C++,Benchmarking,Google Benchmark,我在我的处理器上基准测试了一些示例函数,每个核心运行在2GHz。以下是正在进行基准测试的功能。此外,可在 结果表明,前两个函数的执行时间分别为0.490ns和0.858ns。 然而,我不明白的是,如果我的核心运行在2GHz,这意味着一个周期是0.5ns,这使得结果看起来不合理 我知道显示的结果是迭代次数的平均值。如此低的执行时间意味着大多数样本低于0.5ns 我错过了什么 编辑1: 从评论中可以看出,将常量i添加到x似乎不是一个好主意。事实上,我首先在虚拟函数和非虚拟函数中调用std::cout
0.490ns
和0.858ns
。
然而,我不明白的是,如果我的核心运行在2GHz,这意味着一个周期是0.5ns
,这使得结果看起来不合理
我知道显示的结果是迭代次数的平均值。如此低的执行时间意味着大多数样本低于0.5ns
我错过了什么
编辑1:
从评论中可以看出,将常量i
添加到x
似乎不是一个好主意。事实上,我首先在虚拟函数和非虚拟函数中调用std::cout
。这有助于我理解虚拟函数不是内联的,需要在运行时解析调用
然而,在被基准标记的函数中有输出在终端上看起来并不好。(有没有办法分享我的Godbolt代码?)
有人能提出一种替代方法来打印函数内部的内容吗?现代编译器的功能非常强大。不总是最可预测的事情,但通常是好事。 您可以通过按照建议观看ASM或降低优化级别来了解这一点。 Optim=1使非虚拟函数在CPU时间方面与virtualFunc相当,Optim=0将所有函数提升到一个类似的级别(编辑:当然是一种糟糕的方式;不要这样做是为了得出性能结论)
是的,当我第一次使用QuickBench时,我也被“DoNotOptimize”弄糊涂了。他们最好将其称为“UseResult()”,以表明在进行基准测试时它实际上打算假装什么。最有可能的是,优化器在您的功能上完成了它的工作—您知道现代CPU中的支持吗?或者你只是惊讶于编译器将你的
非虚拟函数
简化为一条add
指令(参见快速工作台链接中的程序集)?一个周期并不意味着它运行一条指令。你的CPU在一个周期内完成了很多事情。@斯塔克,我调用了函数benchmark::DoNotOptimize(),你的意思是这对编译器来说还不够吗?@talekeDskobeDa的意思是“编译器,请不要把我的代码简化为无操作,因为它实际上什么都不做”,而不是“编译器,请不要执行任何优化”.完全禁用优化以进行基准测试是一个糟糕的建议;这不是一个有效的建议。您的代码将有不同的瓶颈,即存储/重载C++语句之间的所有变量。(存储转发延迟)。这会产生各种奇怪的效果,比如。另请参见和。至少使用-Og
或-O1
,但最好至少使用-O2
,最好使用-O3
,并小心使用内联asm宏,如DoNotOptimize
,以强制编译器在寄存器中具体化值,和/或忘记某个值,因此必须基于该值重新计算某些内容。e、 g.作为重复循环的一部分,使某事花费可测量的时间。请记住,您是在测量延迟(循环携带依赖性)还是吞吐量(独立,具有指令级并行性(ILP)),因为对于超标量无序CPU,单个指令具有吞吐量!=延迟。@PeterCordes-Ehm,我没有建议他禁用优化,然后从中得出性能方面的结论!然而,他可以使用这个切换来理解为什么代码在优化中表现得出奇地好。然而,总的来说,我会说,像那些需要几纳秒时间的小语句这样的小语句没有多大意义。如果您将一些主要的计算作为一个整体进行基准测试,那么您在项目中实际优化代码的机会会高得多。您提到的optim=0
将所有函数提升到了类似的水平。如果不添加任何红色标志,如“(永远不要这样做,结果不会告诉您正常编译时的性能情况)”。人们经常会错误地认为,禁用优化使其基准不进行优化总比什么都不优化好。事实并非如此,所以在我看来,消除这种误解是一个好主意。老实说,我甚至写过“通过降低优化级别”,我很难下定决心去理解我的文章,但正如你所说。。。我将简要编辑。
#include <stdlib.h>
#include <time.h>
#include <memory>
class Base
{
public:
virtual int addNumVirt( int x ) { return (i + x); }
int addNum( int x ) { return (x + i); }
virtual ~Base() {}
private:
uint32_t i{10};
};
class Derived : public Base
{
public:
// Overrides of virtual functions are always virtual
int addNumVirt( int x ) { return (x + i); }
int addNum( int x ) { return (x + i); }
private:
uint32_t i{20};
};
static void BM_nonVirtualFunc(benchmark::State &state)
{
srand(time(0));
volatile int x = rand();
std::unique_ptr<Derived> derived = std::make_unique<Derived>();
for (auto _ : state)
{
auto result = derived->addNum( x );
benchmark::DoNotOptimize(result);
}
}
BENCHMARK(BM_nonVirtualFunc);
static void BM_virtualFunc(benchmark::State &state)
{
srand(time(0));
volatile int x = rand();
std::unique_ptr<Base> derived = std::make_unique<Derived>();
for (auto _ : state)
{
auto result = derived->addNumVirt( x );
benchmark::DoNotOptimize(result);
}
}
BENCHMARK(BM_virtualFunc);
static void StringCreation(benchmark::State& state) {
// Code inside this loop is measured repeatedly
for (auto _ : state) {
std::string created_string("hello");
// Make sure the variable is not optimized away by compiler
benchmark::DoNotOptimize(created_string);
}
}
// Register the function as a benchmark
BENCHMARK(StringCreation);
static void StringCopy(benchmark::State& state) {
// Code before the loop is not measured
std::string x = "hello";
for (auto _ : state) {
std::string copy(x);
}
}
BENCHMARK(StringCopy);
Run on (64 X 2000 MHz CPU s)
CPU Caches:
L1 Data 32K (x32)
L1 Instruction 64K (x32)
L2 Unified 512K (x32)
L3 Unified 8192K (x8)
Load Average: 0.08, 0.04, 0.00
------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------
BM_nonVirtualFunc 0.490 ns 0.490 ns 1000000000
BM_virtualFunc 0.858 ns 0.858 ns 825026009
StringCreation 2.74 ns 2.74 ns 253578500
BM_StringCopy 5.24 ns 5.24 ns 132874574