2016-06-09 63 views
6

我在执行未对齐的加载或存储在页面边界旁边(例如使用_mm_loadu_si128/_mm_storeu_si128内部函数)之前,应先检查整个向量(本例中为16字节)属于同一页面,如果不是,则切换到非矢量指令。我明白,如果下一页不属于进程,则需要这样做来防止coredump。SSE:未对齐的加载和跨越页面边界的存储

但是,如果两页都属于进程(例如它们是一个缓冲区的一部分,并且我知道该缓冲区的大小)呢?我写了一个小的测试程序,它执行跨越页面边界的未对齐的加载和存储,并没有崩溃。在这种情况下,我是否必须始终检查页面边界,还是足以确保我不会溢出缓冲区?

ENV:Linux中,x86_64的,GCC

回答

7

页行分割都是不好的表现,但不影响未对齐访问的正确性。 当您知道提前的时间长度时,确保您不会读取缓冲区末尾的内容


为了确保正确无误,你经常需要像strlen,在那里,当你发现一个标记值的循环停止执行的东西时,担心它。该值可能位于矢量内的任何位置,因此只要执行16B未对齐的加载就可以读取数组的末尾。如果终止0位于一页的最后一个字节中,并且下一页不可读,并且当前位置指针未对齐,则包含0字节的负载也将包含来自不可读页面的字节,因此它将会出错。

一个解决方案是做标量,直到你的指针对齐,然后加载对齐的向量。对齐的负载总是来自一个页面,也来自一个缓存行。所以,即使你会读取字符串末尾的一些字节,你也保证不会出错。尽管如此,Valgrind可能会不高兴,但标准库strlen实现使用这个。

而不是标量,直到对准指针,你可以做从字符串的开始不对齐的矢量(只要不会跨页行),然后做平衡负载。第一个对齐的负载将重叠第一个未对齐的负载,但对于像strlen这样的函数来说,这并不在意它是否看到相同的数据两次。


出于性能原因,可能值得避免页面线拆分。即使您知道您的src指针未对齐,让硬件处理缓存线分割通常也会更快。但在Skylake之前,页面分割会有额外的〜100c延迟。 (Down to 5c in Skylake)。如果您有不同可以相互对齐多个指针,你不能总是只用一个序幕对准你的src。 (例如c[i] = a[i] + b[i]c对准但b不是。)

在这种情况下,它可能会使用一个分支做对齐的加载与以前和页面拆分后的价值,并与palignr将它们结合起来。

一个分支错误预测(〜15℃)比页分裂延迟便宜,但会延迟一切(而不仅仅是负荷)。所以它可能也是值得的,这取决于硬件和计算的比例内存访问。


如果你正在编写一个函数,通常称为有排列的指针,是有意义的只是使用未对齐的加载/存储指令。任何检测错位的序言对于已经对齐的情况来说都是额外的开销,而在现代硬件(Nehalem和更新版本)上,在运行时对齐的地址上未对齐的加载与对齐的加载指令具有相同的性能。 (但你需要AVX作为未对齐的加载,以折叠为其他指令作为内存操作数,例如vpxor xmm0, xmm1, [rsi]

通过添加代码来处理未对齐的输入,可以减慢常见的对齐情况以加速罕见的未对齐情况。对未对齐的加载/存储的快速硬件支持使得软件可以在发生这种情况的少数情况下将其留给硬件。如果你使用的是AVX,顺序32B AVX加载将缓存线分割所有其他负载。如果你使用的是AVX,那么序列32B AVX加载将缓存行分割。 )

有关更多信息,请参阅Agner Fog's Optimizing Assembly guide以及标记wiki中的其他链接。

+0

@ZheyuanLi:是的,我很好奇哪些设计更改启用了。 Skylake还可以并行执行两个页面散步来解决两个TLB未命中。这两个事实可能是相关的。 –

+0

谢谢!我也没有意识到跨页访问可能会有这么高的成本。所以这绝对是要寻找的东西。 –

+1

BTW,Valgrind有选项--partial-loads-ok = yes当加载的数据超过缓冲区末尾时,它可以隐藏由矢量加载引起的“无效读”问题。 –