我需要在向量中找到max元素,所以我使用std::max_element
,但我发现它是一个非常慢的函数,因此我编写了自己的版本并设法获得x3更好的性能,这是代码:
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <sys/time.h>
double getRealTime()
{
struct timeval tv;
gettimeofday(&tv, 0);
return (double) tv.tv_sec + 1.0e-6 * (double) tv.tv_usec;
}
inline int my_max_element(const std::vector<int> &vec, int size)
{
auto it = vec.begin();
int max = *it++;
for (; it != vec.end(); it++)
{
if (*it > max)
{
max = *it;
}
}
return max;
}
int main()
{
const int size = 1 << 20;
std::vector<int> vec;
for (int i = 0; i < size; i++)
{
if (i == 59)
{
vec.push_back(1000000012);
}
else
{
vec.push_back(i);
}
}
double startTime = getRealTime();
int maxIter = *std::max_element(vec.begin(), vec.end());
double stopTime = getRealTime();
double totalIteratorTime = stopTime - startTime;
startTime = getRealTime();
int maxArray = my_max_element(vec, size);
stopTime = getRealTime();
double totalArrayTime = stopTime - startTime;
std::cout << "MaxIter = " << maxIter << std::endl;
std::cout << "MaxArray = " << maxArray << std::endl;
std::cout << "Total CPU time iterator = " << totalIteratorTime << std::endl;
std::cout << "Total CPU time array = " << totalArrayTime << std::endl;
std::cout << "iter/array ratio: = " << totalIteratorTime / totalArrayTime << std::endl;
return 0;
}
输出:
MaxIter = 1000000012
MaxArray = 1000000012
Total CPU time iterator = 0.000989199
Total CPU time array = 0.000293016
iter/array ratio: = 3.37592
平均std::max_element
比my_max_element
多花费x3倍。
那么为什么我能够如此轻松地创建更快的std函数呢?我应该停止使用std并编写自己的函数,因为std太慢了吗?
注意:起初我虽然是因为我在for循环中使用了整数i
而不是迭代器,但现在接缝并不重要。
汇编信息:
g ++(GCC)4.8.2
g ++ -O3 -Wall -c -fmessage-length = 0 -std = c ++ 0x
答案 0 :(得分:28)
在对此答案进行投票之前,请在您的机器上测试(并验证)此操作并发表评论/添加结果。请注意,我的测试使用了1000 * 1000 * 1000的矢量大小。目前,这个答案有19个upvotes但只有一个发布的结果,这些结果没有显示下面描述的效果(虽然使用不同的测试代码获得,请参阅注释)。
似乎有一个优化器错误/工件。比较时间:
template<typename _ForwardIterator, typename _Compare>
_ForwardIterator
my_max_element_orig(_ForwardIterator __first, _ForwardIterator __last,
_Compare __comp)
{
if (__first == __last) return __first;
_ForwardIterator __result = __first;
while(++__first != __last)
if (__comp(__result, __first))
__result = __first;
return __result;
}
template<typename _ForwardIterator, typename _Compare>
_ForwardIterator
my_max_element_changed(_ForwardIterator __first, _ForwardIterator __last,
_Compare __comp)
{
if (__first == __last) return __first;
_ForwardIterator __result = __first;
++__first;
for(; __first != __last; ++__first)
if (__comp(__result, __first))
__result = __first;
return __result;
}
第一个是original libstdc++ implementation,第二个应该是转换,行为或要求没有任何变化。 Clang ++为这两个函数生成非常相似的运行时间,而g ++ 4.8.2对第二个版本的运行速度快四倍。
根据Maxim的建议,将矢量从int
更改为int64_t
,更改后的版本不是4,但只比原始版本(g ++ 4.8.2)快1.7倍。
区别在于*result
的预测共通,即存储当前max元素的值,以便每次都不必从内存重新加载。这提供了更清晰的缓存访问模式:
w/o commoning with commoning
* *
** *
** *
** *
* * *
* * *
* * *
这是用于比较的asm(rdi
/ rsi
分别包含第一个/最后一个迭代器):
使用while循环(2.88743 ms; gist):
movq %rdi, %rax
jmp .L49
.L51:
movl (%rdi), %edx
cmpl %edx, (%rax)
cmovl %rdi, %rax
.L49:
addq $4, %rdi
cmpq %rsi, %rdi
jne .L51
使用for循环(1235.55μs):
leaq 4(%rdi), %rdx
movq %rdi, %rax
cmpq %rsi, %rdx
je .L53
movl (%rdi), %ecx
.L54:
movl (%rdx), %r8d
cmpl %r8d, %ecx
cmovl %rdx, %rax
cmovl %r8d, %ecx
addq $4, %rdx
cmpq %rdx, %rsi
jne .L54
.L53:
如果我通过在开始时*result
显式存储prev
并且更新result
并使用prev
代替*result
来强制执行公共操作在比较中,我获得了更快的循环(377.601μs):
movl (%rdi), %ecx
movq %rdi, %rax
.L57:
addq $4, %rdi
cmpq %rsi, %rdi
je .L60
.L59:
movl (%rdi), %edx
cmpl %edx, %ecx
jge .L57
movq %rdi, %rax
addq $4, %rdi
movl %edx, %ecx
cmpq %rsi, %rdi
jne .L59
.L60:
这比for
循环更快的原因是上面的条件移动(cmovl
)是悲观的,因为它们很少被执行(Linus says只有cmov是如果分支是不可预测的,那就好了。请注意,对于随机分布的数据,分支预计需要Hn次,这是一个可以忽略不计的比例(H n 以对数方式增长,因此H n / n迅速接近0)。条件移动代码仅对病理数据更好,例如[1,0,3,2,5,4,......]。
答案 1 :(得分:10)
您可能正在以64位模式运行测试,其中sizeof(int) == 4
只有sizeof(std::vector<>::iterator) == 8
,因此循环中的赋值为int
(my_max_element
做什么)比std::vector<>::iterator
更快(这是std::max_element
所做的)。
如果您将std::vector<int>
更改为std::vector<long>
,则将结果更改为std::max_element
:
MaxIter = 1000000012
MaxArray = 1000000012
Total CPU time iterator = 0.00429082
Total CPU time array = 0.00572205
iter/array ratio: = 0.749875
一个重要的注意事项:基准测试时禁用CPU频率缩放,以便CPU在基准测试中间不切换。
但是我认为其他的东西在这里发挥作用,因为只需将循环变量从int
更改为long
不会改变结果......
答案 2 :(得分:2)