yeptris 0.1.13.1 → 0.1.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17b1311c9327a9986b7bb89f3731fd40ca040c49336fdb97596951fbf87ed1c0
4
- data.tar.gz: 6c1b2f707d15d469963ffc0ee030fc90287a5e01954d7431996f24765bddf9e3
3
+ metadata.gz: dfe67fff242e1aeaa61b4faed0633a90f4b2de0c70b630e733ae557a467cae2e
4
+ data.tar.gz: e8755921e9b5b8fe78c5db36a24727fee8d61e4742d56aa70ca939e13091034c
5
5
  SHA512:
6
- metadata.gz: b04ca9a9d899c00207d8cf09ab1d1aa071c1cc63f2adab17cf87453d0a0646fc84c9df9f3e80f245bbc977a32e67feb7f25e2b562293a59adc102764b80507dc
7
- data.tar.gz: 25c9033573e619dc69924f4ae15c102b650efdbf7d39c1dcc4d3d73243d1d0ac3037750c7b28b5e6d28562175296fd4b125e39253f4ab9c9ed799529d7fcb4ee
6
+ metadata.gz: 1f0443a1150f595e94eb6cf59afca9fd182a2ea269f2ca3a784260efb5d3cb1174325f48d056b06edaf50f2b30d4f90f641c90beebe456d0a862918b9edbdfe0
7
+ data.tar.gz: 0daf1afec8df455dd2bf72353b694f54003f6c3cbfbf1aa4dbf571c84083029362e0abeee5318f3fc22713199b9221650ac8bab1ca1705e39a0bab3a8f7d8467
data/README.adoc CHANGED
@@ -72,9 +72,9 @@ fastest first:
72
72
  +
73
73
  ....
74
74
  CI referee (gated at 1.05 on both platforms, order-alternating
75
- interleave, N=200):
76
- ubuntu (x86_64, gc=disable) mean 0.961x vs JSON.parse
77
- macos (arm64, gc=none) mean 0.718x (head-to-head 173/200)
75
+ interleave, N=200; prefix-hashed token cache):
76
+ ubuntu (x86_64) mean 0.745x vs JSON.parse, 200/200 head-to-head
77
+ macos (arm64) mean 0.759x vs JSON.parse, 169/200 head-to-head
78
78
  ....
79
79
 
80
80
  The parse-window GC strategy is per-arch and runtime-switchable:
@@ -35,7 +35,11 @@ end
35
35
  abort "libyeptris not found (set YEPTRIS_LIB_PATH)" unless libdir
36
36
 
37
37
  $LIBPATH << libdir
38
- $LDFLAGS << " -Wl,-rpath,#{libdir}" if RUBY_PLATFORM =~ /linux|darwin/
38
+ # Platform gems bake a RELATIVE rpath so the bundle finds the
39
+ # vendored libyeptris next to itself; absolute build-time paths
40
+ # would dangle on user machines.
41
+ rpath = ENV["YEPTRIS_RPATH"] || libdir
42
+ $LDFLAGS << " -Wl,-rpath,#{rpath}" if RUBY_PLATFORM =~ /linux|darwin/
39
43
  have_library("yeptris", "yep_json_string") or abort "yep_json_string not exported — rebuild libyeptris"
40
44
  have_library("yeptris", "yeptris_visit_json") or abort "yeptris_visit_json missing"
41
45
 
@@ -5,6 +5,7 @@
5
5
  #include <stdlib.h>
6
6
  #include <string.h>
7
7
  #include "parse/scalars.h"
8
+ #include <yeptris/visit.h>
8
9
  #include "scan/json.h"
9
10
 
10
11
  #define YEP_JR_MAX 1000
@@ -29,6 +30,7 @@ typedef struct {
29
30
  } jr;
30
31
 
31
32
  static VALUE jr_value(jr* j);
33
+ static VALUE jr_object_body(jr* j, VALUE h_pre);
32
34
 
33
35
  static void jr_ws(jr* j) {
34
36
  while (j->i < j->len) {
@@ -38,15 +40,33 @@ static void jr_ws(jr* j) {
38
40
  }
39
41
  }
40
42
 
43
+ /* Token-cache knob (TODO.restructure/37): off = fresh strings
44
+ * everywhere (JSON.parse's shape: every key pays its own aset
45
+ * hashing); on = interned keys/tokens (shared frozen VALUEs
46
+ * memoize their hash). The CI referee A/Bs the net sign per arch. */
47
+ enum { YEP_CACHE_ON = 0, YEP_CACHE_OFF = 1 };
48
+ static int yep_cache_mode = YEP_CACHE_ON;
49
+
50
+ /* Allocation-shape knob (TODO.restructure/39): pre = capa(8) up
51
+ * front (forces heap buffers even for tiny arrays); natural =
52
+ * rb_ary_new() so 3-element arrays stay EMBEDDED in the RVALUE, and
53
+ * bulk-path hashes get exact capacity from the counted pairs. */
54
+ enum { YEP_SHAPE_PRE = 0, YEP_SHAPE_NATURAL = 1 };
55
+ static int yep_shape_mode = YEP_SHAPE_PRE;
56
+
41
57
  static uint64_t jr_hash(const char* sp, long sl) {
42
- /* FNV-1a 64 cheap, good enough for short keys */
43
- uint64_t h = 14695981039346656037ull;
44
- for (long i = 0; i < sl; i++) {
45
- h ^= (unsigned char)sp[i];
46
- h *= 1099511628211ull;
47
- }
48
- h ^= (uint64_t)sl;
49
- return h;
58
+ /* 8-byte-prefix key (the leptris nametab trick): one safe load
59
+ * of min(8, len) bytes + a multiply mix. Replaced byte-wise FNV
60
+ * (~15-20ns per token on the cached path, ~15k tokens per
61
+ * reference-corpus parse - the x86 materialization cost the
62
+ * decomposition isolated, TODO.restructure/37/38). */
63
+ uint64_t k = 0;
64
+ uint64_t take = (uint64_t)sl < 8 ? (uint64_t)sl : 8;
65
+ memcpy(&k, sp, (size_t)take);
66
+ k ^= (uint64_t)sl * 0x9E3779B97F4A7C15ull;
67
+ k *= 0xC2B2AE3D27D4EB4Full;
68
+ k ^= k >> 29;
69
+ return k;
50
70
  }
51
71
 
52
72
  static VALUE jr_cached(jr* j, const char* sp, long sl) {
@@ -86,7 +106,7 @@ static VALUE jr_str(jr* j, int as_key) {
86
106
  sp = j->p + start + 1;
87
107
  sl = (long)(close - start - 1);
88
108
  }
89
- if (as_key || sl <= 24) return jr_cached(j, sp, sl);
109
+ if (yep_cache_mode == YEP_CACHE_ON && (as_key || sl <= 24)) return jr_cached(j, sp, sl);
90
110
  return rb_enc_str_new(sp, sl, j->enc);
91
111
  }
92
112
 
@@ -128,9 +148,20 @@ static int yep_ins_mode = YEP_INS_BULK;
128
148
 
129
149
  static VALUE jr_object(jr* j) {
130
150
  j->i++; j->depth++;
131
- VALUE h = rb_hash_new_capa(8);
151
+ if (yep_shape_mode == YEP_SHAPE_PRE) {
152
+ VALUE h = rb_hash_new_capa(8);
153
+ jr_ws(j);
154
+ if (j->i < j->len && j->p[j->i] == '}') { j->i++; j->depth--; return h; }
155
+ return jr_object_body(j, h);
156
+ }
132
157
  jr_ws(j);
133
- if (j->i < j->len && j->p[j->i] == '}') { j->i++; j->depth--; return h; }
158
+ if (j->i < j->len && j->p[j->i] == '}') { j->i++; j->depth--; return rb_hash_new(); }
159
+ return jr_object_body(j, Qundef); /* created post-loop with exact capa */
160
+
161
+ /* NOTREACHED */
162
+ }
163
+
164
+ static VALUE jr_object_body(jr* j, VALUE h_pre) {
134
165
  VALUE pairs[64];
135
166
  VALUE* pv = pairs;
136
167
  size_t pcap = 64, pn = 0, heap_cap = 0;
@@ -145,7 +176,7 @@ static VALUE jr_object(jr* j) {
145
176
  VALUE val = jr_value(j);
146
177
  if (j->err) goto out;
147
178
  if (yep_ins_mode == YEP_INS_ASET) {
148
- rb_hash_aset(h, key, val);
179
+ rb_hash_aset(h_pre == Qundef ? (h_pre = rb_hash_new_capa(8)) : h_pre, key, val);
149
180
  } else {
150
181
  if (pn + 2 > pcap) {
151
182
  size_t ncap = pcap * 2;
@@ -165,18 +196,23 @@ static VALUE jr_object(jr* j) {
165
196
  j->err = -2; goto out;
166
197
  }
167
198
  if (yep_ins_mode == YEP_INS_BULK) {
199
+ VALUE h = (h_pre == Qundef) ? rb_hash_new_capa((long)(pn / 2)) : h_pre;
168
200
  rb_hash_bulk_insert((long)pn, (const VALUE*)pv, h);
201
+ if (pv != pairs) { free(pv); }
202
+ j->depth--;
203
+ return h;
169
204
  }
205
+ if (h_pre == Qundef) { h_pre = rb_hash_new_capa(8); }
170
206
  out:
171
207
  if (pv != pairs) { free(pv); (void)heap_cap; }
172
208
  if (j->err) return Qnil;
173
209
  j->depth--;
174
- return h;
210
+ return h_pre;
175
211
  }
176
212
 
177
213
  static VALUE jr_array(jr* j) {
178
214
  j->i++; j->depth++;
179
- VALUE a = rb_ary_new_capa(8);
215
+ VALUE a = (yep_shape_mode == YEP_SHAPE_NATURAL) ? rb_ary_new() : rb_ary_new_capa(8);
180
216
  jr_ws(j);
181
217
  if (j->i < j->len && j->p[j->i] == ']') { j->i++; j->depth--; return a; }
182
218
  for (;;) {
@@ -244,6 +280,14 @@ static int yep_gc_mode = YEP_GC_DEFAULT;
244
280
 
245
281
  int yep_rb_gc_mode(void) { return yep_gc_mode; }
246
282
  int yep_rb_ins_mode(void) { return yep_ins_mode; }
283
+ int yep_rb_cache_mode(void) { return yep_cache_mode; }
284
+ int yep_rb_shape_mode(void) { return yep_shape_mode; }
285
+ void yep_rb_set_shape_mode(int mode) {
286
+ if (mode == YEP_SHAPE_PRE || mode == YEP_SHAPE_NATURAL) yep_shape_mode = mode;
287
+ }
288
+ void yep_rb_set_cache_mode(int mode) {
289
+ if (mode == YEP_CACHE_ON || mode == YEP_CACHE_OFF) yep_cache_mode = mode;
290
+ }
247
291
  void yep_rb_set_ins_mode(int mode) {
248
292
  if (mode == YEP_INS_BULK || mode == YEP_INS_ASET) yep_ins_mode = mode;
249
293
  }
@@ -253,6 +297,21 @@ void yep_rb_set_gc_mode(int mode) {
253
297
  }
254
298
  }
255
299
 
300
+ /* Pure grammar walk, no materialization (TODO.restructure/37): the
301
+ * same scan kernels through the null vtable. Decomposes scan vs
302
+ * materialize cost. Returns seconds for n iterations. */
303
+ #include <time.h>
304
+ double yep_rb_scan_time(const char* p, size_t len, int n) {
305
+ static const YeptrisVisitVTable none = {0};
306
+ struct timespec t0, t1;
307
+ clock_gettime(CLOCK_MONOTONIC, &t0);
308
+ for (int i = 0; i < n; i++) {
309
+ (void)yeptris_visit_json(p, len, &none, NULL);
310
+ }
311
+ clock_gettime(CLOCK_MONOTONIC, &t1);
312
+ return (double)(t1.tv_sec - t0.tv_sec) + (double)(t1.tv_nsec - t0.tv_nsec) / 1e9;
313
+ }
314
+
256
315
  VALUE yep_rb_parse_json(const char* p, size_t len) {
257
316
  jr j;
258
317
  memset(&j, 0, sizeof(j));
@@ -329,6 +329,11 @@ extern int yep_rb_gc_mode(void);
329
329
  extern void yep_rb_set_gc_mode(int mode);
330
330
  extern int yep_rb_ins_mode(void);
331
331
  extern void yep_rb_set_ins_mode(int mode);
332
+ extern int yep_rb_cache_mode(void);
333
+ extern void yep_rb_set_cache_mode(int mode);
334
+ extern int yep_rb_shape_mode(void);
335
+ extern void yep_rb_set_shape_mode(int mode);
336
+ extern double yep_rb_scan_time(const char* p, size_t len, int n);
332
337
 
333
338
  static VALUE native_gc_mode(VALUE self) {
334
339
  (void)self;
@@ -354,6 +359,44 @@ static VALUE native_ins_mode_set(VALUE self, VALUE mode) {
354
359
  return mode;
355
360
  }
356
361
 
362
+ static VALUE native_cache_mode(VALUE self) {
363
+ (void)self;
364
+ return yep_rb_cache_mode() == 1 ? ID2SYM(rb_intern("off")) : ID2SYM(rb_intern("on"));
365
+ }
366
+
367
+ static VALUE native_cache_mode_set(VALUE self, VALUE mode) {
368
+ (void)self;
369
+ Check_Type(mode, T_SYMBOL);
370
+ ID id = rb_sym2id(mode);
371
+ if (id == rb_intern("on")) yep_rb_set_cache_mode(0);
372
+ else if (id == rb_intern("off")) yep_rb_set_cache_mode(1);
373
+ else rb_raise(rb_eArgError, "cache_mode must be :on or :off");
374
+ return mode;
375
+ }
376
+
377
+ static VALUE native_scan_time(VALUE self, VALUE input, VALUE count) {
378
+ (void)self;
379
+ StringValue(input);
380
+ int n = NUM2INT(count);
381
+ double secs = yep_rb_scan_time(RSTRING_PTR(input), (size_t)RSTRING_LEN(input), n);
382
+ return DBL2NUM(secs / (double)n);
383
+ }
384
+
385
+ static VALUE native_shape_mode(VALUE self) {
386
+ (void)self;
387
+ return yep_rb_shape_mode() == 1 ? ID2SYM(rb_intern("natural")) : ID2SYM(rb_intern("pre"));
388
+ }
389
+
390
+ static VALUE native_shape_mode_set(VALUE self, VALUE mode) {
391
+ (void)self;
392
+ Check_Type(mode, T_SYMBOL);
393
+ ID id = rb_sym2id(mode);
394
+ if (id == rb_intern("pre")) yep_rb_set_shape_mode(0);
395
+ else if (id == rb_intern("natural")) yep_rb_set_shape_mode(1);
396
+ else rb_raise(rb_eArgError, "shape_mode must be :pre or :natural");
397
+ return mode;
398
+ }
399
+
357
400
  static VALUE native_gc_mode_set(VALUE self, VALUE mode) {
358
401
  (void)self;
359
402
  Check_Type(mode, T_SYMBOL);
@@ -376,6 +419,11 @@ RUBY_FUNC_EXPORTED void Init_native(void) {
376
419
  rb_define_singleton_method(mNat, "gc_mode=", native_gc_mode_set, 1);
377
420
  rb_define_singleton_method(mNat, "ins_mode", native_ins_mode, 0);
378
421
  rb_define_singleton_method(mNat, "ins_mode=", native_ins_mode_set, 1);
422
+ rb_define_singleton_method(mNat, "cache_mode", native_cache_mode, 0);
423
+ rb_define_singleton_method(mNat, "cache_mode=", native_cache_mode_set, 1);
424
+ rb_define_singleton_method(mNat, "shape_mode", native_shape_mode, 0);
425
+ rb_define_singleton_method(mNat, "shape_mode=", native_shape_mode_set, 1);
426
+ rb_define_singleton_method(mNat, "scan_time", native_scan_time, 2);
379
427
  rb_define_const(mNat, "AVAILABLE", Qtrue);
380
428
  const char* env = getenv("YEPTRIS_NATIVE_GC");
381
429
  if (env != NULL && strcmp(env, "none") == 0) yep_rb_set_gc_mode(1);
@@ -383,4 +431,8 @@ RUBY_FUNC_EXPORTED void Init_native(void) {
383
431
  else if (env != NULL && strcmp(env, "disable") == 0) yep_rb_set_gc_mode(0);
384
432
  const char* ins = getenv("YEPTRIS_NATIVE_INSERT");
385
433
  if (ins != NULL && strcmp(ins, "aset") == 0) yep_rb_set_ins_mode(1);
434
+ const char* cache = getenv("YEPTRIS_NATIVE_CACHE");
435
+ if (cache != NULL && strcmp(cache, "off") == 0) yep_rb_set_cache_mode(1);
436
+ const char* shape = getenv("YEPTRIS_NATIVE_SHAPE");
437
+ if (shape != NULL && strcmp(shape, "natural") == 0) yep_rb_set_shape_mode(1);
386
438
  }
data/lib/yeptris.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Yeptris
4
4
  # The gem's version lives in the parent namespace's file — the last
5
5
  # internal require (yeptris/version) retired with it.
6
- VERSION = "0.1.13.1".freeze
6
+ VERSION = "0.1.13.3".freeze
7
7
  # The error hierarchy lives in THIS file (the parent namespace's
8
8
  # own file): nested constants do not trigger a parent-constant
9
9
  # autoload, and the law forbids internal requires — defining the
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yeptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13.1
4
+ version: 0.1.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.