teptris 0.2.47 → 0.2.48

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: eb7e2d637c9a7d99cf511aa513c6b107bf53ae3338057466f52c8b86ed94da79
4
- data.tar.gz: 8bd5fe1d88f86a95ea4b9711bf53ef1bfb41793fe922865928b720e9a57bec49
3
+ metadata.gz: 6c1158decbe9ec09fbae4504a660b409d0d3b082d56c4afc232228b3fe09f6ba
4
+ data.tar.gz: feb5d4823c5b238ca40565048f4445772a02e05148de97ef6f988a5c64d6d1cc
5
5
  SHA512:
6
- metadata.gz: 9ea8e593b8dbdb13bc4e47b0de51f9417ece4947519ef6f6206a8e56efe381f7cabe59a726a22592bf30822e100be15ebcfec9c1288b13ddb86a351799b8949b
7
- data.tar.gz: d2b11be087c63609aa8e993efb4f9fcea8ad879adcc78e47c6ea19193bdd9b23f3e98e8b97f11ac631bbe52ed364b273c09ec301727a284393e75fe6d7aa137a
6
+ metadata.gz: e3bd532ee7bf75cfd4bf214be2960bf369c7cc4ad804362ee2f037ae1d1ad0a569691ba903240fda70066d1cf0d601a78a29e08f9dc8176ddb4f39341164eed4
7
+ data.tar.gz: 9f0a8037043b602f41b4272f30419ecda8e64f05bb40fa8d27cb27c4d543339194098906e5429d3bb53de17e399141afcf88f6c116fb24d8d4f6cc59e3265c31
@@ -89,7 +89,87 @@ static VALUE dt_string(const teptris_datetime *d, int kind) {
89
89
  return rb_enc_str_new(buf, l, rb_utf8_encoding());
90
90
  }
91
91
 
92
- static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
92
+ /* ---- per-load table-key cache ------------------------------------------------
93
+ * TOML keys repeat within a document and across a batch; a fresh String
94
+ * per occurrence costs an allocation and a full Hash re-hash each time.
95
+ * This cache maps key CONTENT to one frozen String, shared everywhere:
96
+ * repeated keys become a single GC-pinned object with its hash already
97
+ * computed. It is document-local (dies with the load call), capped at
98
+ * KC_CAP entries so unique-key corpora pay only a cheap failed probe
99
+ * after the cap, and never touches the VM's global fstring table (the
100
+ * intern-always attempt regressed there — teptris-ruby#122: unique
101
+ * keys make the intern table pure overhead). Strings are frozen so the
102
+ * sharing cannot alias a mutation. NULL cache = fresh strings (the
103
+ * low-volume schema-RAW path). */
104
+
105
+ #define KC_CAP 4096
106
+ #define KC_SLOTS (KC_CAP * 2) /* stays <=50% full: probes stay ~2 */
107
+
108
+ typedef struct {
109
+ VALUE str; /* Qfalse = empty slot */
110
+ } kc_slot;
111
+
112
+ typedef struct {
113
+ VALUE keep; /* Ruby Array pinning every cached String */
114
+ kc_slot *slots;
115
+ size_t used;
116
+ } keycache;
117
+
118
+ static uint64_t kc_hash(const char *p, size_t len)
119
+ {
120
+ uint64_t h = 1469598103934665603ULL;
121
+ for (size_t i = 0; i < len; i++) {
122
+ h ^= (unsigned char)p[i];
123
+ h *= 1099511628211ULL;
124
+ }
125
+ return h;
126
+ }
127
+
128
+ static void kc_init(keycache *kc)
129
+ {
130
+ kc->keep = rb_ary_new_capa(64);
131
+ kc->slots = calloc(KC_SLOTS, sizeof(kc_slot)); /* zero = Qfalse empty */
132
+ if (kc->slots == NULL) {
133
+ /* no cache is a correctness-preserving fallback */
134
+ kc->keep = Qnil;
135
+ return;
136
+ }
137
+ kc->used = 0;
138
+ }
139
+
140
+ static void kc_free(keycache *kc)
141
+ {
142
+ free(kc->slots);
143
+ kc->slots = NULL;
144
+ }
145
+
146
+ static VALUE kc_key(keycache *kc, const char *p, size_t len)
147
+ {
148
+ if (kc == NULL || kc->slots == NULL) {
149
+ return rb_enc_str_new(p, (long)len, rb_utf8_encoding());
150
+ }
151
+ size_t mask = KC_SLOTS - 1;
152
+ size_t slot = (size_t)kc_hash(p, len) & mask;
153
+ while (kc->slots[slot].str != Qfalse) {
154
+ VALUE s = kc->slots[slot].str;
155
+ if ((size_t)RSTRING_LEN(s) == len &&
156
+ memcmp(RSTRING_PTR(s), p, len) == 0) {
157
+ return s; /* hit: one shared object, hash already cached */
158
+ }
159
+ slot = (slot + 1) & mask;
160
+ }
161
+ VALUE fresh = rb_enc_str_new(p, (long)len, rb_utf8_encoding());
162
+ rb_obj_freeze(fresh);
163
+ rb_ary_push(kc->keep, fresh); /* GC pin before it enters the slots */
164
+ if (kc->used < KC_CAP) {
165
+ kc->slots[slot].str = fresh;
166
+ kc->used++;
167
+ }
168
+ return fresh;
169
+ }
170
+
171
+ static VALUE obj_from_node(const teptris_node *n, unsigned flags,
172
+ keycache *kc) {
93
173
  switch (teptris_node_kind(n)) {
94
174
  case TEPTRIS_TABLE: {
95
175
  size_t len = teptris_node_table_length(n);
@@ -97,9 +177,8 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
97
177
  for (size_t i = 0; i < len; i++) {
98
178
  teptris_view key;
99
179
  const teptris_node *v = teptris_node_table_at(n, i, &key);
100
- rb_hash_aset(h,
101
- rb_enc_str_new(key.ptr, (long)key.len, rb_utf8_encoding()),
102
- obj_from_node(v, flags));
180
+ rb_hash_aset(h, kc_key(kc, key.ptr, (size_t)key.len),
181
+ obj_from_node(v, flags, kc));
103
182
  }
104
183
  return h;
105
184
  }
@@ -107,7 +186,7 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
107
186
  size_t len = teptris_node_array_length(n);
108
187
  VALUE a = rb_ary_new_capa((long)len);
109
188
  for (size_t i = 0; i < len; i++)
110
- rb_ary_push(a, obj_from_node(teptris_node_array_at(n, i), flags));
189
+ rb_ary_push(a, obj_from_node(teptris_node_array_at(n, i), flags, kc));
111
190
  return a;
112
191
  }
113
192
  case TEPTRIS_STRING: {
@@ -176,7 +255,10 @@ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
176
255
  if (RTEST(fd)) flags |= FMT_FORBID_DATE;
177
256
  }
178
257
  teptris_document *doc = parse_or_raise(str);
179
- VALUE out = obj_from_node(teptris_document_root(doc), flags);
258
+ keycache kc;
259
+ kc_init(&kc);
260
+ VALUE out = obj_from_node(teptris_document_root(doc), flags, &kc);
261
+ kc_free(&kc);
180
262
  teptris_document_free(doc);
181
263
  return out;
182
264
  }
@@ -526,7 +608,7 @@ static VALUE asm_rows(const teptris_plan *p, const teptris_plan_result *res,
526
608
  case TEPTRIS_PLAN_RAW_RESULT: {
527
609
  const teptris_node *raw = teptris_plan_result_raw_at(res, i);
528
610
  if (raw != NULL) {
529
- val = obj_from_node(raw, 0);
611
+ val = obj_from_node(raw, 0, NULL);
530
612
  }
531
613
  break;
532
614
  }
@@ -780,7 +862,11 @@ static VALUE lazy_dig(int argc, VALUE *argv, VALUE self) {
780
862
  /* full materialization: the eager path, owned */
781
863
  static VALUE lazy_to(VALUE self) {
782
864
  lazy_value *v = lazy_self(self);
783
- return obj_from_node(v->node, 0);
865
+ keycache kc;
866
+ kc_init(&kc);
867
+ VALUE out = obj_from_node(v->node, 0, &kc);
868
+ kc_free(&kc);
869
+ return out;
784
870
  }
785
871
 
786
872
  /* ---------------------------------------------------------------- batch --
@@ -936,11 +1022,14 @@ static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
936
1022
  rb_exc_raise(ex);
937
1023
  }
938
1024
  VALUE out = rb_ary_new_capa(n);
1025
+ keycache kc;
1026
+ kc_init(&kc); /* one cache across the batch: keys repeat across docs */
939
1027
  for (long i = 0; i < n; i++) {
940
1028
  const teptris_node *root = teptris_document_root(s->docs[i]);
941
- rb_ary_store(out, i, obj_from_node(root, flags));
1029
+ rb_ary_store(out, i, obj_from_node(root, flags, &kc));
942
1030
  teptris_document_free(s->docs[i]);
943
1031
  }
1032
+ kc_free(&kc);
944
1033
  batch_scratch_free(s);
945
1034
  return out;
946
1035
  }
@@ -1,3 +1,3 @@
1
1
  module Teptris
2
- VERSION = "0.2.47"
2
+ VERSION = "0.2.48"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teptris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.47
4
+ version: 0.2.48
5
5
  platform: ruby
6
6
  authors:
7
7
  - teptris contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-23 00:00:00.000000000 Z
11
+ date: 2026-09-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A native C extension over libteptris, a C99 TOML 1.1 parser/emitter.
14
14
  API shape mirrors the tomlib gem.