【发布时间】:2021-11-04 15:05:15
【问题描述】:
这是我从 SDK 获得的基础设施声明:
struct alignas(32) Input {
union {
float values[16] = {};
float value;
};
// other members variables
}
std::vector<Input> myInputs;
const int numInputsA = 4;
const int numInputsB = 4;
const int numInputsC = 4;
const int numInputsD = 4;
const int numInputsE = 4;
myInputs.resize(numInputsA + numInputsB + numInputsC + numInputsD + numInputsE);
使用 simd 更快地加载记录的最佳方法是什么,例如:
__m128 targetA0 = { myInputs[0].values[0], myInputs[1].values[0], myInputs[2].values[0], myInputs[3].values[0] }
__m128 targetB0 = { myInputs[4 + 0].values[0], myInputs[4 + 1].values[0], myInputs[4 + 2].values[0], myInputs[4 + 3].values[0] }
__m128 targetC0 = { myInputs[8 + 0].values[0], myInputs[8 + 1].values[0], myInputs[8 + 2].values[0], myInputs[8 + 3].values[0] }
...
__m128 targetA1 = { myInputs[0].values[1], myInputs[1].values[1], myInputs[2].values[1], myInputs[3].values[1] }
__m128 targetB1 = { myInputs[4 + 0].values[1], myInputs[4 + 1].values[1], myInputs[4 + 2].values[1], myInputs[4 + 3].values[1] }
__m128 targetC1 = { myInputs[8 + 0].values[1], myInputs[8 + 1].values[1], myInputs[8 + 2].values[1], myInputs[8 + 3].values[1] }
...
... and so on
如您所见,我继承的结构并不是真正面向以这种方式捕获数据,但无法更改它。
所以这个问题,感谢您的经验:是否可以加载数据以在每个起始索引上注册“偏移量”?还是缓存线需要加载整个块,涉及大量缓存未命中?
也许有一些技巧可以加快整个过程。
至于我之前的帖子,仍然在 windows/64 位机器上,使用 FLAGS += -O3 -march=nocona -funsafe-math-optimizations(由我正在发展的生态系统强加)。
感谢您提供给我的任何帮助/提示/建议。
【问题讨论】:
-
我想我在上一个问题中提到了这一点,但您可能想要
-march=nocona -mtune=generic,除非您实际上更关心 P4 上的性能而不是典型的现代 CPU。它仍会在那些旧 P4 上运行,但调整选项(例如何时内联以及使用哪些指令)将基于主流 AMD 和 Intel CPU 的优势。 -
x86 没有跨步加载,但如果您可以使用 4x8 或 8x8 转置,则进行矢量加载和随机转置可能是值得的,尽管只有 16 个 XMM regs 持有每个 4 个花车,你不能容纳 12x 16 个花车。
-
@PeterCordes 是的,过去你对
-mtune=generic的建议已经做过了,但我没有得到任何显着的收益(不到 1%) -
@PeterCordes 实际上我可以为每个输入水平“加载”
values(这是 16xFloat,因此是 64 字节,可以一次加载),然后转置每个相关索引垂直。这种“换位”的最佳方式是什么?有什么例子吗? -
你完全没有抓住重点。手动预取无关紧要,重要的是执行实际预取需要多少条指令。如果缓存中的数据一开始是冷的,内存流水线和软件预取将在负载尝试执行时将其引入。如果发生任何缓存未命中,调整您的代码以提高吞吐量,最小化 uops 的数量,将使 OoO exec 能够更好地重叠之前和之后的代码。
标签: c++ arrays vectorization simd sse