不对齐的内存访问

3
我正在处理一个嵌入式设备,该设备不支持非对齐内存访问。
对于视频解码器,我需要以8x8像素块为单位处理像素(每个像素一个字节)。该设备具有一些SIMD处理能力,允许我并行处理4个字节。
问题在于,8x8像素块不能保证从对齐地址开始,并且函数需要读/写多达三个这样的8x8块。
如果您想要非常好的性能,您会如何处理?经过一番思考,我想到了以下三个想法:
1. 所有内存访问都作为字节进行。这是最简单的方法,但速度慢,并且与SIMD功能不兼容(这是我当前在参考C代码中所做的)。
2. 编写四个复制函数(每个对齐情况一个),通过两个32位读取加载像素数据,将位移至正确位置,然后将数据写入某个对齐的临时存储器块。然后,视频处理函数可以使用32位访问和SIMD。缺点:CPU将无法在处理之后隐藏内存延迟。
3. 与上面的想法相同,但是不要将像素写入临时存储器,而是原地进行视频处理。这可能是最快的方法,但我必须为此方法编写的函数数量很高(我猜大约有60个左右)。
顺便说一下:我将不得不使用汇编语言编写所有函数,因为当涉及到SIMD扩展时,编译器会生成可怕的代码。
您会选择哪条路线,还是您有其他想法来处理这个问题?
5个回答

4
您可以使用memcpy(如果我没记错的话,可以优化为尽可能执行字复制)将数据复制到对齐的数据结构中(例如,在堆栈上分配的东西或从 malloc 中分配的东西)。然后在该对齐的数据结构上执行处理。
不过,最有可能的情况是您想要在处理器的寄存器中处理事物,而不是在内存中处理。如何处理您的任务取决于硬件的功能(例如,一个32位寄存器是否可以分成四个8位的寄存器?SIMD操作操作哪些寄存器?)如果您选择简单路线,可以调用一个小型加载器函数来为您执行非对齐读取。

我看到的几个memcopy源代码大多是汇编语言,并考虑了对齐方式。这可能是一个合理的选择。 - EvilTeach
呃?每次复制8个字节就调用memcpy 8次?听起来有点过度了。不过我会看一下代码的。也许GCC足够聪明,可以将其折叠成一些很酷的东西。 - Nils Pipenbrinck
@Nils,你的8x8块不是连续的吗?这是我假设的。你需要将64字节复制到一个64字节的临时缓冲区中,然后对其进行操作。 - strager
陌生人,不,这些块每个有8行8个像素,行之间有一个偏移量(始终对齐为4,因此行之间的对齐不会改变)。 - Nils Pipenbrinck
@Nils,然后只需使用memcpy复制一个块,并在从数据结构中读取时跳过未使用的部分。 - strager
我一直在使用这种方法来避免在IA64上的未对齐访问,但最近的GCC 4.5版本似乎完全优化了我的memcpy,将其恢复为未对齐访问。请注意。 - Zan Lynx

3

首先对齐数据,然后采用对齐的SIMD方法。

这比选项3要少工作量,并且有可能您的代码会在顶级速度25%的时间内(即已经对齐的情况下)运行。在将来需要在您知道输入将被正确对齐的情况下,可以愉快地重复使用代码。

只有在这种方法不能满足您的要求时,才应该考虑将所有四种对齐可能性硬编码到函数中。


3
你应该首先将代码分为提取和处理两个部分。
提取代码应该复制到一个工作缓冲区,并具有用于内存对齐的情况(在此情况下,您应该能够使用SIMD寄存器进行复制)和非对齐内存的情况,其中您需要逐字节复制(如果您的平台无法执行未对齐访问,并且源/目标具有不同的对齐方式,则这是您可以做的最好的事情)。
然后,您的处理代码可以保证在对齐的数据上进行SIMD操作。对于任何真正的处理程度,复制+处理肯定比非SIMD操作快得多。
假设您的源和目标相同,进一步的优化是仅在源未对齐时使用工作缓冲区,并在内存对齐时就地处理。这样做的好处将取决于您的数据特征。
根据您的体系结构,您可能会在处理之前预取数据而获得进一步的好处。这是您可以发出指令以将内存区域提取到缓存中,以便在需要之前获取下一个块的位置,因此您将在当前处理之前发出提取命令。

2

我建议你选择选项1),直到你确定它太慢了(慢是可以的,但太慢就不好了)。


2

普遍建议:为什么不选择听起来合理的东西(比如#2),然后再测量性能?如果不可接受,您可以回到原点。

在测量之前手工编写60多个汇编函数肯定算是“过早优化”了。 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接