Skip to content

Commit a9c9a22

Browse files
committed
Use SWAR for parsing integers on little endian machines.
1 parent 8f5c7f9 commit a9c9a22

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

ext/json/ext/parser/parser.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,32 @@ static VALUE json_string_unescape(JSON_ParserState *state, const char *string, c
767767

768768
#define MAX_FAST_INTEGER_SIZE 18
769769

770+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
771+
// From: https://lemire.me/blog/2022/01/21/swar-explained-parsing-eight-digits/
772+
// Additional References:
773+
// https://johnnylee-sde.github.io/Fast-numeric-string-to-int/
774+
// http://0x80.pl/notesen/2014-10-12-parsing-decimal-numbers-part-1-swar.html
775+
static inline uint32_t parse_eight_digits_unrolled(const char *p) {
776+
uint64_t val;
777+
memcpy(&val, p, sizeof(uint64_t));
778+
const uint64_t mask = 0x000000FF000000FF;
779+
const uint64_t mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
780+
const uint64_t mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
781+
val -= 0x3030303030303030;
782+
val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
783+
val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
784+
return (uint32_t) val;
785+
}
786+
787+
// From: https://github.com/simdjson/simdjson/blob/32b301893c13d058095a07d9868edaaa42ee07aa/include/simdjson/generic/numberparsing.h#L333
788+
// Branchless version of: http://0x80.pl/articles/swar-digits-validate.html
789+
static inline int has_eight_consecutive_digits(const char *p) {
790+
uint64_t val;
791+
memcpy(&val, p, sizeof(uint64_t));
792+
return (((val & 0xF0F0F0F0F0F0F0F0) | (((val + 0x0606060606060606) & 0xF0F0F0F0F0F0F0F0) >> 4)) == 0x3333333333333333);
793+
}
794+
#endif
795+
770796
static VALUE json_decode_large_integer(const char *start, long len)
771797
{
772798
VALUE buffer_v;
@@ -1105,6 +1131,19 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
11051131
}
11061132
}
11071133

1134+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1135+
while (state->cursor + 8 <= state->end) {
1136+
if (has_eight_consecutive_digits(state->cursor)) {
1137+
uint64_t val = (uint64_t) parse_eight_digits_unrolled(state->cursor);
1138+
mantissa = mantissa * 100000000 + val;
1139+
mantissa_digits += 8;
1140+
state->cursor += 8;
1141+
continue;
1142+
}
1143+
break;
1144+
}
1145+
#endif
1146+
11081147
// Parse integer part and extract mantissa digits
11091148
while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) {
11101149
mantissa = mantissa * 10 + (*state->cursor - '0');
@@ -1128,6 +1167,18 @@ static VALUE json_parse_any(JSON_ParserState *state, JSON_ParserConfig *config)
11281167
raise_parse_error_at("invalid number: %s", state, start);
11291168
}
11301169

1170+
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1171+
while (state->cursor + 8 <= state->end) {
1172+
if (has_eight_consecutive_digits(state->cursor)) {
1173+
uint64_t val = (uint64_t) parse_eight_digits_unrolled(state->cursor);
1174+
mantissa = mantissa * 100000000 + val;
1175+
mantissa_digits += 8;
1176+
state->cursor += 8;
1177+
continue;
1178+
}
1179+
break;
1180+
}
1181+
#endif
11311182
while ((state->cursor < state->end) && rb_isdigit(*state->cursor)) {
11321183
mantissa = mantissa * 10 + (*state->cursor - '0');
11331184
mantissa_digits++;

0 commit comments

Comments
 (0)