页行分割对性能不利,但不会影响未对齐访问的正确性。确保您不会读取超出缓冲区末尾的内容就足够了,当你提前知道长度时。
为了正确性,在实现类似的东西时你经常需要担心它strlen
,当您找到哨兵值时,循环将停止。该值可以位于向量内的任何位置,因此只需执行 16B 未对齐加载就会读取超出数组末尾的内容。如果终止0
位于一页的最后一个字节,并且下一页不可读,并且当前位置指针未对齐,加载包括0
byte 还将包含来自不可读页面的字节,因此会出错。
一种解决方案是执行标量直到指针对齐,然后加载对齐的向量。对齐的加载始终完全来自一页,也来自一个缓存行。因此,即使您将读取超出字符串末尾的一些字节,也保证不会出错。Valgrind http://valgrind.org/不过,标准库可能对此不满意strlen
实现使用这个。
您可以从字符串的开头执行未对齐的向量(只要不会跨越页线),然后执行对齐的加载,而不是在对齐指针之前使用标量。第一个对齐的加载将与第一个未对齐的加载重叠,但这对于像 strlen 这样的函数来说完全没问题,因为它不关心是否两次看到相同的数据。
出于性能原因,避免页行分割可能是值得的。即使您知道 src 指针未对齐,让硬件处理缓存行拆分通常会更快。但在 Skylake 之前,页面分割有大约 100c 的额外延迟。 (Skylake 低至 5c https://stackoverflow.com/questions/37361145/deoptimizing-a-program-for-the-pipeline-in-intel-sandybridge-family-cpus/37362225#37362225)。如果您有多个可以相对彼此以不同方式对齐的指针,则不能总是仅使用序言来对齐 src。 (例如。c[i] = a[i] + b[i]
, and c
已对齐,但是b
isn't.)
在这种情况下,可能值得使用分支在页面拆分之前和之后进行对齐加载,并将它们与palignr
.
分支错误预测(~15c)比页面分割延迟要便宜,但会延迟所有内容(不仅仅是加载)。所以也可能not是否值得,具体取决于硬件以及计算与内存访问的比率。
如果你正在编写一个函数usually使用对齐指针调用,仅使用未对齐的加载/存储指令是有意义的。任何检测未对齐的序言对于已经对齐的情况来说都只是额外的开销,并且在现代硬件(Nehalem 和更新的)上,地址上未对齐的加载在运行时被对齐,其性能与对齐的加载指令相同。 (但是您需要 AVX 将未对齐的加载折叠到其他指令中作为内存操作数。例如vpxor xmm0, xmm1, [rsi]
)
通过添加代码来处理未对齐的输入,您可以减慢常见对齐情况的速度,从而加快不常见未对齐情况的速度。对未对齐加载/存储的快速硬件支持使软件可以在少数情况下将其留给硬件。
(如果输入未对齐很常见,那么它is值得使用序言来对齐输入指针,尤其是。如果您使用 AVX。顺序 32B AVX 加载将缓存行分割所有其他加载。)
See Agner Fog 的优化装配指南 http://agner.org/optimize/欲了解更多信息,以及其他链接x86 /questions/tagged/x86标签维基。