MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve string_builder_t performance

+276 -113
+133 -110
src/ant.c
··· 4669 4669 sb->is_dynamic = false; 4670 4670 } 4671 4671 4672 + static void string_builder_dispose(string_builder_t *sb) { 4673 + if (sb->is_dynamic && sb->buffer) free(sb->buffer); 4674 + sb->buffer = NULL; 4675 + sb->capacity = 0; 4676 + sb->size = 0; 4677 + sb->is_dynamic = false; 4678 + } 4679 + 4672 4680 static bool string_builder_append(string_builder_t *sb, const char *data, size_t len) { 4673 - if (sb->size + len > sb->capacity) { 4674 - size_t new_capacity = sb->capacity ? sb->capacity * 2 : 256; 4675 - while (new_capacity < sb->size + len) new_capacity *= 2; 4681 + if (len > SIZE_MAX - sb->size) return false; 4682 + size_t needed = sb->size + len; 4683 + 4684 + if (needed > sb->capacity) { 4685 + size_t new_capacity = sb->capacity 4686 + ? sb->capacity : 256; 4687 + 4688 + while (new_capacity < needed) { 4689 + if (new_capacity > SIZE_MAX / 2) { new_capacity = needed; break; } 4690 + new_capacity *= 2; 4691 + } 4676 4692 4677 - char *new_buffer = (char *)ant_calloc(new_capacity); 4693 + char *new_buffer = sb->is_dynamic 4694 + ? (char *)ant_realloc(sb->buffer, new_capacity) 4695 + : (char *)ant_calloc(new_capacity); 4696 + 4678 4697 if (!new_buffer) return false; 4679 - 4680 - if (sb->size > 0) memcpy(new_buffer, sb->buffer, sb->size); 4681 - if (sb->is_dynamic) free(sb->buffer); 4698 + if (!sb->is_dynamic && sb->size > 0) memcpy(new_buffer, sb->buffer, sb->size); 4682 4699 4683 4700 sb->buffer = new_buffer; 4684 4701 sb->capacity = new_capacity; ··· 4695 4712 4696 4713 static ant_value_t string_builder_finalize(ant_t *js, string_builder_t *sb) { 4697 4714 ant_value_t result = js_mkstr(js, sb->buffer, sb->size); 4698 - if (sb->is_dynamic && sb->buffer) free(sb->buffer); 4715 + string_builder_dispose(sb); 4699 4716 return result; 4700 4717 } 4701 4718 4719 + static bool string_builder_append_value( 4720 + ant_t *js, string_builder_t *sb, ant_value_t value, ant_value_t *err 4721 + ) { 4722 + ant_value_t s = js_tostring_val(js, value); 4723 + if (is_err(s)) { 4724 + if (err) *err = s; 4725 + return false; 4726 + } 4727 + 4728 + ant_offset_t slen = 0; 4729 + ant_offset_t soff = vstr(js, s, &slen); 4730 + 4731 + if (string_builder_append(sb, 4732 + (const char *)(uintptr_t)soff, 4733 + (size_t)slen) 4734 + ) return true; 4735 + 4736 + if (err) *err = js_mkerr(js, "oom"); 4737 + return false; 4738 + } 4739 + 4740 + static bool string_template_append_value( 4741 + ant_t *js, string_builder_t *sb, 4742 + ant_value_t value, ant_value_t *err 4743 + ) { 4744 + switch (vtype(value)) { 4745 + case T_STR: { 4746 + ant_offset_t len = 0; 4747 + ant_offset_t off = vstr(js, value, &len); 4748 + return string_builder_append(sb, (const char *)(uintptr_t)off, (size_t)len); 4749 + } 4750 + 4751 + case T_NUM: { 4752 + char buf[32]; 4753 + size_t len = strnum(value, buf, sizeof(buf)); 4754 + return string_builder_append(sb, buf, len); 4755 + } 4756 + 4757 + case T_BOOL: { 4758 + if (vdata(value)) return string_builder_append(sb, "true", 4); 4759 + return string_builder_append(sb, "false", 5); 4760 + } 4761 + 4762 + case T_NULL: return string_builder_append(sb, "null", 4); 4763 + case T_UNDEF: return string_builder_append(sb, "undefined", 9); 4764 + default: return string_builder_append_value(js, sb, value, err); 4765 + }} 4766 + 4702 4767 ant_offset_t str_len_fast(ant_t *js, ant_value_t str) { 4703 4768 if (vtype(str) != T_STR) return 0; 4704 4769 if (str_is_heap_rope(str)) return rope_len(str); ··· 4747 4812 if ( 4748 4813 !string_builder_append(&sb, (char *)(uintptr_t)(off1), len1) || 4749 4814 !string_builder_append(&sb, (char *)(uintptr_t)(off2), len2) 4750 - ) return js_mkerr(js, "string concatenation failed"); 4815 + ) { 4816 + string_builder_dispose(&sb); 4817 + return js_mkerr(js, "string concatenation failed"); 4818 + } 4751 4819 4752 4820 return string_builder_finalize(js, &sb); 4753 4821 } ··· 10787 10855 10788 10856 static ant_value_t builtin_string_template(ant_t *js, ant_value_t *args, int nargs) { 10789 10857 ant_value_t str = to_string_val(js, js->this_val); 10858 + 10859 + if (is_err(str)) return str; 10790 10860 if (vtype(str) != T_STR) return js_mkerr(js, "template called on non-string"); 10791 10861 if (nargs < 1 || vtype(args[0]) != T_OBJ) return str; 10792 10862 ··· 10794 10864 ant_offset_t str_len, str_off = vstr(js, str, &str_len); 10795 10865 const char *str_ptr = (char *)(uintptr_t)(str_off); 10796 10866 10797 - size_t result_cap = str_len + 256; 10798 - size_t result_len = 0; 10799 - char *result = (char *)ant_calloc(result_cap); 10800 - if (!result) return js_mkerr(js, "oom"); 10801 - ant_offset_t i = 0; 10867 + string_builder_t sb; 10868 + char static_buf[512]; 10869 + string_builder_init(&sb, static_buf, sizeof(static_buf)); 10802 10870 10803 - #define ENSURE_CAP(need) do { \ 10804 - if (result_len + (need) >= result_cap) { \ 10805 - result_cap = (result_len + (need) + 1) * 2; \ 10806 - char *nr = (char *)ant_realloc(result, result_cap); \ 10807 - if (!nr) return js_mkerr(js, "oom"); \ 10808 - result = nr; \ 10809 - } \ 10810 - } while(0) 10871 + ant_offset_t i = 0; 10872 + ant_offset_t literal_start = 0; 10811 10873 10812 10874 while (i < str_len) { 10813 - if (i < str_len - 3 && str_ptr[i] == '{' && str_ptr[i + 1] == '{') { 10814 - ant_offset_t start = i + 2; 10815 - ant_offset_t end = start; 10816 - while (end < str_len - 1 && !(str_ptr[end] == '}' && str_ptr[end + 1] == '}')) { 10817 - end++; 10818 - } 10819 - if (end < str_len - 1 && str_ptr[end] == '}' && str_ptr[end + 1] == '}') { 10820 - ant_offset_t key_len = end - start; 10821 - ant_offset_t prop_off = lkp(js, data, str_ptr + start, key_len); 10822 - 10823 - if (prop_off != 0) { 10824 - ant_value_t value = propref_load(js, prop_off); 10825 - if (vtype(value) == T_STR) { 10826 - ant_offset_t val_len, val_off = vstr(js, value, &val_len); 10827 - ENSURE_CAP(val_len); 10828 - memcpy(result + result_len, (const void *)(uintptr_t)val_off, val_len); 10829 - result_len += val_len; 10830 - } else if (vtype(value) == T_NUM) { 10831 - char numstr[32]; 10832 - snprintf(numstr, sizeof(numstr), "%g", tod(value)); 10833 - size_t num_len = strlen(numstr); 10834 - ENSURE_CAP(num_len); 10835 - memcpy(result + result_len, numstr, num_len); 10836 - result_len += num_len; 10837 - } else if (vtype(value) == T_BOOL) { 10838 - const char *boolstr = vdata(value) ? "true" : "false"; 10839 - size_t bool_len = strlen(boolstr); 10840 - ENSURE_CAP(bool_len); 10841 - memcpy(result + result_len, boolstr, bool_len); 10842 - result_len += bool_len; 10843 - } 10844 - } 10845 - i = end + 2; 10846 - continue; 10875 + if (i >= str_len - 3) goto next_char; 10876 + if (str_ptr[i] != '{' || str_ptr[i + 1] != '{') goto next_char; 10877 + 10878 + ant_offset_t key_start = i + 2; 10879 + ant_offset_t key_end = key_start; 10880 + 10881 + while ( 10882 + key_end < str_len - 1 && 10883 + !(str_ptr[key_end] == '}' && 10884 + str_ptr[key_end + 1] == '}') 10885 + ) key_end++; 10886 + 10887 + if (key_end >= str_len - 1) goto next_char; 10888 + if (!string_builder_append(&sb, str_ptr + literal_start, (size_t)(i - literal_start))) goto oom; 10889 + 10890 + ant_offset_t prop_off = lkp(js, data, str_ptr + key_start, key_end - key_start); 10891 + if (prop_off != 0) { 10892 + ant_value_t err = js_mkundef(); 10893 + ant_value_t value = propref_load(js, prop_off); 10894 + if (!string_template_append_value(js, &sb, value, &err)) { 10895 + string_builder_dispose(&sb); 10896 + return is_err(err) ? err : js_mkerr(js, "oom"); 10847 10897 } 10848 10898 } 10849 - ENSURE_CAP(1); 10850 - result[result_len++] = str_ptr[i++]; 10899 + 10900 + i = key_end + 2; 10901 + literal_start = i; 10902 + continue; 10903 + 10904 + next_char: 10905 + i++; 10851 10906 } 10852 - ant_value_t ret = js_mkstr(js, result, result_len); 10853 - free(result); 10854 - return ret; 10855 - #undef ENSURE_CAP 10907 + 10908 + if (!string_builder_append(&sb, str_ptr + literal_start, (size_t)(str_len - literal_start))) goto oom; 10909 + return string_builder_finalize(js, &sb); 10910 + 10911 + oom: 10912 + string_builder_dispose(&sb); 10913 + return js_mkerr(js, "oom"); 10856 10914 } 10857 10915 10858 10916 static size_t html_attr_escaped_len(const char *s, ant_offset_t len) { ··· 11562 11620 return ret; 11563 11621 } 11564 11622 11565 - static bool string_builder_append_value( 11566 - ant_t *js, char **buf, 11567 - size_t *len, size_t *cap, 11568 - ant_value_t value, ant_value_t *err 11569 - ) { 11570 - ant_value_t s = js_tostring_val(js, value); 11571 - if (is_err(s)) { 11572 - if (err) *err = s; 11573 - return false; 11574 - } 11575 - 11576 - ant_offset_t slen = 0; 11577 - ant_offset_t soff = vstr(js, s, &slen); 11578 - 11579 - size_t need = *len + (size_t)slen + 1; 11580 - if (need > *cap) { 11581 - size_t next = (*cap == 0) ? 64 : *cap; 11582 - while (next < need) next *= 2; 11583 - char *grown = (char *)realloc(*buf, next); 11584 - if (!grown) { 11585 - if (err) *err = js_mkerr(js, "oom"); 11586 - return false; 11587 - } 11588 - *buf = grown; 11589 - *cap = next; 11590 - } 11591 - 11592 - if (slen > 0) memcpy(*buf + *len, (const void *)(uintptr_t)soff, (size_t)slen); 11593 - *len += (size_t)slen; 11594 - (*buf)[*len] = '\0'; 11595 - return true; 11596 - } 11597 - 11598 11623 static ant_value_t builtin_string_raw(ant_t *js, ant_value_t *args, int nargs) { 11599 11624 if (nargs < 1 || is_null(args[0]) || is_undefined(args[0])) { 11600 11625 return js_mkerr_typed(js, JS_ERR_TYPE, "String.raw requires a template object"); ··· 11617 11642 size_t literal_count = (size_t)raw_len_num; 11618 11643 if (literal_count == 0) return js_mkstr(js, "", 0); 11619 11644 11620 - char *buf = NULL; 11621 - size_t len = 0; size_t cap = 0; 11645 + string_builder_t sb; 11646 + char static_buf[256]; 11647 + string_builder_init(&sb, static_buf, sizeof(static_buf)); 11648 + 11622 11649 ant_value_t err = js_mkundef(); 11623 - 11624 11650 for (size_t i = 0; i < literal_count; i++) { 11625 11651 ant_value_t chunk = js_mkundef(); 11626 11652 if (vtype(raw) == T_ARR) chunk = js_arr_get(js, raw, (ant_offset_t)i); ··· 11629 11655 snprintf(key, sizeof(key), "%zu", i); 11630 11656 chunk = js_get(js, raw, key); 11631 11657 } 11632 - 11633 - if (!string_builder_append_value(js, &buf, &len, &cap, chunk, &err)) { 11634 - free(buf); 11658 + 11659 + if (!string_builder_append_value(js, &sb, chunk, &err)) { 11660 + string_builder_dispose(&sb); 11635 11661 return is_err(err) ? err : js_mkerr(js, "oom"); 11636 11662 } 11637 - 11638 - if (i + 1 < literal_count && (int)(i + 1) < nargs) { 11639 - if (!string_builder_append_value(js, &buf, &len, &cap, args[i + 1], &err)) { 11640 - free(buf); return is_err(err) ? err : js_mkerr(js, "oom"); 11641 - } 11663 + 11664 + if (i + 1 >= literal_count || (int)(i + 1) >= nargs) continue; 11665 + if (!string_builder_append_value(js, &sb, args[i + 1], &err)) { 11666 + string_builder_dispose(&sb); 11667 + return is_err(err) ? err : js_mkerr(js, "oom"); 11642 11668 } 11643 11669 } 11644 11670 11645 - ant_value_t out = js_mkstr(js, buf ? buf : "", len); 11646 - free(buf); 11647 - 11648 - return out; 11671 + return string_builder_finalize(js, &sb); 11649 11672 } 11650 11673 11651 11674 static ant_value_t builtin_number_toString(ant_t *js, ant_value_t *args, int nargs) {
+3 -3
src/modules/collections.c
··· 150 150 } 151 151 152 152 static bool set_store_entry(ant_t *js, set_entry_t **set_ptr, ant_value_t value) { 153 - collection_key_t key; 154 - if (!collection_key_init(js, value, &key)) return false; 153 + ant_value_t stored_value = normalize_map_key(value); collection_key_t key; 154 + if (!collection_key_init(js, stored_value, &key)) return false; 155 155 156 156 set_entry_t *entry = NULL; 157 157 HASH_FIND(hh, *set_ptr, key.bytes, key.len, entry); ··· 175 175 176 176 memcpy(entry->key, key.bytes, key.len); 177 177 entry->key_len = key.len; 178 - entry->value = value; 178 + entry->value = stored_value; 179 179 180 180 HASH_ADD_KEYPTR(hh, *set_ptr, entry->key, entry->key_len, entry); 181 181 collection_key_free(&key);
+109
tests/bench_string_builder.cjs
··· 1 + const now = 2 + typeof performance !== "undefined" && performance && typeof performance.now === "function" 3 + ? () => performance.now() 4 + : () => Date.now(); 5 + 6 + const scale = 7 + typeof process !== "undefined" && process.argv && process.argv.length > 2 8 + ? Math.max(1, Number(process.argv[2]) || 1) 9 + : 1; 10 + 11 + let sink = 0; 12 + 13 + function mixString(value) { 14 + const len = value.length; 15 + sink = (sink + len * 33 + (len ? value.charCodeAt(len - 1) : 0)) | 0; 16 + } 17 + 18 + function bench(name, iterations, fn) { 19 + const warmup = Math.max(1, iterations >> 5); 20 + for (let i = 0; i < warmup; i++) mixString(fn(i)); 21 + 22 + const start = now(); 23 + for (let i = 0; i < iterations; i++) mixString(fn(i)); 24 + const elapsed = now() - start; 25 + const opsPerSec = elapsed > 0 ? (iterations * 1000) / elapsed : 0; 26 + 27 + console.log( 28 + name + 29 + ": " + 30 + elapsed.toFixed(2) + 31 + "ms (" + 32 + iterations + 33 + " ops, " + 34 + opsPerSec.toFixed(0) + 35 + " ops/s)" 36 + ); 37 + } 38 + 39 + function makeTemplateCase(count, literalSize) { 40 + const data = {}; 41 + let template = ""; 42 + 43 + for (let i = 0; i < count; i++) { 44 + const key = "p" + i; 45 + data[key] = i % 3 === 0 ? "value" + i : i % 3 === 1 ? i : (i & 1) === 0; 46 + template += "x".repeat(literalSize) + "{{" + key + "}}"; 47 + } 48 + 49 + template += "tail"; 50 + return { template, data }; 51 + } 52 + 53 + function makeRawCase(count, literalSize) { 54 + const raw = []; 55 + const args = [{ raw }]; 56 + 57 + for (let i = 0; i < count; i++) { 58 + raw[i] = "r".repeat(literalSize); 59 + if (i + 1 < count) args[i + 1] = i % 3 === 0 ? "sub" + i : i % 3 === 1 ? i : true; 60 + } 61 + 62 + return args; 63 + } 64 + 65 + const smallTemplate = "Hello {{name}} {{count}} {{ok}}."; 66 + const smallTemplateData = { name: "Ant", count: 42, ok: true }; 67 + const largeTemplateCase = makeTemplateCase(96, 24); 68 + 69 + const smallRawArgs = [{ raw: ["Hello ", " ", "."] }, "Ant", 42]; 70 + const largeRawArgs = makeRawCase(128, 24); 71 + 72 + const objectToString = Object.prototype.toString; 73 + const smallTagObject = { [Symbol.toStringTag]: "SmallTag" }; 74 + const largeTagObject = { [Symbol.toStringTag]: "T".repeat(800) }; 75 + 76 + const hugeLeft = "L".repeat(320 * 1024); 77 + const hugeRight = "R".repeat(320 * 1024); 78 + 79 + console.log("String builder bench scale=" + scale); 80 + 81 + bench("String.prototype.template small/static", 250000 * scale, () => { 82 + return smallTemplate.template(smallTemplateData); 83 + }); 84 + 85 + bench("String.prototype.template large/spill", 25000 * scale, () => { 86 + return largeTemplateCase.template.template(largeTemplateCase.data); 87 + }); 88 + 89 + bench("String.raw small/static", 200000 * scale, () => { 90 + return String.raw.apply(String, smallRawArgs); 91 + }); 92 + 93 + bench("String.raw large/realloc", 20000 * scale, () => { 94 + return String.raw.apply(String, largeRawArgs); 95 + }); 96 + 97 + bench("Object.prototype.toString small/static", 300000 * scale, () => { 98 + return objectToString.call(smallTagObject); 99 + }); 100 + 101 + bench("Object.prototype.toString large/spill", 150000 * scale, () => { 102 + return objectToString.call(largeTagObject); 103 + }); 104 + 105 + bench("large binary concat flatten", 300 * scale, () => { 106 + return hugeLeft + hugeRight; 107 + }); 108 + 109 + console.log("sink=" + sink);
+31
tests/test_string_template_modern.cjs
··· 1 + function assertEq(actual, expected, label) { 2 + if (actual !== expected) { 3 + throw new Error(label + ": expected " + JSON.stringify(expected) + ", got " + JSON.stringify(actual)); 4 + } 5 + } 6 + 7 + assertEq( 8 + "Hello, {{name}}. Missing: {{missing}}.".template({ name: "Ant" }), 9 + "Hello, Ant. Missing: .", 10 + "missing placeholders stay empty" 11 + ); 12 + 13 + assertEq( 14 + "{{nil}}/{{undef}}/{{obj}}/{{arr}}/{{ok}}".template({ 15 + nil: null, 16 + undef: undefined, 17 + obj: { toString() { return "custom"; } }, 18 + arr: [1, 2], 19 + ok: false 20 + }), 21 + "null/undefined/custom/1,2/false", 22 + "placeholder values use normal string coercion" 23 + ); 24 + 25 + assertEq( 26 + "before {{name after".template({ name: "ignored" }), 27 + "before {{name after", 28 + "unterminated placeholders remain literal" 29 + ); 30 + 31 + console.log("ok");