在只有 3 个颜色分量的浮点像素上使用 SSE

Using SSE on floating point pixels with only 3 color components

我正在创建一个结构来存储图像中的单个 RGB 像素。

struct Pixel
{
    // color values range from 0.0 to 1.0
    float r, g, b;
}__attribute__((aligned(16));

我想使用 128 位 SSE 指令来执行加法、乘法等操作。这样我就可以同时对所有 3 个颜色通道执行操作。因此,我的 SSE 寄存器中的第一个打包浮点数将是红色,然后是绿色,然后是蓝色,但我不太确定第四个寄存器中的内容。我真的不在乎额外的 32 位填充中有哪些位。当我将一个像素加载到 SSE 寄存器时,我会想象它包含零值或垃圾值。这有问题吗?我是否应该添加第四个 alpha 通道,即使我真的不需要一个?我认为这是一个问题的唯一方法是,如果我除以一个像素并且在第四个位置有一个零值,或者我正在取一个负数的根,等等。

整数运算对于未初始化的值完全没有问题,因为延迟从不依赖于数据。浮点数不同。某些 FPU 在处理非正规数、NaN 和无穷大(在任何一个向量元素中)时会变慢。

Intel Nehalem 和更早版本在使用非正态 inputs/outputs 和 FP underflow/overflow 进行数学运算时速度变慢很多。 Sandybridge 有一个很好的 FPU,对于任何输入都具有快速 add/sub(根据 Agner Fog's instruction tables), but multiply can still slow down.

Add/sub/multiply 用零没问题,但可能代表 NaN 或其他东西的未初始化垃圾可能会出现问题。

小心除法,不要除以零。这甚至可能引发 FPU 异常,具体取决于硬件设置。

所以是的,将未使用的元素置零可能是个好主意。取决于您最初生成事物的方式,这可能很容易实现。 (例如 movd/pinsrd/pinsrd(或 insertps)将三个 32 位元素放入一个向量中,初始 movd 将高位 96b 归零。)

一种解决方法是在第四个元素中存储蓝色通道的第二个副本。 (或任何在那里洗牌最方便的东西。)您可以使用 movsldup(SSE3) / movlps 加载向量。在 movsldup 之后,您的寄存器将保持 { b b r r }movlps 将重新加载较低的 64 位,因此您将拥有 { b b g r }。 (这相当于 movsd,顺便说一句。)或者如果 shuffle 端口不如加载端口繁忙,则执行一个 16B 加载,然后进行 shufps。 (movsldup 在 Intel CPU 上是在加载端口上运行的单个 uop,即使它内置了复制。)

另一种选择是将您的像素打包成 12 个字节,这样 16B 的负载将获得下一个像素的一个组件。根据您正在做的事情,重叠存储会破坏下一个像素的一个元素可能会也可能不会。在存储当前像素之前加载下一个像素可以解决某些操作的问题。很容易受到缓存或带宽限制,因此以偶尔的缓存行拆分 load/store 的小成本节省 1/4 space 可能是值得的。