我对SIMD感到好奇,想知道它是否可以处理此用例。
假设我有一个2048个整数的数组,例如 [0x018A,0x004B,0x01C0、0x0234、0x0098、0x0343、0x0222、0x0301、0x0398、0x0087、0x0167、0x0389、0x03F2、0x0034、0x0345,...]
请注意它们如何以0x00、0x01、0x02或0x03开头。我想将它们分成4个数组:
我想我会有这样的代码:
int main() {
uint16_t in[2048] = ...;
// 4 arrays, one for each category
uint16_t out[4][2048];
// Pointers to the next available slot in each of the arrays
uint16_t *nextOut[4] = { out[0], out[1], out[2], out[3] };
for (uint16_t *nextIn = in; nextIn < 2048; nextIn += 4) {
(*** magic simd instructions here ***)
// Equivalent non-simd code:
uint16_t categories[4];
for (int i = 0; i < 4; i++) {
categories[i] = nextIn[i] & 0xFF00;
}
for (int i = 0; i < 4; i++) {
uint16_t category = categories[i];
*nextOut[category] = nextIn[i];
nextOut[category]++;
}
}
// Now I have my categoried arrays!
}
我想象我的第一个内部循环不需要SIMD,它可以只是一条(x & 0xFF00FF00FF00FF00)
指令,但是我想知道是否可以将第二个内部循环变成SIMD指令。
对于我正在执行的“分类”操作,是否有任何SIMD指令?
“插入”指令似乎有些许前途,但是我有点绿了,无法理解https://software.intel.com/en-us/node/695331中的描述。
如果不是,有什么要紧吗?
谢谢!
答案 0 :(得分:3)
您可以使用SIMD做到这一点,但是它的速度将取决于您可用的指令集的确切程度以及实现的精明程度。
一种方法是获取数组并将其“过滤”以分离出属于不同存储桶的元素。例如,从数组中获取32个字节,该数组将包含16个16位元素。使用一些cmpgt
指令获取一个掩码,该掩码确定每个元素是属于00 + 01
存储桶还是02 + 03
存储桶。然后使用某种“压缩”或“过滤”操作将所有被屏蔽的元素连续移动到寄存器的一端,然后对未屏蔽的元素进行相同的移动。
然后再重复一次以从00
中筛选出01
,从02
中筛选出03
。
使用AVX2,您可以从this question开始,以启发“压缩”操作。借助AVX512,您可以使用vcompress
指令来提供帮助:它完全可以执行此操作,但是仅在32位或64位粒度下执行,因此您至少需要对每个向量进行两次。
您还可以尝试一种垂直方法,先加载N个矢量,然后在它们之间交换,以便第0个矢量具有最小的元素,依此类推。此时,您可以对压缩阶段使用更优化的算法(例如,。如果垂直排序足够的向量,则末端的向量可能完全以0x00
等开头。
最后,您还可以考虑在源头或作为预处理步骤来不同地组织数据:从有效载荷字节中分离出始终为0-3的“类别”字节。许多处理步骤只需要在一个或另一个上执行,因此您可以通过将它们分开来提高效率。例如,您可以对所有类别的32个字节进行比较操作,然后对32个有效负载字节进行压缩操作(至少在每个类别都是唯一的最后一步中)。
这将导致字节元素数组,而不是16位元素数组,其中“类别”字节是隐式的。您已将数据大小减少了一半,这可能会加快您将来要处理数据的其他所有事情。
如果无法生成这种格式的源数据,则可以在将有效负载放入正确的存储桶中时使用存储桶作为删除标记字节的机会,因此输出为uint8_t out[4][2048];
。如注释中所述,如果您正在使用pshufb
字节混洗进行SIMD左包装,则可以选择一种混洗控制向量,该向量仅将有效载荷字节压缩到下半部分。
(直到AVX512BW,x86 SIMD没有任何可变控制字改组,只有字节或dword,因此您已经需要一个字节改组,它可以像将有效载荷字节打包到底部。)