【发布时间】:2017-07-26 14:16:26
【问题描述】:
我们正在开发一段代码,用于检查何时不允许用户在一段时间内进入某个扇区,我的一位同事创建了一个函数,在下面的代码中是 isAllowed em> 并包含几个比较,我采用了不同的方法,即函数 isAllowed2 它使用时间段之间的秒数。
一开始我们并没有怀疑他的功能会更快,但实际运行代码并比较速度时并非如此,即使差异是我们可以完全忽略的,我们想知道为什么会这样“应该”更快的那个实际上更慢。
考虑以下代码:
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;
struct timing {
short hour;
short minute;
};
bool isAllowed(timing &from, timing &to, timing &actual) {
return !(((from.hour > to.hour && (actual.hour >= from.hour || actual.hour <= to.hour)) ||
(actual.hour >= from.hour && actual.hour <= to.hour)) &&
!(actual.minute > from.minute && actual.minute < to.minute));
}
long getSecs(short hour, short minutes) {
return (hour * 3600) + (minutes * 60);
}
bool isAllowed2(timing &from, timing &to, timing ¤t) {
long secsFrom = getSecs(from.hour, from.minute);
long secsTo = getSecs(to.hour, to.minute);
long secsCurrent = getSecs(current.hour, current.minute);
if (secsFrom > secsTo) secsTo += 24 * 60 * 60;
if (secsCurrent > secsFrom && secsCurrent < secsTo) {
return false;
}
return true;
}
int main() {
//debug messages
std::string okay = " - ok";
std::string error = " - er";
std::string receive = " - allowed";
std::string notReceive = " - denied";
//testing times
int const testDataCount = 5;
timing from[testDataCount] = {
{ 16, 30 },
{ 8, 30 },
{ 10, 30 },
{ 0, 30 },
{ 0, 0 }
};
timing to[testDataCount] = {
{ 8, 30 },
{ 20, 0 },
{ 20, 0 },
{ 6, 0 },
{ 7, 0 }
};
for (int i = 0; i < testDataCount; i++) {
std::cout << i + 1 << ": " << from[i].hour << ":" << from[i].minute << " to " << to[i].hour << ":"
<< to[i].minute << std::endl;
}
//test current times
timing current[5] = {
{ 12, 0 },
{ 23, 0 },
{ 17, 30 },
{ 15, 12 },
{ 0, 20 }
};
bool ergValues[][testDataCount] = {
{ true, false, false, true, true },
{ false, true, true, true, true },
{ false, false, false, true, true },
{ true, false, false, true, true },
{ false, true, true, true, false }
};
long totalNs1 = 0;
long totalNs2 = 0;
for (int i = 0; i < 4; i++) {
std::cout << std::endl << i + 1 << ". Test: " << current[i].hour << ":" << current[i].minute << std::endl;
for (int j = 0; j < testDataCount; j++) {
high_resolution_clock::time_point t1 = high_resolution_clock::now();
bool response = isAllowed(from[j], to[j], current[i]);
high_resolution_clock::time_point t2 = high_resolution_clock::now();
high_resolution_clock::time_point t3 = high_resolution_clock::now();
bool response2 = isAllowed2(from[j], to[j], current[i]);
high_resolution_clock::time_point t4 = high_resolution_clock::now();
long ns1 = duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
totalNs1 += ns1;
long ns2 = duration_cast<std::chrono::nanoseconds>(t4 - t3).count();
totalNs2 += ns2;
std::cout << j + 1 << "\t\t:1:" << ns1 << "ns: " << response << (response == ergValues[i][j] ? okay : error) << "\t\t:2:" << ns2 << "ms: " << response2 << (response2 == ergValues[i][j] ? okay : error) << "\t\t"
<< (ergValues[i][j] ? receive : notReceive) << std::endl;
}
}
std::cout << "\r\ntotalNs1 = " << totalNs1 << "\r\ntotalNs2 = " << totalNs2 << "\r\n\r\n";
return 0;
}
结果显然总是不同的,但无论如何totalNs2总是小于totalNs1。
例如:
totalNs1 = 38796
totalNs2 = 25913
我在 Windows 10 和 Debian 8 上的 AMD Phenom II X4 和 Intel i7-3770 上进行了测试,结果非常相似。
那么最后的问题是,为什么函数isAllowed2比isAllowed快?
注意:这主要是一个好奇的问题,如果有人认为标题或标签不是最合适的,请告诉我,我会相应地更改它们,请原谅任何最终的语法错误因为英语不是我的母语。
编辑
同时,在了解了微基准测试的不准确程度之后,我一直在根据所有建议和 cmets 进行进一步研究,包括 this 非常详细的答案(非常感谢 Baum mit Augen用于链接 this 惊人的谈话,这有很大帮助)我最终使用 Google microbenchmark library 来获得更“准确”的结果,似乎 isAllowed 函数实际上更快(编译时没有优化) 如库的输出所示。
Run on (8 X 2395 MHz CPU s)
-----------------------------------------------------------------------
Benchmark Time CPU Iterations
-----------------------------------------------------------------------
BM_isAllowed/2/min_time:2.000 22 ns 22 ns 128000000
BM_isAllowed/4/min_time:2.000 22 ns 22 ns 137846154
BM_isAllowed/8/min_time:2.000 22 ns 22 ns 128000000
BM_isAllowed/16/min_time:2.000 22 ns 22 ns 128000000
BM_isAllowed/22/min_time:2.000 22 ns 22 ns 137846154
BM_isAllowed2/2/min_time:2.000 24 ns 24 ns 112000000
BM_isAllowed2/4/min_time:2.000 24 ns 24 ns 119466667
BM_isAllowed2/8/min_time:2.000 24 ns 24 ns 119466667
BM_isAllowed2/16/min_time:2.000 24 ns 24 ns 119466667
BM_isAllowed2/22/min_time:2.000 24 ns 24 ns 119466667
注意:正如Martin Bonner 所指出的,isAllowed 函数似乎存在逻辑缺陷,请勿在生产代码中使用它。
你可以在下面找到我用来做这个基准测试的代码,如果我不熟悉Google library,请告诉我是否有任何缺陷。
重要的是,此代码是使用 Visual Studio 2015 编译的,应该对我们要测试的部分禁用优化。
#include "benchmark/benchmark.h"
using namespace std;
using namespace benchmark;
#pragma optimize( "[optimization-list]", {on | off} )
volatile const long extraDay = 24 * 60 * 60;
#pragma optimize( "", off )
struct timing {
short hour;
short minute;
timing(short hour, short minute) : hour(hour), minute(minute) {}
};
static void BM_isAllowed(benchmark::State& state) {
while (state.KeepRunning())
{
timing from(state.range(0), state.range(0));
timing to(state.range(0), state.range(0));
timing current(state.range(0), state.range(0));
bool b = !(((from.hour > to.hour && (current.hour >= from.hour || current.hour <= to.hour)) ||
(current.hour >= from.hour && current.hour <= to.hour)) &&
!(current.minute > from.minute && current.minute < to.minute));
}
}
static void BM_isAllowed2(benchmark::State& state) {
while (state.KeepRunning())
{
timing from(state.range(0), state.range(0));
timing to(state.range(0), state.range(0));
timing current(state.range(0), state.range(0));
bool b;
long secsFrom = secsFrom = (from.hour * 3600) + (from.minute * 60);
long secsTo = (to.hour * 3600) + (to.minute * 60);
long secsCurrent = (current.hour * 3600) + (current.minute * 60);
if (secsFrom > secsTo)
secsTo += extraDay;
if (secsCurrent > secsFrom && secsCurrent < secsTo)
b = false;
else
b = true;
}
}
#pragma optimize( "", on )
BENCHMARK(BM_isAllowed)->RangeMultiplier(2)->Range(2, 22)->MinTime(2);
BENCHMARK(BM_isAllowed2)->RangeMultiplier(2)->Range(2, 22)->MinTime(2);
BENCHMARK_MAIN();
【问题讨论】:
-
看看汇编代码 ;) 你用优化标志编译了吗?
-
这就是为什么您不猜测性能的原因。整数算术很便宜,分支介于字面上免费和昂贵得离谱之间,包括在内。所以至少很难预测这样的事情。
-
您将有兴趣阅读this answer about branch prediction。这可能是 Stack Overflow 上投票率最高的答案之一。这个问题绝对是票数最高的问题。
-
@Mike 1)
std::chrono::nanoseconds确实返回非零结果。就是所有t1/t2/t3/t4中存储的值都是相等的。 2)正如我已经提到的 - 由于调度程序,所有如此小的时间范围都可以作为随机噪声被注销。增加测试用例的大小,直到总运行时间以秒为单位,然后比较结果。 -
我完全不相信第一个函数是正确的!如果
isAllowed(10:01, 12:59, 11:30)返回true,而第二个返回false。 (并且第二个的正确性可以通过检查来确定。)
标签: c++ performance c++11 optimization