如何使用SIMD实现atoi函数?

30
我想尝试使用SIMD指令编写一个atoi实现,以便包含在RapidJSON(一个C++ JSON读/写库)中。它目前在其他地方有一些SSE2和SSE4.2优化。如果这可以提高速度,多个atoi结果可以并行执行。字符串最初来自JSON数据缓冲区,因此多重atoi函数将不得不执行任何所需的调整。我想出的算法如下:
  1. 我可以按以下方式初始化长度为N的向量: [10^N..10^1]
  2. 我将缓冲区中的每个字符转换为整数,并将它们放入另一个向量中。
  3. 我取显着数字向量中的每个数字,并将其乘以数字向量中匹配的数字并求和。
我针对x86和x86-64架构。

我知道AVX2支持三操作数融合乘加,所以我能够执行Sum = Number * Significant Digit + Sum。
这是我目前为止的进展。
我的算法正确吗?有更好的方法吗?
是否有使用任何SIMD指令集的atoi的参考实现?


2
如果你想使用 x86 SIMD 指令来完成此操作,我建议你将其标记为 [tag:assembly] 和 [tag:x86],这样阅读相应标签队列的人就能看到你的帖子。 - fuz
@FUZxxl,我看到的大多数问题都将SIMD与C标记在一起,因为这是他们用来实现SIMD操作的语言。 - the_drow
1
顺便说一下,快速谷歌一下SIMD atoi,可以找到几个相关结果:https://software.intel.com/en-us/forums/intel-c-compiler/topic/296952 这里讨论的内容与这里的回答和评论大致相同(尽管没有zx485的回答详细)。还有这个http://www.mersenneforum.org/showthread.php?t=11590,其中有几个人在讨论一些实际代码。他们在谈论使用“double”来处理32位整数的全部范围的数学计算。早期的帖子中有一个明显完整的`atoi`函数,据他所说,在core2上需要70个周期。 - Peter Cordes
1
相关:SIMD字符串转换为无符号整数的C#性能提升在C#和C++中具有经过良好调整的高达8字节的字符串->uint,比此处的答案更简单和更快。 - Peter Cordes
显示剩余6条评论
2个回答

27
算法及其实现已完成。它是完整的,稍微经过测试(更新以减少内存使用和允许加号字符)。
此代码的属性如下:
- 适用于`int`和`uint`,范围从MIN_INT=-2147483648MAX_INT=2147483647,以及从MIN_UINT=0MAX_UINT=4294967295 - 前导符号`'-'`表示负数(合理),前导符号`'+'`被忽略 - 前导零(带或不带符号)将被忽略 - 溢出被忽略——更大的数字会环绕 - 零长度字符串的结果为`0=-0` - 无效字符将被识别,并在第一个无效字符处结束转换 - 在最后一个前导零后至少有16个字节可访问,读取EOS后面可能存在安全隐患由调用者处理 - 仅需要SSE4.2
关于该实现:
- 可以使用GNU Assembler(as)运行此代码示例,开头使用.intel_syntax noprefix - 常量数据的数据占用空间为64字节(4 * 128位XMM),相当于一个缓存行。 - 代码占用空间为46个指令,51个微操作和64个周期延迟。 - 除了错误处理之外,仅有一次用于去除前导零的循环,因此... - 时间复杂度为O(1)
算法的方法:
- Pointer to number string is expected in ESI
- Check if first char is '-', then indicate if negative number in EDX (**A**)
- Check for leading zeros and EOS (**B**)
- Check string for valid digits and get strlen() of valid chars (**C**)
- Reverse string so that power of 
  10^0 is always at BYTE 15
  10^1 is always at BYTE 14
  10^2 is always at BYTE 13
  10^3 is always at BYTE 12
  10^4 is always at BYTE 11 
  ... 
  and mask out all following chars (**D**)
- Subtract saturated '0' from each of the 16 possible chars (**1**)
- Take 16 consecutive byte-values and and split them to WORDs 
  in two XMM-registers (**2**)
  P O N M L K J I  | H G F E D C B A ->
    H   G   F   E  |   D   C   B   A (XMM0)
    P   O   N   M  |   L   K   J   I (XMM1)
- Multiply each WORD by its place-value modulo 10000 (1,10,100,1000)
  (factors smaller then MAX_WORD, 4 factors per QWORD/halfXMM)
  (**2**) so we can horizontally combine twice before another multiply.
  The PMADDWD instruction can do this and the next step:
- Horizontally add adjacent WORDs to DWORDs (**3**)
  H*1000+G*100  F*10+E*1  |  D*1000+C*100  B*10+A*1 (XMM0)
  P*1000+O*100  N*10+M*1  |  L*1000+K*100  J*10+I*1 (XMM1)
- Horizontally add adjacent DWORDs from XMM0 and XMM1 to XMM0 (**4**)
  xmmDst[31-0]   = xmm0[63-32]  + xmm0[31-0]
  xmmDst[63-32]  = xmm0[127-96] + xmm0[95-64]
  xmmDst[95-64]  = xmm1[63-32]  + xmm1[31-0]
  xmmDst[127-96] = xmm1[127-96] + xmm1[95-64]
- Values in XMM0 are multiplied with the factors (**5**)
  P*1000+O*100+N*10+M*1 (DWORD factor 1000000000000 = too big for DWORD, but possibly useful for QWORD number strings)
  L*1000+K*100+J*10+I*1 (DWORD factor 100000000)
  H*1000+G*100+F*10+E*1 (DWORD factor 10000)
  D*1000+C*100+B*10+A*1 (DWORD factor 1)
- The last step is adding these four DWORDs together with 2*PHADDD emulated by 2*(PSHUFD+PADDD)
  - xmm0[31-0]  = xmm0[63-32]  + xmm0[31-0]   (**6**)
    xmm0[63-32] = xmm0[127-96] + xmm0[95-64]
      (the upper QWORD contains the same and is ignored)
  - xmm0[31-0]  = xmm0[63-32]  + xmm0[31-0]   (**7**)
- If the number is negative (indicated in EDX by 000...0=pos or 111...1=neg), negate it(**8**)

以下是使用GNU Assembler和intel语法的示例实现:

.intel_syntax noprefix
.data
  .align 64
    ddqDigitRange: .byte  '0','9',0,0,0,0,0,0,0,0,0,0,0,0,0,0
    ddqShuffleMask:.byte  15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 
    ddqFactor1:    .word  1,10,100,1000, 1,10,100,1000  
    ddqFactor2:    .long  1,10000,100000000,0
.text    
_start:
   mov   esi, lpInputNumberString
   /* (**A**) indicate negative number in EDX */
   mov   eax, -1
   xor   ecx, ecx
   xor   edx, edx
   mov   bl,  byte ptr [esi]
   cmp   bl,  '-'
   cmove edx, eax
   cmp   bl,  '+'
   cmove ecx, eax
   sub   esi, edx
   sub   esi, ecx
   /* (**B**)remove leading zeros */
   xor   eax,eax               /* return value ZERO */
  remove_leading_zeros:
   inc   esi
   cmp   byte ptr [esi-1], '0'  /* skip leading zeros */
  je remove_leading_zeros
   cmp   byte ptr [esi-1], 0    /* catch empty string/number */
  je FINISH
   dec   esi
   /* check for valid digit-chars and invert from front to back */
   pxor      xmm2, xmm2         
   movdqa    xmm0, xmmword ptr [ddqDigitRange]
   movdqu    xmm1, xmmword ptr [esi]
   pcmpistri xmm0, xmm1, 0b00010100 /* (**C**) iim8=Unsigned bytes, Ranges, Negative Polarity(-), returns strlen() in ECX */
  jo FINISH             /* if first char is invalid return 0 - prevent processing empty string - 0 is still in EAX */
   mov al , '0'         /* value to subtract from chars */
   sub ecx, 16          /* len-16=negative to zero for shuffle mask */
   movd      xmm0, ecx
   pshufb    xmm0, xmm2 /* broadcast CL to all 16 BYTEs */
   paddb     xmm0, xmmword ptr [ddqShuffleMask] /* Generate permute mask for PSHUFB - all bytes < 0 have highest bit set means place gets zeroed */
   pshufb    xmm1, xmm0 /* (**D**) permute - now from highest to lowest BYTE are factors 10^0, 10^1, 10^2, ... */
   movd      xmm0, eax                         /* AL='0' from above */
   pshufb    xmm0, xmm2                        /* broadcast AL to XMM0 */
   psubusb   xmm1, xmm0                        /* (**1**) */
   movdqa    xmm0, xmm1
   punpcklbw xmm0, xmm2                        /* (**2**) */
   punpckhbw xmm1, xmm2
   pmaddwd   xmm0, xmmword ptr [ddqFactor1]    /* (**3**) */
   pmaddwd   xmm1, xmmword ptr [ddqFactor1]
   phaddd    xmm0, xmm1                        /* (**4**) */
   pmulld    xmm0, xmmword ptr [ddqFactor2]    /* (**5**) */
   pshufd    xmm1, xmm0, 0b11101110            /* (**6**) */
   paddd     xmm0, xmm1
   pshufd    xmm1, xmm0, 0b01010101            /* (**7**) */
   paddd     xmm0, xmm1
   movd      eax, xmm0
   /* negate if negative number */              
   add       eax, edx                          /* (**8**) */
   xor       eax, edx
  FINISH:
   /* EAX is return (u)int value */

Haswell 32位架构的Intel-IACA吞吐量分析结果:

Throughput Analysis Report
--------------------------
Block Throughput: 16.10 Cycles       Throughput Bottleneck: InterIteration

Port Binding In Cycles Per Iteration:
---------------------------------------------------------------------------------------
|  Port  |  0   -  DV  |  1   |  2   -  D   |  3   -  D   |  4   |  5   |  6   |  7   |
---------------------------------------------------------------------------------------
| Cycles | 9.5    0.0  | 10.0 | 4.5    4.5  | 4.5    4.5  | 0.0  | 11.1 | 11.4 | 0.0  |
---------------------------------------------------------------------------------------

N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion happened
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256 instruction, dozens of cycles penalty is expected
! - instruction not supported, was not accounted in Analysis

| Num Of |                    Ports pressure in cycles                     |    |
|  Uops  |  0  - DV  |  1  |  2  -  D  |  3  -  D  |  4  |  5  |  6  |  7  |    |
---------------------------------------------------------------------------------
|   0*   |           |     |           |           |     |     |     |     |    | xor eax, eax
|   0*   |           |     |           |           |     |     |     |     |    | xor ecx, ecx
|   0*   |           |     |           |           |     |     |     |     |    | xor edx, edx
|   1    |           | 0.1 |           |           |     |     | 0.9 |     |    | dec eax
|   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     | CP | mov bl, byte ptr [esi]
|   1    |           |     |           |           |     |     | 1.0 |     | CP | cmp bl, 0x2d
|   2    | 0.1       | 0.2 |           |           |     |     | 1.8 |     | CP | cmovz edx, eax
|   1    | 0.1       | 0.5 |           |           |     |     | 0.4 |     | CP | cmp bl, 0x2b
|   2    | 0.5       | 0.2 |           |           |     |     | 1.2 |     | CP | cmovz ecx, eax
|   1    | 0.2       | 0.5 |           |           |     |     | 0.2 |     | CP | sub esi, edx
|   1    | 0.2       | 0.5 |           |           |     |     | 0.3 |     | CP | sub esi, ecx
|   0*   |           |     |           |           |     |     |     |     |    | xor eax, eax
|   1    | 0.3       | 0.1 |           |           |     |     | 0.6 |     | CP | inc esi
|   2^   | 0.3       |     | 0.5   0.5 | 0.5   0.5 |     |     | 0.6 |     |    | cmp byte ptr [esi-0x1], 0x30
|   0F   |           |     |           |           |     |     |     |     |    | jz 0xfffffffb
|   2^   | 0.6       |     | 0.5   0.5 | 0.5   0.5 |     |     | 0.4 |     |    | cmp byte ptr [esi-0x1], 0x0
|   0F   |           |     |           |           |     |     |     |     |    | jz 0x8b
|   1    | 0.1       | 0.9 |           |           |     |     |     |     | CP | dec esi
|   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | movdqa xmm0, xmmword ptr [0x80492f0]
|   1    |           |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     | CP | movdqu xmm1, xmmword ptr [esi]
|   0*   |           |     |           |           |     |     |     |     |    | pxor xmm2, xmm2
|   3    | 2.0       | 1.0 |           |           |     |     |     |     | CP | pcmpistri xmm0, xmm1, 0x14
|   1    |           |     |           |           |     |     | 1.0 |     |    | jo 0x6e
|   1    |           | 0.4 |           |           |     | 0.1 | 0.5 |     |    | mov al, 0x30
|   1    | 0.1       | 0.5 |           |           |     | 0.1 | 0.3 |     | CP | sub ecx, 0x10
|   1    |           |     |           |           |     | 1.0 |     |     | CP | movd xmm0, ecx
|   1    |           |     |           |           |     | 1.0 |     |     | CP | pshufb xmm0, xmm2
|   2^   |           | 1.0 | 0.5   0.5 | 0.5   0.5 |     |     |     |     | CP | paddb xmm0, xmmword ptr [0x80492c0]
|   1    |           |     |           |           |     | 1.0 |     |     | CP | pshufb xmm1, xmm0
|   1    |           |     |           |           |     | 1.0 |     |     |    | movd xmm0, eax
|   1    |           |     |           |           |     | 1.0 |     |     |    | pshufb xmm0, xmm2
|   1    |           | 1.0 |           |           |     |     |     |     | CP | psubusb xmm1, xmm0
|   0*   |           |     |           |           |     |     |     |     | CP | movdqa xmm0, xmm1
|   1    |           |     |           |           |     | 1.0 |     |     | CP | punpcklbw xmm0, xmm2
|   1    |           |     |           |           |     | 1.0 |     |     |    | punpckhbw xmm1, xmm2
|   2^   | 1.0       |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     | CP | pmaddwd xmm0, xmmword ptr [0x80492d0]
|   2^   | 1.0       |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     |    | pmaddwd xmm1, xmmword ptr [0x80492d0]
|   3    |           | 1.0 |           |           |     | 2.0 |     |     | CP | phaddd xmm0, xmm1
|   3^   | 2.0       |     | 0.5   0.5 | 0.5   0.5 |     |     |     |     | CP | pmulld xmm0, xmmword ptr [0x80492e0]
|   1    |           |     |           |           |     | 1.0 |     |     | CP | pshufd xmm1, xmm0, 0xee
|   1    |           | 1.0 |           |           |     |     |     |     | CP | paddd xmm0, xmm1
|   1    |           |     |           |           |     | 1.0 |     |     | CP | pshufd xmm1, xmm0, 0x55
|   1    |           | 1.0 |           |           |     |     |     |     | CP | paddd xmm0, xmm1
|   1    | 1.0       |     |           |           |     |     |     |     | CP | movd eax, xmm0
|   1    |           |     |           |           |     |     | 1.0 |     | CP | add eax, edx
|   1    |           |     |           |           |     |     | 1.0 |     | CP | xor eax, edx
Total Num Of Uops: 51

Intel-IACA对Haswell 32位的延迟分析结果:

Latency Analysis Report
---------------------------
Latency: 64 Cycles

N - port number or number of cycles resource conflict caused delay, DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3), CP - on a critical path
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion happened
# - ESP Tracking sync uop was issued
@ - Intel(R) AVX to Intel(R) SSE code switch, dozens of cycles penalty is expected
! - instruction not supported, was not accounted in Analysis

The Resource delay is counted since all the sources of the instructions are ready
and until the needed resource becomes available

| Inst |                 Resource Delay In Cycles                  |    |
| Num  | 0  - DV | 1  | 2  - D  | 3  - D  | 4  | 5  | 6  | 7  | FE |    |
-------------------------------------------------------------------------
|  0   |         |    |         |         |    |    |    |    |    |    | xor eax, eax
|  1   |         |    |         |         |    |    |    |    |    |    | xor ecx, ecx
|  2   |         |    |         |         |    |    |    |    |    |    | xor edx, edx
|  3   |         |    |         |         |    |    |    |    |    |    | dec eax
|  4   |         |    |         |         |    |    |    |    | 1  | CP | mov bl, byte ptr [esi]
|  5   |         |    |         |         |    |    |    |    |    | CP | cmp bl, 0x2d
|  6   |         |    |         |         |    |    |    |    |    | CP | cmovz edx, eax
|  7   |         |    |         |         |    |    |    |    |    | CP | cmp bl, 0x2b
|  8   |         |    |         |         |    |    | 1  |    |    | CP | cmovz ecx, eax
|  9   |         |    |         |         |    |    |    |    |    | CP | sub esi, edx
| 10   |         |    |         |         |    |    |    |    |    | CP | sub esi, ecx
| 11   |         |    |         |         |    |    |    |    | 3  |    | xor eax, eax
| 12   |         |    |         |         |    |    |    |    |    | CP | inc esi
| 13   |         |    |         |         |    |    |    |    |    |    | cmp byte ptr [esi-0x1], 0x30
| 14   |         |    |         |         |    |    |    |    |    |    | jz 0xfffffffb
| 15   |         |    |         |         |    |    |    |    |    |    | cmp byte ptr [esi-0x1], 0x0
| 16   |         |    |         |         |    |    |    |    |    |    | jz 0x8b
| 17   |         |    |         |         |    |    |    |    |    | CP | dec esi
| 18   |         |    |         |         |    |    |    |    | 4  |    | movdqa xmm0, xmmword ptr [0x80492f0]
| 19   |         |    |         |         |    |    |    |    |    | CP | movdqu xmm1, xmmword ptr [esi]
| 20   |         |    |         |         |    |    |    |    | 5  |    | pxor xmm2, xmm2
| 21   |         |    |         |         |    |    |    |    |    | CP | pcmpistri xmm0, xmm1, 0x14
| 22   |         |    |         |         |    |    |    |    |    |    | jo 0x6e
| 23   |         |    |         |         |    |    |    |    | 6  |    | mov al, 0x30
| 24   |         |    |         |         |    |    |    |    |    | CP | sub ecx, 0x10
| 25   |         |    |         |         |    |    |    |    |    | CP | movd xmm0, ecx
| 26   |         |    |         |         |    |    |    |    |    | CP | pshufb xmm0, xmm2
| 27   |         |    |         |         |    |    |    |    | 7  | CP | paddb xmm0, xmmword ptr [0x80492c0]
| 28   |         |    |         |         |    |    |    |    |    | CP | pshufb xmm1, xmm0
| 29   |         |    |         |         |    | 1  |    |    |    |    | movd xmm0, eax
| 30   |         |    |         |         |    | 1  |    |    |    |    | pshufb xmm0, xmm2
| 31   |         |    |         |         |    |    |    |    |    | CP | psubusb xmm1, xmm0
| 32   |         |    |         |         |    |    |    |    |    | CP | movdqa xmm0, xmm1
| 33   |         |    |         |         |    |    |    |    |    | CP | punpcklbw xmm0, xmm2
| 34   |         |    |         |         |    |    |    |    |    |    | punpckhbw xmm1, xmm2
| 35   |         |    |         |         |    |    |    |    | 9  | CP | pmaddwd xmm0, xmmword ptr [0x80492d0]
| 36   |         |    |         |         |    |    |    |    | 9  |    | pmaddwd xmm1, xmmword ptr [0x80492d0]
| 37   |         |    |         |         |    |    |    |    |    | CP | phaddd xmm0, xmm1
| 38   |         |    |         |         |    |    |    |    | 10 | CP | pmulld xmm0, xmmword ptr [0x80492e0]
| 39   |         |    |         |         |    |    |    |    |    | CP | pshufd xmm1, xmm0, 0xee
| 40   |         |    |         |         |    |    |    |    |    | CP | paddd xmm0, xmm1
| 41   |         |    |         |         |    |    |    |    |    | CP | pshufd xmm1, xmm0, 0x55
| 42   |         |    |         |         |    |    |    |    |    | CP | paddd xmm0, xmm1
| 43   |         |    |         |         |    |    |    |    |    | CP | movd eax, xmm0
| 44   |         |    |         |         |    |    |    |    |    | CP | add eax, edx
| 45   |         |    |         |         |    |    |    |    |    | CP | xor eax, edx

Resource Conflict on Critical Paths: 
-----------------------------------------------------------------
|  Port  | 0  - DV | 1  | 2  - D  | 3  - D  | 4  | 5  | 6  | 7  |
-----------------------------------------------------------------
| Cycles | 0    0  | 0  | 0    0  | 0    0  | 0  | 0  | 1  | 0  |
-----------------------------------------------------------------

List Of Delays On Critical Paths
-------------------------------
6 --> 8 1 Cycles Delay On Port6

评论中建议由Peter Cordes提出的替代方案是用imul替换最后两个add+xor指令。这种操作码的集中可能更为优越。不幸的是,IACA不支持该指令,并会抛出!-指令不受支持,未在分析中计算的注释。尽管我喜欢OpCodes的减少和从(2uops,2c延迟)到(1 uop,3c延迟 - “延迟更差,但在AMD上仍然是一个m-op”)的减少,但我更喜欢让实现者选择方法。我没有检查以下代码是否足以解析任何数字。它仅仅是为了完整性而提及,并且可能需要对其他部分进行代码修改(特别是处理正数的情况)。

另一种替代方案可以将最后两行替换为:

  ...
  /* negate if negative number */              
   imul eax, edx
  FINISH:
  /* EAX is return (u)int value */

2
@Peter Cordes:我不确定你的意思。XMM寄存器宽度为128位,QWORD宽度为64位,DWORD宽度为32位,WORD宽度为16位,BYTE宽度为8位。因此,一个128位的寄存器可以被认为包含两个64位的值(QWORDs)。我选择这个表达式是因为有四个因素1、10、100、1000,每个WORD宽度应用于半个XMM寄存器,一个QWORD=4*WORD。我这样做是为了清晰明了,但可能在这方面失败了。 - zx485
1
无论如何,现在你已经知道了最有效的atoi实现方式,接下来就是掩码处理字符串末尾之外的字节的棘手部分。也许可以使用一个全零向量进行pcmpeqb操作?然后使用pmovmskb / 位扫描来查找位置?或者如果你是从更大的缓冲区解析它,那么你已经有了字符串的长度。用ASCII '0'或整数零填充字符串/向量的其余部分,在减法之后会起作用。也许可以使用长度作为索引进入掩码表中? - Peter Cordes
1
我相信你(并且知道)phaddd速度较慢。我只是想鼓励你提供一些代码,因为我已经尝试了许多其他替代方案...;-) 顺便说一句,SDE很好,但如果我没记错的话,你不能用它调试运行的代码 :-( - zx485
1
啊,好的。那么你只需要使用 pshufd xmm1, xmm0, 0x3 <<2 + 0x2(或者 movhlps)将高两个双字移动到另一个向量的低位位置,然后执行 paddd xmm0, xmm1。这样就可以模拟出 psrldq xmm0, 8psrldq xmm0, 4 的效果,但是操作是非破坏性的。如果您使用 AVX,只需使用 vpsrldq xmm1, xmm0, 8 即可。由于您最终只是获取低双字结果,因此在其他元素中得到垃圾值或零值并不重要,只要避免错误依赖即可(所以 movhlps 在这方面并不好,因为它会合并到目标寄存器中)。 - Peter Cordes
1
使用 mov eax, -1 而不是 xor / dec-1 存入 eax。由于 uop-cache 空间比 L1 I-cache 更有限且更珍贵,因此应优化融合域 uops 而不是代码大小。使用 imul eax, edx 而不是 add/xor 进行取反。它只需要 1 个 uop,3 个时钟周期的延迟,而不是 2 个 uop,2 个时钟周期的延迟(在 AMD 上延迟更差,但仍然是一个 m-op)。延迟可能无关紧要:当 RapidJSON 解析时,它只是存储结果并解析下一个整数,而不是将结果整数用于依赖计算。 - Peter Cordes
显示剩余17条评论

5
我会这样解决这个问题:
  1. 将累加器初始化为0。
  2. 将字符串的下一个四个字符加载到SSE寄存器中。
  3. 从每个字符中减去值'0'
  4. 找到向量中第一个无符号值大于9的值。
  5. 如果找到了一个值,则将向量的组件向右移动,以便在上一步中找到的值刚好被移出。
  6. 加载包含十的幂次方(1000, 100, 10, 1)的向量并进行乘法计算。
  7. 计算向量中所有条目的总和。
  8. 用适当的值(取决于步骤5中的位移数)乘以累加器并添加向量。你可以使用FMA指令来实现,但我不知道是否存在整数的这种指令。
  9. 如果在第四步中没有找到大于9的值,则转到第二步。
  10. 返回累加器。

您可以通过在步骤5中清零从错误位置开始的所有条目,而不是进行移位并在最后除以适当的十次幂,来简化算法。

请注意,该算法会读取超出字符串结尾的内容,因此不能完全替代atoi


1
@the_drow:你不能轻易地这样做 - 你正试图以一种不太适合的方式使用SIMD,因为SIMD是为“垂直”操作而设计的,而不是像这样的“水平”操作。你需要确保你的输入字符串填充到16字节的倍数。不过,如果你真的想要一个稳健的实现(即不仅仅是学习练习),你可以在处理之前将其复制到一个临时填充缓冲区中。 - Paul R
1
仅有的SSE或AVX整数FMAs对此无用:PMADDWD:打包字垂直相乘,然后水平添加相邻的双字。和PMADDUBSW:将第一个操作数中的无符号字节与第二个操作数的相应有符号字节进行垂直相乘,然后水平添加相邻的有符号字对(带有有符号饱和)。如果我没记错的话,AVX512扩展之一有一些整数FMA内容。 - Peter Cordes
1
@the_drow:可以参考这个问答:https://dev59.com/XlsX5IYBdhLWcg3wCLyx。另一个选择是确保你的字符串缓存每16个字节对齐,这样就不会跨越一页或者缓存行边界了。 - Peter Cordes
1
@PaulR:如果您在查找字符串结束时一次写入一个字节到本地缓冲区,那么您必须承受存储-转发暂停的延迟。虽然这不是直接的吞吐量问题。不过,我认为如果在一般情况下有性能优势,atoi 已经以这种方式实现了。不过,关于学习练习的观点很好。它肯定是一个易于验证结果的问题,并且具有 libc 中现有的已知良好实现。 - Peter Cordes
2
@FUZxxl:它确实说:“我知道AXV2支持...”。但尽管如此,如果我可以因你暗中批评OP在问题中缺乏明确性而再给你+1, 我会的。因为这并没有明确说明他的目标是什么。假如他愿意假定哪个级别的SSE指令,并且潜在地进行了哪些微架构的优化,这是很重要的。同样重要的是他是否能够有用地并行 atoi多个字符串。(虽然在实践中,从4或16个字符串中每次获取一个字符的洗牌开销将是致命的。) - Peter Cordes
显示剩余20条评论

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