第二个变量只是更加隐晦的条件语句吗,因为CPU仍然无法预测将要调用的函数?我猜想这与分支目标预测有关吗?
是的,无条件间接分支需要分支目标缓冲器命中,以便CPU确定从哪里获取代码。现代CPU具有高度流水线化,如果它们要避免管道中没有任何任务可执行的气泡,就需要提前获取代码。等到计算出“magic”时已经太晚了,无法避免指令获取气泡。我认为性能计数器将BTB未命中显示为分支预测错误。
正如我在评论中建议的那样,如果可以的话,您应该重构代码,在向量化循环周围进行标量介绍和清理。介绍处理元素,直到达到对齐的元素。清理循环处理最后一个完整向量之后还剩余一定数量元素的情况。然后,您不必因为第一个元素的大小或对齐方式不理想而陷入做标量循环的困境。
根据您正在处理的内容,如果重复工作和重叠是可以接受的,则可以创建一个无分支启动程序,执行不对齐的块,然后执行其余对齐的部分。一些库可能实现了类似于memset
的功能。
// not shown: check that count >= 16
endp = dest + count
unaligned_store_16B( dest )
dest+=16
dest &= ~0xf
for (
aligned_store_16B( dest )
}
// handle the last up-to-15 bytes from dest to endp similarly.
这使得处理循环分支的非对齐起点变得无需分支,因为您不需要关心非对齐起点重叠了多少。
请注意,大多数单缓冲函数都不能重复执行。例如,就地执行
a[i] *= 2
或
sum+=a[i]
需要避免重复处理相同的输入。通常使用标量循环直到达到对齐地址。但是,
a[i] &= 0x7f
或
maxval = max(a[i], maxval)
是例外。
具有两个独立指针的函数,可以由不同数量的
偏移量使其错位。您必须小心,不要通过掩码改变它们的相对偏移量。
memcpy
是一个从源缓冲区到目标缓冲区处理数据的最简单示例。如果
(src+3)%16==0
和
(dest+7)%16==0
,则
memcpy
必须工作。除非您可以对调用者施加约束,否则通常情况下,您所能做的最好的事情就是在主循环中对每个加载或每个存储进行对齐。
在x86上,不需要对齐的移动指令(
movdqu
等)与要求对齐的版本一样快,当地址对齐时。因此,当src和dest具有相同的(错位)对齐方式时,不需要为特殊情况创建单独的循环,并且负载和存储都可以对齐。据我所知,这适用于英特尔Nehalem和更新的CPU以及较新的AMD。
// check count >= 16
endp = dest + count
unaligned_copy_16B( dest, src )
// src+=16
dest_misalign = dest & 0xf
src += 16 - dest_misalign
dest += 16 - dest_misalign
for (
tmpvec = unaligned_load_16B( src )
aligned_store_16B( dest, tmpvec )
}
// handle the last dest to endp bytes.
一个对齐的目标指针比对齐的源指针更可能。当我们要对齐的指针已经对齐时,不会发生重复工作的重叠。
如果你没有进行memcpy,那么让src对齐可能是有优势的,因为加载可以折叠到另一个指令中作为内存操作数。这节省了一条指令,在许多情况下也节省了Intel uop的内部开销。
对于src和dest具有不同对齐方式的情况,我还没有测试过做对齐的加载和未对齐的存储哪种更快。我选择了对齐存储,因为短缓冲区可能会有存储->加载转发的好处。如果目标缓冲区是对齐的,并且只有几个向量长,并且将立即再次读取,则从dest进行对齐加载将停顿约10个周期(Intel SnB),如果加载跨越两个前置存储之间的边界,这些存储尚未进入L1高速缓存。(即存储转发失败)。请参见
http://agner.org/optimize/以获取此类低级细节的信息(特别是微架构指南)。
如果缓冲区很小(可能最多达到64B),或者下一个循环从缓冲区的末尾开始读取(即使开头已经被驱逐出缓存),那么从memcpy到下一个循环中的负载的存储转发只会发生。否则,对于缓冲区开头的存储将从存储缓冲区传输到L1,因此存储转发不会起作用。
对于具有不同对齐方式的大缓冲区,对齐的负载和不对齐的存储可能更好。我只是在这里编造东西,但是如果不对齐的存储可以快速退役,即使它们跨越缓存线或页面线,也可能是真实的。当然,直到实际加载数据,不对齐的负载才能退役。随着更多的load / store指令在飞行中,缓存未命中导致停顿的机会就会减少。(您潜在地利用了CPU的更多load / store缓冲区)。再次纯属猜测。我试图搜索不对齐的存储是否比不对齐的负载更好或更差,但只获得了有关如何执行它们以及适用于两者的不对齐惩罚的命中。