我想尝试使用SIMD指令编写atoi实现,包含在RapidJSON(C++ JSON 读 /写库)中.它目前在其他地方有一些SSE2和SSE4.2优化.
如果是速度增益,atoi则可以并行执行多个结果.字符串最初来自JSON数据的缓冲区,因此多atoi函数将不得不进行任何所需的调配.
我想出的算法如下:
我的目标是x86和x86-64架构.
我知道AVX2支持三个操作数Fused Multiply-Add,所以我将能够执行Sum = Number*有效数字+和.
那是我到目前为止的地方.
我的算法是否正确?有没有更好的办法?
是否有使用任何SIMD指令集的atoi参考实现?
AVX2有很多好东西.例如,它有很多指令,它们比它们的前体更加强大.Take VPERMD:它允许您从一个256位长的32位值向量中完全任意地广播/混洗/置换到另一个,并且在运行时1可以选择置换.在功能上,它废除了大量现有的旧解包,广播,置换,随机和移位指令3.
凉豆.
那么在哪里VPERMB?即,相同的指令,但在字节大小的元素上工作.或者,就此而言,VPERMW对于16位元素,在哪里?已经涉足x86程序集已经有一段时间了,很明显SSE PSHUFB指令几乎是有史以来最有用的指令之一.它可以进行任何可能的排列,广播或逐字节混洗.此外,它还可用于执行16个并行4位 - > 8位表查找2.
不幸的是,PSHUFB在AVX2中没有延伸到跨车道,所以它仅限于车道内行为.该VPERM指令能够做到跨洗牌(事实上,"烫发"和"SHUF"似乎是在指令助记符同义词?) -但被省略了8位和16位版本?
甚至似乎没有一种好的方法来模拟这个指令,而你可以轻松地模拟宽度较小的shuffles(通常,它甚至是免费的:你只需要一个不同的掩码).
我毫不怀疑英特尔已经意识到它的广泛使用PSHUFB,因此自然会出现为什么在AVX2中省略字节变体的问题.操作本质上难以在硬件中实现吗?是否有编码限制迫使其遗漏?
1通过在运行时选择,我的意思是定义混洗行为的掩码来自寄存器.这使得指令比采用立即随机掩码的早期变体更灵活,其方式与add更灵活的inc变换相比,或者变量比立即变换更灵活.
2或AVX2中的32个此类查找.
3较旧的指令偶尔会有用,如果它们的编码较短,或者避免从内存中加载掩码,但在功能上它们会被取代.
我正在尝试使用SIMD内在函数编写流压缩(获取数组并删除空元素).循环的每次迭代一次处理8个元素(SIMD宽度).
使用SSE内在函数,我可以使用_mm_shuffle_epi8()进行相当有效的操作,它执行16条表查找(收集并行计算术语).shuffle索引是预先计算的,并使用位掩码查找.
for (i = 0; i < n; i += 8)
{
v8n_Data = _mm_load_si128(&data[i]);
mask = _mm_movemask_epi8(&is_valid[i]) & 0xff; // is_valid is byte array
v8n_Compacted = _mm_shuffle_epi8(v16n_ShuffleIndices[mask]);
_mm_storeu_si128(&compacted[count], v8n_Compacted);
count += bitCount[mask];
}
Run Code Online (Sandbox Code Playgroud)
我的问题是现在我想为Altivec SIMD实现这个(不要问为什么 - 错误的商业决策).Altivec没有_mm_movemask_epi8()的等价物,这是一个关键因素.所以,我需要找到一种方法
模拟_mm_movemask_epi8() - 似乎很贵,有几个班次和OR
直接生成有效的shuffle指数 -
即,索引i将是未压缩数据中第i个有效元素的索引
element_valid: 0 0 1 0 1 0 0 1 0
gather_indices: x x x x x x 6 4 1
scatter_indices: 3 3 2 2 1 1 1 0 0
Run Code Online (Sandbox Code Playgroud)
串行执行此操作非常简单,但我需要它是并行(SIMD).使用前缀sum生成散列索引似乎很容易,但由于AltiVec和SSE都没有散点指令,我需要收集索引.收集索引是散射指数的反函数,但是如何并行获得?我知道在GPU编程的开创性时代,将散射转换为收集是一种常见的技术,但这两种方法中没有一种看似实用.
也许如果不坚持压缩保留元素顺序将允许更有效的实现?我可以放弃.
SIMD 指令是否仅用于矢量数值计算?或者它是否适合一类字符串操作任务,例如将数据行写入文本文件,其中行的顺序无关紧要?如果是这样,我应该从哪些 API 或库开始?
英特尔的矢量扩展SSE,AVX等为每个元素大小提供了两个解包操作,例如SSE内在函数是_mm_unpacklo_*和_mm_unpackhi_*.对于向量中的4个元素,它执行此操作:
inputs: (A0 A1 A2 A3) (B0 B1 B2 B3)
unpacklo/hi: (A0 B0 A1 B1) (A2 B2 A3 B3)
Run Code Online (Sandbox Code Playgroud)
解压缩的等价物vzip在ARM的NEON指令集中.但是,NEON指令集也提供了与之vuzp相反的操作vzip.对于向量中的4个元素,它执行此操作:
inputs: (A0 A1 A2 A3) (B0 B1 B2 B3)
vuzp: (A0 A2 B0 B2) (A1 A3 B1 B3)
Run Code Online (Sandbox Code Playgroud)
如何vuzp使用SSE或AVX内在函数有效实现?似乎没有针对它的指示.对于4个元素,我假设它可以使用shuffle和随后的unpack移动2个元素来完成:
inputs: (A0 A1 A2 A3) (B0 B1 B2 B3)
shuffle: (A0 A2 A1 A3) (B0 B2 B1 B3)
unpacklo/hi 2: (A0 A2 B0 B2) (A1 A3 B1 B3)
Run Code Online (Sandbox Code Playgroud)
使用单个指令是否有更高效的解决方案?(也许对于SSE优先 …
我编写了一个算法,使用Intel内部函数并行执行多个单精度操作.我的算法的每次迭代的结果是单个256位向量(__m256)中的非零项的数量.
例如:
00000000 FFFFFFFF 00000000 00000000 00000000 FFFFFFFF FFFFFFFF FFFFFFFF
Run Code Online (Sandbox Code Playgroud)
其中迭代的结果是4.
计算向量中非零项数的最快方法是什么?
目前我正在做这样的事情:
float results[8];
_mm256_storeu_ps(results, result_vector);
int count = 0;
for (uint32_t idx = 0; idx < 8; ++idx)
{
if (results[idx] != 0)
{
++count;
}
}
Run Code Online (Sandbox Code Playgroud)
这种方法运行得很好,但我想知道是否有更有效的方法来做,也许是一个不涉及商店的方法.
我在内存中有大量 64 位值。不幸的是,它们可能不会与 64 位地址对齐。我的目标是改变所有这些值的字节序,即交换/反转它们的字节。
我知道bswap交换 32 位或 64 位寄存器字节的指令。但是因为它需要一个寄存器参数,所以我不能将它传递给我的内存地址。当然我可以先将内存加载到寄存器中,然后交换,然后写回:
mov rax, qword [rsi]
bswap rax
mov qword [rsi], rax
Run Code Online (Sandbox Code Playgroud)
但这是否正确,因为地址可能未对齐?
另一种可能性是手动进行交换:
mov al, byte [rsi + 0]
mov bl, byte [rsi + 7]
mov byte [rsi + 0], bl
mov byte [rsi + 7], al
mov al, byte [rsi + 1]
mov bl, byte [rsi + 6]
mov byte [rsi + 1], bl
mov byte [rsi + 6], al
mov al, byte [rsi + 2]
mov bl, …Run Code Online (Sandbox Code Playgroud) 我最近被介绍了向量指令(理论上)并且对如何使用它们来加速我的应用程序感到兴奋。
我想改进的一个方面是一个非常热的循环:
__declspec(noinline) void pleaseVectorize(int* arr, int* someGlobalArray, int* output)
{
for (int i = 0; i < 16; ++i)
{
auto someIndex = arr[i];
output[i] = someGlobalArray[someIndex];
}
for (int i = 0; i < 16; ++i)
{
if (output[i] == 1)
{
return i;
}
}
return -1;
}
Run Code Online (Sandbox Code Playgroud)
但是,当然,所有 3 个主要编译器(msvc、gcc、clang)都拒绝对此进行矢量化。我可以理解为什么,但我想得到确认。
如果我必须手动矢量化它,它将是:
(1) VectorLoad "arr", 这带来了 16 个 4 字节整数,让我们说到 zmm0
(2) 16个内存从zmm0[0..3]指向的地址加载到zmm1[0..3],从zmm0[4..7]指向的地址加载到zmm1[4..7]所以等等
(3)比较zmm0和zmm1
(4) 向量 popcnt 到输出中找出最高有效位并基本上除以 8 得到匹配的索引
首先,向量指令可以做这些事情吗?就像他们可以执行这种“收集”操作,即从指向 zmm0 的地址加载?
以下是 clang 生成的内容:
0000000000400530 …Run Code Online (Sandbox Code Playgroud) 我有两个__m256i向量(每个包含字符),我想知道它们是否完全相同.我需要的只是true所有位都相等,0否则.
这样做最有效的方法是什么?这是加载数组的代码:
char * a1 = "abcdefhgabcdefhgabcdefhgabcdefhg";
__m256i r1 = _mm256_load_si256((__m256i *) a1);
char * a2 = "abcdefhgabcdefhgabcdefhgabcdefhg";
__m256i r2 = _mm256_load_si256((__m256i *) a2);
Run Code Online (Sandbox Code Playgroud) 我最近对理解低级计算很感兴趣.据我所知,今天广泛使用的计算机遵循x86/x86-64架构.
据我所知,架构,更具体地说,指令集架构(ISA)是程序员能够向CPU发出的指令集.
第一个问题,ISA是不断发展还是保持不变?
我认为它不断发展(意味着新指令不断被添加/先前的指令被修改?)但是旧的处理器如何能够执行用新指令编写的代码?(它不知道新的指令,但应该能够执行代码,因为它具有x86架构).编译器是处理这个东西还是处理器?基本上,相同的指令集如何能够在所有处理器上运行,无论是旧的还是新的?
最后,除了微体系结构,这不是程序员的关注(如果我错了,请纠正我),程序员在处理新处理器时会看到哪些变化?由于微体系结构的变化,旧的指令可能因为有效的实现而快速运行.但是,是否引入了新的指令以允许以前无法完成的操作?或者之前可以用一堆指令做什么,但现在可以通过硬件的变化来完成一个?新的寄存器?还要别的吗?
它是否完成了 - 如果处理器支持这个新的强大指令以加快执行速度,那么使用新指令,否则回退到较慢的旧指令.如果是,谁实现了这个if - else子句?编译器?如果不是,那会发生什么?