teptris 0.2.46-x86_64-linux → 0.2.48-x86_64-linux

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: e35572a61b9713a85dc4ae6c77aac499169c453834db2a13314a7cb3c2d82c33
4
- data.tar.gz: 608e5f4f689a2be7b6e346eb4590d6e6bbed0386629a8f64b2946ade312d8e62
3
+ metadata.gz: 27a5c37cf9ee166e9f73c58af86443cf4637e88e24f214b9977033a94b71eea2
4
+ data.tar.gz: b9004d02a920676747305a3e3b8decc87a13ad5048fcbebacd2268cfbebe8c14
5
5
  SHA512:
6
- metadata.gz: d68c4717404352e06217880f09574a8e6e4ce38a4d172f6f91897a084d5ca403104bc531224396822a3d640768f429bbe65dc4752cde4d068fd6fc699cc68319
7
- data.tar.gz: ed6085a0bd55e8b50fa822fbac9fbd395b31cd3de08b66e175575e4e1c4c815d15be2650fbfcc61d986a824b3552ed382d4b218cb37e46790adfa6d21a6acee7
6
+ metadata.gz: 36cb58d33e24bdf452cde88457894aa4024119cf303c9d8d78712d6b825d17b9a3529582bbec20292ccb933de22872281bf106f7dc382bc889d5508b0b175ab6
7
+ data.tar.gz: e7067ffff2dd503af6d57bde51b716023677ab0de2ecd3f761d52a2047714e2400fe6c2241938f471e14b4ba23b12a462148d2b481c69c6ee60ee88236554aef
@@ -21,18 +21,25 @@ static int64_t days_from_civil(int32_t y, uint8_t m, uint8_t d) {
21
21
  }
22
22
 
23
23
  /* exact (no double) Time materialization: nsec stays nsec. Offset
24
- * datetimes go through timespec+fixed-offset; local datetimes through
25
- * Time.local with Rational seconds (rb_time_timespec_new's INT_MAX
26
- * treats the timespec as an absolute instant, not wall-clock local). */
24
+ * datetimes carry a fixed utc offset via rb_time_num_new; local
25
+ * datetimes go through Time.local with Rational seconds (both treat
26
+ * the epoch value as wall-clock for locals / instant+offset for
27
+ * fixed zones). No struct timespec crosses the Ruby boundary:
28
+ * armhf glibc rubies build with _TIME_BITS=64 (16-byte timespec)
29
+ * while an ext compiled without it lays out 8 bytes — the struct
30
+ * APIs then read (load) or write (dump) past each side's layout. */
27
31
  static VALUE time_from_dt(const teptris_datetime *d, bool has_offset) {
28
32
  if (has_offset) {
29
33
  int64_t days = days_from_civil(d->year, d->month, d->day);
30
34
  int64_t secs =
31
35
  days * 86400 + d->hour * 3600 + d->minute * 60 + d->second;
32
- struct timespec ts;
33
- ts.tv_sec = (time_t)(secs - d->offset_seconds);
34
- ts.tv_nsec = (long)d->nanosecond;
35
- return rb_time_timespec_new(&ts, d->offset_seconds);
36
+ VALUE num = LL2NUM(secs - d->offset_seconds);
37
+ if (d->nanosecond != 0) {
38
+ num = rb_funcall(num, '+', 1,
39
+ rb_Rational(LL2NUM((int64_t)d->nanosecond),
40
+ LL2NUM(1000000000)));
41
+ }
42
+ return rb_time_num_new(num, INT2FIX(d->offset_seconds));
36
43
  }
37
44
  VALUE args[6] = {INT2FIX(d->year), INT2FIX(d->month), INT2FIX(d->day),
38
45
  INT2FIX(d->hour), INT2FIX(d->minute),
@@ -82,7 +89,87 @@ static VALUE dt_string(const teptris_datetime *d, int kind) {
82
89
  return rb_enc_str_new(buf, l, rb_utf8_encoding());
83
90
  }
84
91
 
85
- 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) {
86
173
  switch (teptris_node_kind(n)) {
87
174
  case TEPTRIS_TABLE: {
88
175
  size_t len = teptris_node_table_length(n);
@@ -90,9 +177,8 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
90
177
  for (size_t i = 0; i < len; i++) {
91
178
  teptris_view key;
92
179
  const teptris_node *v = teptris_node_table_at(n, i, &key);
93
- rb_hash_aset(h,
94
- rb_enc_str_new(key.ptr, (long)key.len, rb_utf8_encoding()),
95
- 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));
96
182
  }
97
183
  return h;
98
184
  }
@@ -100,7 +186,7 @@ static VALUE obj_from_node(const teptris_node *n, unsigned flags) {
100
186
  size_t len = teptris_node_array_length(n);
101
187
  VALUE a = rb_ary_new_capa((long)len);
102
188
  for (size_t i = 0; i < len; i++)
103
- 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));
104
190
  return a;
105
191
  }
106
192
  case TEPTRIS_STRING: {
@@ -169,7 +255,10 @@ static VALUE ext_load(int argc, VALUE *argv, VALUE self) {
169
255
  if (RTEST(fd)) flags |= FMT_FORBID_DATE;
170
256
  }
171
257
  teptris_document *doc = parse_or_raise(str);
172
- 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);
173
262
  teptris_document_free(doc);
174
263
  return out;
175
264
  }
@@ -180,7 +269,7 @@ static VALUE ext_version(VALUE self) {
180
269
 
181
270
  /* ------------------------------------------------------------------ dump */
182
271
 
183
- static ID id_utc_offset, id_to_s;
272
+ static ID id_utc_offset, id_to_s, id_to_i, id_nsec;
184
273
 
185
274
  static void dump_check(teptris_status st) {
186
275
  if (st == TEPTRIS_OK) return;
@@ -206,12 +295,12 @@ static void civil_from_days(int64_t z, int32_t *y, uint8_t *m, uint8_t *d) {
206
295
  *d = (uint8_t)dd;
207
296
  }
208
297
 
209
- /* two crossings per Time: timespec + utc_offset; the civil fields come
210
- * from integer math (epoch + offset -> y/m/d h:m:s + tv_nsec) */
298
+ /* the instant and its subsecond part come back as Integers (to_i
299
+ * floors, nsec stays in [0, 1e9) — same split the timespec gave);
300
+ * the civil fields are integer math on epoch + offset */
211
301
  static void fill_dt_from_time(VALUE v, teptris_datetime *dt) {
212
- struct timespec ts = rb_time_timespec(v);
213
302
  dt->offset_seconds = NUM2INT(rb_funcall(v, id_utc_offset, 0));
214
- int64_t secs = (int64_t)ts.tv_sec + dt->offset_seconds;
303
+ int64_t secs = NUM2LL(rb_funcall(v, id_to_i, 0)) + dt->offset_seconds;
215
304
  int64_t days = secs / 86400;
216
305
  int32_t rem = (int32_t)(secs % 86400);
217
306
  if (rem < 0) { /* floor division: keep rem in [0, 86400) */
@@ -222,7 +311,7 @@ static void fill_dt_from_time(VALUE v, teptris_datetime *dt) {
222
311
  dt->hour = (uint8_t)(rem / 3600);
223
312
  dt->minute = (uint8_t)((rem / 60) % 60);
224
313
  dt->second = (uint8_t)(rem % 60);
225
- dt->nanosecond = (uint32_t)ts.tv_nsec;
314
+ dt->nanosecond = (uint32_t)NUM2LL(rb_funcall(v, id_nsec, 0));
226
315
  }
227
316
 
228
317
  static void dump_scalar(teptris_builder *b, const char *key, size_t klen,
@@ -519,7 +608,7 @@ static VALUE asm_rows(const teptris_plan *p, const teptris_plan_result *res,
519
608
  case TEPTRIS_PLAN_RAW_RESULT: {
520
609
  const teptris_node *raw = teptris_plan_result_raw_at(res, i);
521
610
  if (raw != NULL) {
522
- val = obj_from_node(raw, 0);
611
+ val = obj_from_node(raw, 0, NULL);
523
612
  }
524
613
  break;
525
614
  }
@@ -773,7 +862,11 @@ static VALUE lazy_dig(int argc, VALUE *argv, VALUE self) {
773
862
  /* full materialization: the eager path, owned */
774
863
  static VALUE lazy_to(VALUE self) {
775
864
  lazy_value *v = lazy_self(self);
776
- 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;
777
870
  }
778
871
 
779
872
  /* ---------------------------------------------------------------- batch --
@@ -929,11 +1022,14 @@ static VALUE ext_load_batch(int argc, VALUE *argv, VALUE self) {
929
1022
  rb_exc_raise(ex);
930
1023
  }
931
1024
  VALUE out = rb_ary_new_capa(n);
1025
+ keycache kc;
1026
+ kc_init(&kc); /* one cache across the batch: keys repeat across docs */
932
1027
  for (long i = 0; i < n; i++) {
933
1028
  const teptris_node *root = teptris_document_root(s->docs[i]);
934
- rb_ary_store(out, i, obj_from_node(root, flags));
1029
+ rb_ary_store(out, i, obj_from_node(root, flags, &kc));
935
1030
  teptris_document_free(s->docs[i]);
936
1031
  }
1032
+ kc_free(&kc);
937
1033
  batch_scratch_free(s);
938
1034
  return out;
939
1035
  }
@@ -967,4 +1063,6 @@ void Init_teptris_ext(void) {
967
1063
  cDate = rb_const_get(rb_cObject, rb_intern("Date"));
968
1064
  id_utc_offset = rb_intern("utc_offset");
969
1065
  id_to_s = rb_intern("to_s");
1066
+ id_to_i = rb_intern("to_i");
1067
+ id_nsec = rb_intern("nsec");
970
1068
  }
Binary file
Binary file
Binary file
@@ -1,3 +1,3 @@
1
1
  module Teptris
2
- VERSION = "0.2.46"
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.46
4
+ version: 0.2.48
5
5
  platform: x86_64-linux
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.