为什么基准测试 mod 运算符 (%) 经常显示 0 耗时,即使是 5,000 轮?
Why does benchmarking mod operator (%) often show 0 time taken, even for 5,000 rounds?
我想了解模数 (%
) 运算符的运行速度。我已经设置了一个简单的程序来对 %
应用于随机生成的值进行基准测试。使用高分辨率时钟以纳秒为单位测量时间。它经常报告 0ns 已经过去。显然什么都不会瞬间发生,那为什么会这样呢?如果我将轮数增加到大约 50,000,通常需要大约 1,000,000ns。但即使是 5000 轮也总是 0ns 。我测量错了吗?正在进行什么优化以允许这样做?
#include <iostream>
#include <chrono>
#include <random>
void runTest(const int rounds, const int min, const int max);
int main()
{
std::cout << "started" << std::endl;
runTest(5000, 1000000, 2000000);
return 0;
}
/*IN: number of rounds to run on the test, the min and max value to choose between for operands to mod
OUT: time taken (in nanoseconds) to complete each operation on the same randomly generated numbers*/
void runTest(const int rounds, const int min, const int max)
{
std::random_device rd; // only used once to initialise (seed) engine
std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased
std::chrono::nanoseconds durationNormalMod = std::chrono::nanoseconds::zero();
std::chrono::nanoseconds durationFastMod = std::chrono::nanoseconds::zero();
long long result = 0;
for(auto i = 0; i < rounds; i++)
{
const int leftOperand = uni(rng);
const int rightOperand = uni(rng);
auto t1 = std::chrono::high_resolution_clock::now();
long long x = (leftOperand % rightOperand);
auto t2 = std::chrono::high_resolution_clock::now();
//std::cout << "x: " << x << std::endl;
result += x;
durationNormalMod += std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1);
}
std::cout << "duration of %: " << durationNormalMod.count() << std::endl;
std::cout << "result: " << result << std::endl;//preventing optimization by using result
}
我用g++ prog.cpp -o prog.exe -O3
编译。
我很感兴趣,因为我有一个特定的案例,我可以使用不同的算法实现模数,我很好奇它是否更快。
时钟,即使是 high_resolution_clock 时钟,在大多数实现中都没有足够精细的粒度来测量纳秒。
您必须连续执行多个操作,并将总时间除以重复次数。您已经有一个循环:将时间测量移到循环外部。并增加重复次数。
不过这有点棘手,因为循环有时会导致编译器执行一些向量运算而不是预期的琐碎重复。
您还必须使用计算结果,否则优化器根本不会执行它。
针对所谓的"as-if rule"进行了优化。只要可观察到的行为相同,编译器就可以对您的代码执行任何转换。由于您不使用计算结果,因此当根本未完成计算时,没有可观察到的行为会发生变化。测量的时间不算作可观察的行为(参见 )。
进行基准测试时,重要的是:
以某种方式使用计算结果。任何从未使用过的结果以及导致它的计算都可以并且将会被优化编译器删除。
时间不是单个计算,而是多个计算,这样时钟访问之间经过的时间大约为毫秒。这是因为时钟访问是一项成本相对较高的操作,并且会显着扭曲非常快速计算的时间。
禁用 CPU 时钟频率缩放或至少在测量时间之前预热 CPU。
我想了解模数 (%
) 运算符的运行速度。我已经设置了一个简单的程序来对 %
应用于随机生成的值进行基准测试。使用高分辨率时钟以纳秒为单位测量时间。它经常报告 0ns 已经过去。显然什么都不会瞬间发生,那为什么会这样呢?如果我将轮数增加到大约 50,000,通常需要大约 1,000,000ns。但即使是 5000 轮也总是 0ns 。我测量错了吗?正在进行什么优化以允许这样做?
#include <iostream>
#include <chrono>
#include <random>
void runTest(const int rounds, const int min, const int max);
int main()
{
std::cout << "started" << std::endl;
runTest(5000, 1000000, 2000000);
return 0;
}
/*IN: number of rounds to run on the test, the min and max value to choose between for operands to mod
OUT: time taken (in nanoseconds) to complete each operation on the same randomly generated numbers*/
void runTest(const int rounds, const int min, const int max)
{
std::random_device rd; // only used once to initialise (seed) engine
std::mt19937 rng(rd()); // random-number engine used (Mersenne-Twister in this case)
std::uniform_int_distribution<int> uni(min,max); // guaranteed unbiased
std::chrono::nanoseconds durationNormalMod = std::chrono::nanoseconds::zero();
std::chrono::nanoseconds durationFastMod = std::chrono::nanoseconds::zero();
long long result = 0;
for(auto i = 0; i < rounds; i++)
{
const int leftOperand = uni(rng);
const int rightOperand = uni(rng);
auto t1 = std::chrono::high_resolution_clock::now();
long long x = (leftOperand % rightOperand);
auto t2 = std::chrono::high_resolution_clock::now();
//std::cout << "x: " << x << std::endl;
result += x;
durationNormalMod += std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1);
}
std::cout << "duration of %: " << durationNormalMod.count() << std::endl;
std::cout << "result: " << result << std::endl;//preventing optimization by using result
}
我用g++ prog.cpp -o prog.exe -O3
编译。
我很感兴趣,因为我有一个特定的案例,我可以使用不同的算法实现模数,我很好奇它是否更快。
时钟,即使是 high_resolution_clock 时钟,在大多数实现中都没有足够精细的粒度来测量纳秒。
您必须连续执行多个操作,并将总时间除以重复次数。您已经有一个循环:将时间测量移到循环外部。并增加重复次数。
不过这有点棘手,因为循环有时会导致编译器执行一些向量运算而不是预期的琐碎重复。
您还必须使用计算结果,否则优化器根本不会执行它。
针对所谓的"as-if rule"进行了优化。只要可观察到的行为相同,编译器就可以对您的代码执行任何转换。由于您不使用计算结果,因此当根本未完成计算时,没有可观察到的行为会发生变化。测量的时间不算作可观察的行为(参见
进行基准测试时,重要的是:
以某种方式使用计算结果。任何从未使用过的结果以及导致它的计算都可以并且将会被优化编译器删除。
时间不是单个计算,而是多个计算,这样时钟访问之间经过的时间大约为毫秒。这是因为时钟访问是一项成本相对较高的操作,并且会显着扭曲非常快速计算的时间。
禁用 CPU 时钟频率缩放或至少在测量时间之前预热 CPU。