Dan*_*ski 8 c linux sse x86-64 memory-alignment
我在某处读到了在页面边界旁边执行未对齐的加载或存储之前(例如使用_mm_loadu_si128/ _mm_storeu_si128intrinsics),代码应首先检查整个向量(在这种情况下是16个字节)是否属于同一页面,如果不是,则切换到非向量指令.我知道如果下一页不属于进程,则需要这样做以防止coredump.
但是,如果两个页面都属于进程(例如,它们是一个缓冲区的一部分,并且我知道该缓冲区的大小),该怎么办?我写了一个小的测试程序,它执行了未对齐的加载和跨越页面边界的存储,并没有崩溃.在这种情况下,我是否必须始终检查页面边界,还是足以确保我不会溢出缓冲区?
环境:Linux,x86_64,gcc
Pet*_*des 10
页面行拆分对性能不利,但不影响未对齐访问的正确性. 当您提前知道长度时,足以确保您不会读取缓冲区的末尾.
为了正确起见,在实现类似的东西时,你经常需要担心它strlen,当你找到一个sentinel值时你的循环停止.该值可以位于向量中的任何位置,因此仅执行16B未对齐的加载将读取超出数组的末尾.如果终止0位于一个页面的最后一个字节中,并且下一页不可读,并且您的当前位置指针未对齐,则包含该0字节的加载也将包含来自不可读页面的字节,因此它将发生故障.
一种解决方案是执行标量直到指针对齐,然后加载对齐的向量.对齐的加载始终完全来自一个页面,也来自一个缓存行.因此,即使您将读取字符串末尾之后的一些字节,也可以保证不会出错. 但是Valgrind可能对此不满意,但标准库strlen实现使用它.
你可以从字符串的开头做一个未对齐的向量(只要它不会越过页面行),然后做对齐的加载,而不是标量直到对齐的指针.第一个对齐的加载将与第一个未对齐的加载重叠,但对于像strlen这样的函数来说,这完全没问题,如果它看到相同的数据两次则无关紧要.
出于性能原因,可能值得避免页面分割.即使你知道你的src指针未对齐,让硬件处理缓存行分割通常会更快.但在Skylake之前,页面拆分有额外的~100c延迟.(Skylake降至5c).如果你有多个指针可以彼此不同地对齐,你不能总是只使用序言来对齐你的src.(例如c[i] = a[i] + b[i],并且c是对齐但b不是.)
在这种情况下,可能值得使用分支来执行页面拆分之前和之后的对齐加载,并将它们组合在一起palignr.
分支错误预测(~15c)比页面分割延迟便宜,但会延迟所有内容(不仅仅是负载).所以它可能也不值得,这取决于硬件和计算与内存访问的比率.
如果你正在编写一个通常使用对齐指针调用的函数,那么只使用未对齐的加载/存储指令是有意义的.任何检测错位的序言只是已经对齐的情况下的额外开销,而在现代硬件(Nehalem和更新)上,在运行时对齐的地址上的未对齐加载与对齐的加载指令具有相同的性能.(但是你需要AVX将未对齐的加载作为内存操作数折叠到其他指令中.例如vpxor xmm0, xmm1, [rsi])
通过添加代码来处理未对齐的输入,您正在减慢常见的对齐情况,以加速不常见的错位情况.对未对齐的加载/存储的快速硬件支持允许软件将其留给硬件,以用于发生它的少数情况.
(如果未对齐的输入是常见的,那么使用序言来对齐输入指针是值得的,例如,如果您正在使用AVX.顺序32B AVX加载将缓存线分割每隔一个负载.)
有关详细信息,请参阅Agner Fog的优化装配指南,以及x86标签wiki 中的其他链接.
| 归档时间: |
|
| 查看次数: |
688 次 |
| 最近记录: |