Skip to content

Commit 2c255db

Browse files
committed
a collection of microoptimizations in the parser.
1 parent 9e6067b commit 2c255db

File tree

1 file changed

+80
-24
lines changed

1 file changed

+80
-24
lines changed

ext/json/ext/parser/parser.c

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,53 @@ static void rvalue_cache_insert_at(rvalue_cache *cache, int index, VALUE rstring
106106
cache->entries[index] = rstring;
107107
}
108108

109-
static inline int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
109+
static inline FORCE_INLINE int rstring_cache_cmp(const char *str, const long length, VALUE rstring)
110110
{
111+
112+
113+
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && defined(__has_builtin) && __has_builtin(__builtin_bswap64)
114+
const char *rptr;
115+
long rstring_length;
116+
117+
RSTRING_GETMEM(rstring, rptr, rstring_length);
118+
119+
if (length != rstring_length) {
120+
return (int)(length - rstring_length);
121+
}
122+
123+
long i = 0;
124+
125+
for (; i + 8 <= length; i += 8) {
126+
uint64_t a, b;
127+
memcpy(&a, str + i, 8);
128+
memcpy(&b, rptr + i, 8);
129+
if (a != b) {
130+
a = __builtin_bswap64(a);
131+
b = __builtin_bswap64(b);
132+
return (a < b) ? -1 : 1;
133+
}
134+
}
135+
136+
for (; i < length; i++) {
137+
unsigned char ca = (unsigned char)str[i];
138+
unsigned char cb = (unsigned char)rptr[i];
139+
if (ca != cb) {
140+
return (ca < cb) ? -1 : 1;
141+
}
142+
}
143+
144+
return 0;
145+
#else
111146
long rstring_length = RSTRING_LEN(rstring);
112147
if (length == rstring_length) {
113148
return memcmp(str, RSTRING_PTR(rstring), length);
114149
} else {
115150
return (int)(length - rstring_length);
116151
}
152+
#endif
117153
}
118154

119-
static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length)
155+
static inline VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const long length)
120156
{
121157
if (RB_UNLIKELY(length > JSON_RVALUE_CACHE_MAX_ENTRY_LENGTH)) {
122158
// Common names aren't likely to be very long. So we just don't
@@ -136,24 +172,50 @@ static VALUE rstring_cache_fetch(rvalue_cache *cache, const char *str, const lon
136172
int mid = 0;
137173
int last_cmp = 0;
138174

175+
// while (low <= high) {
176+
// mid = (high + low) >> 1;
177+
// VALUE entry = cache->entries[mid];
178+
// last_cmp = rstring_cache_cmp(str, length, entry);
179+
180+
// if (last_cmp == 0) {
181+
// return entry;
182+
// } else if (last_cmp > 0) {
183+
// low = mid + 1;
184+
// } else {
185+
// high = mid - 1;
186+
// }
187+
// }
188+
189+
// while (low <= high) {
190+
// mid = (high + low) >> 1;
191+
// VALUE entry = cache->entries[mid];
192+
// last_cmp = rstring_cache_cmp(str, length, entry);
193+
194+
// // Branchless update of low and high
195+
// int is_greater = last_cmp > 0;
196+
// int is_less = last_cmp < 0;
197+
198+
// low = is_greater ? (mid + 1) : low;
199+
// high = is_less ? (mid - 1) : high;
200+
201+
// if (last_cmp == 0) {
202+
// return entry;
203+
// }
204+
// }
205+
139206
while (low <= high) {
140207
mid = (high + low) >> 1;
141208
VALUE entry = cache->entries[mid];
142209
last_cmp = rstring_cache_cmp(str, length, entry);
143-
144-
if (last_cmp == 0) {
145-
return entry;
146-
} else if (last_cmp > 0) {
147-
low = mid + 1;
148-
} else {
149-
high = mid - 1;
150-
}
151-
}
152-
153-
if (RB_UNLIKELY(memchr(str, '\\', length))) {
154-
// We assume the overwhelming majority of names don't need to be escaped.
155-
// But if they do, we have to fallback to the slow path.
156-
return Qfalse;
210+
211+
if (last_cmp == 0) return entry;
212+
213+
// Branchless: uses arithmetic/masking instead of branches
214+
int is_greater = (last_cmp > 0); // 1 if true, 0 if false
215+
int is_less = (last_cmp < 0); // 1 if true, 0 if false
216+
217+
low = low + is_greater * (mid + 1 - low);
218+
high = high + is_less * (mid - 1 - high);
157219
}
158220

159221
VALUE rstring = build_interned_string(str, length);
@@ -202,12 +264,6 @@ static VALUE rsymbol_cache_fetch(rvalue_cache *cache, const char *str, const lon
202264
}
203265
}
204266

205-
if (RB_UNLIKELY(memchr(str, '\\', length))) {
206-
// We assume the overwhelming majority of names don't need to be escaped.
207-
// But if they do, we have to fallback to the slow path.
208-
return Qfalse;
209-
}
210-
211267
VALUE rsymbol = build_symbol(str, length);
212268

213269
if (cache->length < JSON_RVALUE_CACHE_CAPA) {
@@ -635,7 +691,7 @@ static inline VALUE json_string_fastpath(JSON_ParserState *state, const char *st
635691
return build_string(string, stringEnd, intern, symbolize);
636692
}
637693

638-
static VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize)
694+
static inline VALUE json_string_unescape(JSON_ParserState *state, const char *string, const char *stringEnd, bool is_name, bool intern, bool symbolize)
639695
{
640696
size_t bufferSize = stringEnd - string;
641697
const char *p = string, *pe = string, *unescape, *bufferStart;
@@ -908,7 +964,7 @@ static inline VALUE json_decode_object(JSON_ParserState *state, JSON_ParserConfi
908964
return object;
909965
}
910966

911-
static inline VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name)
967+
static inline FORCE_INLINE VALUE json_decode_string(JSON_ParserState *state, JSON_ParserConfig *config, const char *start, const char *end, bool escaped, bool is_name)
912968
{
913969
VALUE string;
914970
bool intern = is_name || config->freeze;

0 commit comments

Comments
 (0)