tinybits 0.9.0 → 0.9.1
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 +4 -4
- data/ext/tinybits/tinybits_ext.c +47 -1
- data/lib/tinybits/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b196201c8c0379a74ae23e62d10022ed7deae323cba4d9281025941b1a8b5d77
|
|
4
|
+
data.tar.gz: 47acdb4977e7cbf79a48c74ea85a8917be38357f886a88ab33f483b8d396c34d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad078aa3bf66b321bf208d679393ba9856a553d321bacc8d3b62728feca300bf42fa5e497ecfd87c6b2bb80050dfbf61d70b8edd2378e1650095b4314303ccbb
|
|
7
|
+
data.tar.gz: 9b441821ee20ddaa0fbbecced7de678bf4dd1e0f7874c3174a73d843bee7b0ed720a37aa08a622117f24ab3a03acd15d448b90e325b1da9f662825ac85128c94
|
data/ext/tinybits/tinybits_ext.c
CHANGED
|
@@ -326,23 +326,59 @@ static VALUE rb_reset(VALUE self){
|
|
|
326
326
|
return self;
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
#define KEY_CACHE_CAPA 63
|
|
330
|
+
#define KEY_CACHE_MAX_MISSES 1024
|
|
331
|
+
|
|
329
332
|
// Unpacker structure
|
|
330
333
|
typedef struct {
|
|
331
334
|
tiny_bits_unpacker* unpacker;
|
|
332
335
|
int symbolize_keys; // map keys that are strings come back as symbols
|
|
333
336
|
VALUE strings; // Ruby strings by dedupe id, dictionary strings first
|
|
334
337
|
VALUE buffer; // the buffer set by #buffer=, kept alive while popping
|
|
338
|
+
int keys_count;
|
|
339
|
+
int keys_misses; // misses while the cache is full
|
|
340
|
+
VALUE keys[KEY_CACHE_CAPA]; // interned map keys sorted by (length, bytes), kept across unpacks
|
|
335
341
|
} UnpackerData;
|
|
336
342
|
|
|
337
343
|
static void unpacker_mark(void* data) {
|
|
338
344
|
UnpackerData* unpacker_data = (UnpackerData*)data;
|
|
339
345
|
rb_gc_mark_movable(unpacker_data->strings);
|
|
340
346
|
rb_gc_mark(unpacker_data->buffer); // pinned, the C unpacker points into it (embedded strings move)
|
|
347
|
+
for (int i = 0; i < unpacker_data->keys_count; i++) rb_gc_mark_movable(unpacker_data->keys[i]);
|
|
341
348
|
}
|
|
342
349
|
|
|
343
350
|
static void unpacker_compact(void* data) {
|
|
344
351
|
UnpackerData* unpacker_data = (UnpackerData*)data;
|
|
345
352
|
unpacker_data->strings = rb_gc_location(unpacker_data->strings);
|
|
353
|
+
for (int i = 0; i < unpacker_data->keys_count; i++) unpacker_data->keys[i] = rb_gc_location(unpacker_data->keys[i]);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Map keys repeat across unpacks, a small sorted cache saves the global fstring table lookup
|
|
357
|
+
// (same approach as the json gem's name cache, but persistent; when full, misses are not
|
|
358
|
+
// inserted, and after KEY_CACHE_MAX_MISSES of them it restarts empty to follow new keys)
|
|
359
|
+
static inline VALUE key_cache_fetch(UnpackerData* unpacker_data, const char* str, long length) {
|
|
360
|
+
if (length < 2 || length > 128) // same range as string dedupe
|
|
361
|
+
return rb_enc_interned_str(str, length, rb_utf8_encoding());
|
|
362
|
+
VALUE* keys = unpacker_data->keys;
|
|
363
|
+
int low = 0, high = unpacker_data->keys_count - 1;
|
|
364
|
+
while (low <= high) {
|
|
365
|
+
int mid = (low + high) >> 1;
|
|
366
|
+
long cmp = length - RSTRING_LEN(keys[mid]);
|
|
367
|
+
if (cmp == 0) cmp = memcmp(str, RSTRING_PTR(keys[mid]), length);
|
|
368
|
+
if (cmp == 0) return keys[mid];
|
|
369
|
+
if (cmp > 0) low = mid + 1; else high = mid - 1;
|
|
370
|
+
}
|
|
371
|
+
VALUE key = rb_enc_interned_str(str, length, rb_utf8_encoding());
|
|
372
|
+
if (unpacker_data->keys_count == KEY_CACHE_CAPA) {
|
|
373
|
+
if (++unpacker_data->keys_misses < KEY_CACHE_MAX_MISSES) return key;
|
|
374
|
+
unpacker_data->keys_misses = 0;
|
|
375
|
+
unpacker_data->keys_count = 0;
|
|
376
|
+
low = 0;
|
|
377
|
+
}
|
|
378
|
+
MEMMOVE(&keys[low + 1], &keys[low], VALUE, unpacker_data->keys_count - low);
|
|
379
|
+
keys[low] = key;
|
|
380
|
+
unpacker_data->keys_count++;
|
|
381
|
+
return key;
|
|
346
382
|
}
|
|
347
383
|
|
|
348
384
|
static void unpacker_free(void* data) {
|
|
@@ -369,6 +405,8 @@ static VALUE rb_unpacker_alloc(VALUE klass) {
|
|
|
369
405
|
unpacker_data->symbolize_keys = 0;
|
|
370
406
|
unpacker_data->strings = Qnil;
|
|
371
407
|
unpacker_data->buffer = Qnil;
|
|
408
|
+
unpacker_data->keys_count = 0;
|
|
409
|
+
unpacker_data->keys_misses = 0;
|
|
372
410
|
return TypedData_Wrap_Struct(klass, &unpacker_data_type, unpacker_data);
|
|
373
411
|
}
|
|
374
412
|
|
|
@@ -421,7 +459,7 @@ static inline VALUE rb_unpack_str(VALUE strings, UnpackerData* unpacker_data, ti
|
|
|
421
459
|
else if(id <= 0){
|
|
422
460
|
VALUE str;
|
|
423
461
|
if(interned > 0){
|
|
424
|
-
str =
|
|
462
|
+
str = key_cache_fetch(unpacker_data, value.str_blob_val.data, value.str_blob_val.length);
|
|
425
463
|
} else {
|
|
426
464
|
str = rb_utf8_str_new(value.str_blob_val.data, value.str_blob_val.length);
|
|
427
465
|
rb_obj_freeze(str);
|
|
@@ -464,6 +502,14 @@ static VALUE unpack_ruby_object(VALUE strings, UnpackerData* unpacker_data, size
|
|
|
464
502
|
case TINY_BITS_FALSE:
|
|
465
503
|
return Qfalse;
|
|
466
504
|
case TINY_BITS_ARRAY: {
|
|
505
|
+
if (value.length <= 16) { // small arrays are created in one call
|
|
506
|
+
VALUE items[16];
|
|
507
|
+
for (size_t i = 0; i < value.length; i++) {
|
|
508
|
+
items[i] = unpack_ruby_object(strings, unpacker_data, 0);
|
|
509
|
+
if (items[i] == Qundef) return Qundef; // Error
|
|
510
|
+
}
|
|
511
|
+
return rb_ary_new_from_values(value.length, items);
|
|
512
|
+
}
|
|
467
513
|
VALUE arr = rb_ary_new_capa(value.length);
|
|
468
514
|
for (size_t i = 0; i < value.length; i++) {
|
|
469
515
|
VALUE element = unpack_ruby_object(strings, unpacker_data, 0);
|
data/lib/tinybits/version.rb
CHANGED