Hi all, author here.
Several people have raised questions about SIMD. For many workloads, SIMD is obviously a huge win. But in my experience, for problems like this, the gains are marginal and the additional complexity often isn't worth it. e.g. Do you have a build-time option? Check at runtime at every call? Use templates to check at startup and run a different code path?
That said, it never hurts to try, so I implemented the fast string path in AVX2 (see https://github.com/chadaustin/sajson/commit/e87da27883ffe0a3...). It actually benchmarks slower than the scalar code: https://gist.github.com/chadaustin/6fe8d250ac8402ad70a724e7a...
I couldn't tell you why - it's possible something could be tweaked to make it actually execute faster, or that enabling -mavx2 causes other code to get slower, but I wanted to illustrate that enabling SIMD is not always an automatic go fast button.
I did look at the instructions generated and they seem reasonable:
.LBB5_118:
vmovdqu ymm5, ymmword ptr [rbx - 32]
vpcmpgtb ymm6, ymm5, ymm0
vpcmpeqb ymm7, ymm5, ymm1
vpcmpeqb ymm8, ymm5, ymm2
vpcmpeqb ymm5, ymm5, ymm3
vpxor ymm5, ymm5, ymm4
vpor ymm6, ymm8, ymm6
vpor ymm5, ymm5, ymm6
vpor ymm5, ymm5, ymm7
vpmovmskb esi, ymm5
test esi, esi
jne .LBB5_148
mov rbp, rax
sub rbp, rbx
mov rsi, rbx
lea rbx, [rbx + 32]
cmp rbp, 31
jg .LBB5_118
jmp .LBB5_120
.LBB5_148:
add rbx, -32
bsr eax, esi
xor eax, 31
lea rsi, [rbx + rax]
mov bl, byte ptr [rbx + rax]
.LBB5_125:
mov r10, qword ptr [rsp + 24] # 8-byte Reload
mov r9b, byte ptr [rsp + 39] # 1-byte Reload
sub rcx, r8
movzx eax, bl
cmp eax, 34
jne .LBB5_127
I suppose it's also worth pointing out that, while string scanning is an important part of JSON parsing, the instruction histogram is generally spread across the entire parser. That is, a doubling in string parsing performance would only help overall parse times by 10-20% maybe.
Edit: Upon thinking about it, it's probably slower because there's a dependency chain through a pile of high-latency vector instructions all the way to the bsr (should be bsf, I fixed on the branch) to the next loop iteration's load. The branch predictor doesn't have much opportunity to help us out here. You could look at Agner Fog's tables and calculate the critical path latency of this loop, but I'm guessing it's not pretty.