复制范围中的优化
Optimizations in copying a range
在阅读GNU C++标准库的源代码时,我发现一些代码用于复制(或移动,如果可能的话)一系列迭代器(文件stl_algobase.h
),它使用模板专用化进行一些优化。与之相对应的评论写道:
所有这些辅助结构都有两个目的。(1) 尽可能用memmove替换对复制的调用。(Memmove,而不是memcpy,因为允许输入和输出范围重叠。)(2)如果我们使用随机访问迭代器,那么将循环写为带有显式计数的for循环。
使用第二个优化的专业化如下所示:
template<>
struct __copy_move<false, false, random_access_iterator_tag>
{
template<typename _II, typename _OI>
static _OI
__copy_m(_II __first, _II __last, _OI __result)
{
typedef typename iterator_traits<_II>::difference_type _Distance;
for(_Distance __n = __last - __first; __n > 0; --__n)
{
*__result = *__first;
++__first;
++__result;
}
return __result;
}
};
因此,我有两个关于的问题
memmove
如何提高复制的速度?它的实现是否比简单的循环更有效- 在
for
循环中使用显式计数器会如何影响性能
一些澄清:我希望看到编译器实际使用的一些优化示例,而不是详细说明这些示例的可能性。
编辑:第一个问题在这里得到了很好的回答。
在回答第二个问题时,显式计数确实会为循环展开带来更多机会,尽管即使指针在固定大小的数组中迭代,gcc也不会执行主动展开,除非-funroll-loops
要求这样做。另一个好处来自于针对非平凡迭代器的可能更简单的循环结束比较测试。
在Core i7-4770上,我通过while循环和显式计数复制实现,对执行最大对齐2048长整数阵列的复制所花费的时间进行了基准测试。(以微秒为单位的时间,包括呼叫开销;带预热的定时环路至少有200个样本。)
while count
gcc -O3 0.179 0.178
gcc -O3 -march=native 0.097 0.095
gcc -O3 -march=native -funroll-loops 0.066 0.066
在每种情况下,生成的代码都非常相似;在每种情况下,while
版本在最后都会做更多的工作,处理检查是否没有任何未填满整个128位(SSE)或256位(AVX)寄存器的条目可以复制,但这些都由分支预测器负责。每个的gcc -O3
程序集如下(省略汇编指令)。while
版本:
array_copy_while(int (&) [2048], int (&) [2048]):
leaq 8192(%rdi), %rax
leaq 4(%rdi), %rdx
movq %rax, %rcx
subq %rdx, %rcx
movq %rcx, %rdx
shrq $2, %rdx
leaq 1(%rdx), %r8
cmpq $8, %r8
jbe .L11
leaq 16(%rsi), %rdx
cmpq %rdx, %rdi
leaq 16(%rdi), %rdx
setae %cl
cmpq %rdx, %rsi
setae %dl
orb %dl, %cl
je .L11
movq %r8, %r9
xorl %edx, %edx
xorl %ecx, %ecx
shrq $2, %r9
leaq 0(,%r9,4), %r10
.L9:
movdqa (%rdi,%rdx), %xmm0
addq $1, %rcx
movdqa %xmm0, (%rsi,%rdx)
addq $16, %rdx
cmpq %rcx, %r9
ja .L9
leaq 0(,%r10,4), %rdx
addq %rdx, %rdi
addq %rdx, %rsi
cmpq %r10, %r8
je .L1
movl (%rdi), %edx
movl %edx, (%rsi)
leaq 4(%rdi), %rdx
cmpq %rdx, %rax
je .L1
movl 4(%rdi), %edx
movl %edx, 4(%rsi)
leaq 8(%rdi), %rdx
cmpq %rdx, %rax
je .L20
movl 8(%rdi), %eax
movl %eax, 8(%rsi)
ret
.L11:
movl (%rdi), %edx
addq $4, %rdi
addq $4, %rsi
movl %edx, -4(%rsi)
cmpq %rdi, %rax
jne .L11
.L1:
rep ret
.L20:
rep ret
count
版本:
array_copy_count(int (&) [2048], int (&) [2048]):
leaq 16(%rsi), %rax
movl $2048, %ecx
cmpq %rax, %rdi
leaq 16(%rdi), %rax
setae %dl
cmpq %rax, %rsi
setae %al
orb %al, %dl
je .L23
movw $512, %cx
xorl %eax, %eax
xorl %edx, %edx
.L29:
movdqa (%rdi,%rax), %xmm0
addq $1, %rdx
movdqa %xmm0, (%rsi,%rax)
addq $16, %rax
cmpq %rdx, %rcx
ja .L29
rep ret
.L23:
xorl %eax, %eax
.L31:
movl (%rdi,%rax,4), %edx
movl %edx, (%rsi,%rax,4)
addq $1, %rax
cmpq %rax, %rcx
jne .L31
rep ret
然而,当迭代器更加复杂时,差异会变得更加明显。考虑一个假设的容器,它将值存储在一系列固定大小的已分配缓冲区中。迭代器包括指向块链的指针、块索引和块偏移。比较两个迭代器可能需要进行两次比较。增加迭代器需要检查我们是否弹出块边界。
我制作了这样一个容器,并执行了相同的基准测试来复制一个2000长的int
容器,其块大小为512 int
s。
while count
gcc -O3 1.560 2.818
gcc -O3 -march=native 1.660 2.854
gcc -O3 -march=native -funroll-loops 1.432 2.858
这看起来很奇怪!哦,等等,这是因为gcc 4.8有一个错误的优化,它使用了条件移动,而不是友好的分支预测器比较。(gcc错误56309)。
让我们在另一台机器(Xeon E5-2670)上尝试icc。
while count
icc -O3 3.952 3.704
icc -O3 -xHost 3.898 3.624
这更接近于我们的预期,与更简单的循环条件相比,有一个微小但显著的改进。在不同的体系结构上,增益更加明显。clang瞄准1.6GHz的PowerA2:
while count
bgclang -O3 36.528 31.623
我将省略这个集会,因为它很长!
- 为什么在全局范围内使用"extern int a"似乎不行?
- 空基优化子对象的地址
- 尝试通过多个向量访问变量时,向量下标超出范围
- 错误:未在此范围内声明'reverse'
- 正在将指针转换为范围
- 使用std::transform将一个范围的元素添加到另一个范围中
- 在基于范围的for循环中使用结构化绑定声明
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 如何计算数据类型的范围,例如int
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- GCC 优化了基于固定范围的 for 循环,就好像它具有更长的可变长度一样
- GCC LTO:限制优化范围
- 优化的范围检查并返回值
- 为什么编译器不优化集合元素上的空范围循环?
- 如何优化与范围的匹配(家庭作业)
- 从优化端基于范围的循环 C++11
- 优化代码以获取给定范围内可被整数整除的整数数
- 复制范围中的优化
- 关于优化的数学函数、范围和区间