diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2025-08-23 19:43:33 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2025-08-27 11:45:28 +0200 |
| commit | 413aac1035c855f3e735bf8539c0864182f9c6fb (patch) | |
| tree | 3d8ef24389ee9e5662cc98900d4909ace4d97edc | |
| parent | a44ce5c6f74fb777408b455ae21ca545447c0ade (diff) | |
code cleanup & keep using simd in string
| -rw-r--r-- | .vscode/launch.json | 8 | ||||
| -rw-r--r-- | README.md | 1 | ||||
| -rw-r--r-- | src/json.c | 28 | ||||
| -rw-r--r-- | tests/test_json.c | 10 |
4 files changed, 32 insertions, 15 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json index 7a44b33..544f33d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,6 +14,14 @@ "1" ], "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Test", + "program": "${workspaceFolder}/Debug/test", + "args": [], + "cwd": "${workspaceFolder}" } ] } @@ -20,6 +20,7 @@ | no 0 mem alloc | 299 ms ± 8 ms per loop (mean ± std. dev. of 10 runs, 1 loop each) | | | | Str_view | 272 ms ± 6 ms per loop (mean ± std. dev. of 10 runs, 1 loop each) | 330.46 MB/s | 1255 MB | | simd | 254 ms ± 6 ms per loop (mean ± std. dev. of 10 runs, 1 loop each) | 353.36 MB/s | 1255 MB | +| negative | 206 ms ± 6 ms per loop (mean ± std. dev. of 10 runs, 1 loop each) | 436.25 MB/s | 1255MB | ```c void print_bytes(byte_32 *data) { @@ -145,9 +145,9 @@ parse_result parse_number(byte *src, uptr start_pos) { return result; } -uptr absorb_whitespaces(byte *src, uptr start_pos) { +uptr absorb_whitespaces(byte *src, uptr start_pos, uptr max_pos) { uptr pos = start_pos; - while (isspace(src[pos])) { + while (pos < max_pos && isspace(src[pos])) { pos++; } return pos; @@ -163,10 +163,10 @@ parse_result parse_array(Arena *a, byte *src, uptr start_pos, uptr max_pos) { } Vec items = Vec_new(a, JSON, 2); for (;;) { - result.pos = absorb_whitespaces(src, result.pos); + result.pos = absorb_whitespaces(src, result.pos, max_pos); parse_result item_result = parse_any(a, src, result.pos, max_pos); Vec_push(a, &items, &item_result.value); - result.pos = absorb_whitespaces(src, item_result.pos); + result.pos = absorb_whitespaces(src, item_result.pos, max_pos); if (src[result.pos] == ',') { result.pos++; } else { @@ -187,17 +187,15 @@ parse_result parse_string(byte *src, uptr start_pos, uptr max_pos) { // SIMD optimization: process 32 bytes at a time using AVX2 byte_32 quote_mask = _mm256_set1_epi8('"'); - while (1) { - if (max_pos - result.pos < 32) break; + for (; result.pos + 32 <= max_pos; result.pos += 32) { byte_32 chunk = _mm256_loadu_si256((byte_32 *)(src + result.pos)); byte_32 cmp = _mm256_cmpeq_epi8(chunk, quote_mask); i32 mask = _mm256_movemask_epi8(cmp); - if (_mm_popcnt_u32(mask) > 0) { + if (mask != 0) { result.pos += __builtin_ctz(mask); - break; + if (src[result.pos - 1] != '\\') break; }; - result.pos += 32; } // Fall back to original byte-by-byte processing for the remaining bytes @@ -216,7 +214,7 @@ parse_result parse_string(byte *src, uptr start_pos, uptr max_pos) { parse_result parse_object(Arena *a, byte *src, uptr start_pos, uptr max_pos) { assert_parse(src[start_pos] == '{', "invalid object start", src, start_pos); parse_result result = {0}; - result.pos = absorb_whitespaces(src, start_pos + 1); + result.pos = absorb_whitespaces(src, start_pos + 1, max_pos); size len = 0; Vec keys = Vec_new(a, Str, 2); Vec values = Vec_new(a, JSON, 2); @@ -227,21 +225,21 @@ parse_result parse_object(Arena *a, byte *src, uptr start_pos, uptr max_pos) { } for (;;) { parse_result key_result = parse_string(src, result.pos, max_pos); - result.pos = absorb_whitespaces(src, key_result.pos); + result.pos = absorb_whitespaces(src, key_result.pos, max_pos); assert_parse(src[result.pos] == ':', "invalid keyval delim", src, result.pos); - result.pos = absorb_whitespaces(src, result.pos + 1); + result.pos = absorb_whitespaces(src, result.pos + 1, max_pos); parse_result value_result = parse_any(a, src, result.pos, max_pos); len++; assert(key_result.value.type == JSON_STRING, "invalid json type for key: %d", key_result.value.type); Vec_push(a, &keys, &key_result.value.as.string); Vec_push(a, &values, &value_result.value); - result.pos = absorb_whitespaces(src, value_result.pos); + result.pos = absorb_whitespaces(src, value_result.pos, max_pos); if (src[result.pos] != ',') { break; } - result.pos = absorb_whitespaces(src, result.pos + 1); + result.pos = absorb_whitespaces(src, result.pos + 1, max_pos); } assert_parse(src[result.pos] == '}', "invalid object end", src, result.pos); result.pos++; @@ -250,7 +248,7 @@ parse_result parse_object(Arena *a, byte *src, uptr start_pos, uptr max_pos) { } parse_result parse_any(Arena *a, byte *src, uptr pos, uptr max_pos) { - pos = absorb_whitespaces(src, pos); + pos = absorb_whitespaces(src, pos, max_pos); if (isdigit(src[pos]) || src[pos] == '-') return parse_number(src, pos); if (src[pos] == '[') return parse_array(a, src, pos, max_pos); if (src[pos] == '"') return parse_string(src, pos, max_pos); diff --git a/tests/test_json.c b/tests/test_json.c index 1aec8c5..b2f8ece 100644 --- a/tests/test_json.c +++ b/tests/test_json.c @@ -138,6 +138,16 @@ i32 test_json_null() { return 0; } +i32 test_json_long_spaces() { + Arena perm = Arena_new(128e3); + JSON doc = JSON_parse_raw( + &perm, "{\t \t \t \t \t }"); + + t_assert(doc.as.object.len == 0); + + return 0; +} + i32 test_json_multiline() { Arena perm = Arena_new(128e3); |
