我在某处读取了在执行未alignment的加载或存储在页面边界旁边(例如使用_mm_loadu_si128
/ _mm_storeu_si128
内在函数)之前,代码应该首先检查整个向量(本例中为16字节)是否属于同一页面,并切换到非向量指令如果不。 我明白,如果下一页不属于进程,则需要防止coredump。
但是,如果这两个页面属于进程(例如,它们是一个缓冲区的一部分,并且我知道该缓冲区的大小)呢? 我写了一个小的testing程序,执行跨越页面边界的未alignment的加载和存储,并没有崩溃。 在这种情况下,我是否必须经常检查页面边界,还是足以确保我不会溢出缓冲区?
Env:Linux,x86_64,gcc
页面线拆分对性能不利,但不影响未对齐的访问的正确性。 当你知道提前的时间长度时, 确保你没有读过缓冲区的末尾就足够了 。
为了正确,在执行类似strlen
事情时,你经常需要担心它,当你找到一个标记值时,循环停止。 这个值可以在你的向量中的任何位置,所以只要做了16B未对齐的加载就可以读取数组的末尾。 如果终止0
位于一页的最后一个字节中,而下一页不可读,且当前位置指针未对齐,则包含0
字节的负载也将包含来自不可读页面的字节, 。
一种解决方案是标量直到指针对齐,然后加载对齐的向量。 对齐的负载总是来自一个页面,也来自一个缓存行。 所以,即使你会读取字符串末尾的一些字节,你也保证不会出错。 Valgrind可能会不高兴,但标准库strlen
实现使用这个。
直到一个对齐的指针,而不是标量,你可以做一个不对齐的矢量从字符串的开始(只要不会越过页面线),然后做对齐加载。 第一个对齐的负载将重叠第一个未对齐的负载,但对于像strlen这样的函数来说,这完全没有问题,不管它是否看到相同的数据两次。
出于性能原因,可能需要避免页面线拆分。 即使你知道你的src指针没有对齐,让硬件处理cache-line拆分通常也会更快。 但在Skylake之前,页面分割有一个额外的~100c的延迟。 ( 在Skylake下降到5c )。 如果你有多个指针可以相互不同地对齐,你不能总是只使用序言来对齐你的src。 (例如c[i] = a[i] + b[i]
,并且c
是对齐的,但b
不是)。
在这种情况下,可能需要使用分支从页面拆分之前和之后进行对齐加载,并将其与palignr
组合。
一个分支mispredict(〜15c)比分页延迟要便宜,但延迟了一切(不仅仅是负载)。 所以它可能也不值得,这取决于硬件和计算与内存访问的比率。
如果您正在编写通常使用对齐指针调用的函数,则只需使用未对齐的加载/存储指令即可。 检测未对齐的任何序言对于已经对齐的情况来说是额外的开销,而在现代硬件(Nehalem和更新的版本)上,在运行时对齐的地址上未对齐的加载与对齐的加载指令具有相同的性能。 (但你需要AVX的未对齐的负载折叠成其他指令作为内存操作数,例如vpxor xmm0, xmm1, [rsi]
)
通过添加代码来处理未对齐的输入,您正在减慢常见的对齐情况,以加速罕见的不一致情况。 对未对齐的加载/存储的快速硬件支持使得软件可以在硬件发生的情况下将其留给硬件。
(如果输入对齐不一致,那么使用序言对齐输入指针是值得的,尤其是使用AVX的时候,顺序的32B AVX加载将缓存行分割所有其他负载。
有关更多信息,请参阅Agner Fog的优化汇编指南以及x86标记wiki中的其他链接。