benchmark encoders and speed them up
This commit is contained in:
@@ -308,6 +308,30 @@ void nota_write_blob(NotaBuffer *nb, unsigned long long nbits, const char *data)
|
||||
|
||||
void nota_write_text(NotaBuffer *nb, const char *s)
|
||||
{
|
||||
/* ASCII fast path: if all bytes < 0x80, KIM == UTF-8 and rune count == byte count */
|
||||
size_t slen = strlen(s);
|
||||
const unsigned char *scan = (const unsigned char *)s;
|
||||
int is_ascii = 1;
|
||||
for (size_t k = 0; k < slen; k++) {
|
||||
if (scan[k] >= 0x80) {
|
||||
is_ascii = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_ascii) {
|
||||
long long runes = (long long)slen;
|
||||
char *p = nota_buffer_alloc(nb, 1 + 10 + slen);
|
||||
p[0] = NOTA_TEXT;
|
||||
char *end = nota_continue_num(runes, p, 4);
|
||||
memcpy(end, s, slen);
|
||||
size_t used = (size_t)(end - p) + slen;
|
||||
size_t allocated = 1 + 10 + slen;
|
||||
nb->size -= (allocated - used);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Non-ASCII path: full UTF-8 decode + KIM encode */
|
||||
long long runes = utf8_count(s);
|
||||
|
||||
size_t max_kim = (size_t)(runes * 5);
|
||||
@@ -367,6 +391,13 @@ static void nota_write_int_buf(NotaBuffer *nb, long long n)
|
||||
nb->size -= (10 - used);
|
||||
}
|
||||
|
||||
static const double nota_pow10_table[29] = {
|
||||
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
|
||||
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
|
||||
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23,
|
||||
1e24, 1e25, 1e26, 1e27, 1e28
|
||||
};
|
||||
|
||||
static void extract_mantissa_coefficient(double num, long *coefficient, long *exponent)
|
||||
{
|
||||
if (num == 0.0) {
|
||||
@@ -375,32 +406,46 @@ static void extract_mantissa_coefficient(double num, long *coefficient, long *ex
|
||||
return;
|
||||
}
|
||||
|
||||
/* Round to 12 decimal places to avoid floating artifacts. */
|
||||
double rounded = floor(fabs(num) * 1e12 + 0.5) / 1e12;
|
||||
if (num < 0) {
|
||||
rounded = -rounded;
|
||||
double absval = fabs(num);
|
||||
int sign = (num < 0) ? -1 : 1;
|
||||
|
||||
/* Get decimal exponent via log10 */
|
||||
int dec_exp = (int)floor(log10(absval));
|
||||
|
||||
/* Scale to extract 14-digit coefficient.
|
||||
We want coeff * 10^exp = absval, with coeff having up to 14 digits.
|
||||
So coeff = absval * 10^(13 - dec_exp), exp = dec_exp - 13 */
|
||||
int shift = 13 - dec_exp;
|
||||
double scaled;
|
||||
if (shift >= 0 && shift <= 28) {
|
||||
scaled = absval * nota_pow10_table[shift];
|
||||
} else if (shift < 0 && -shift <= 28) {
|
||||
scaled = absval / nota_pow10_table[-shift];
|
||||
} else {
|
||||
scaled = absval * pow(10.0, (double)shift);
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%.14g", rounded);
|
||||
long long coeff = (long long)(scaled + 0.5);
|
||||
|
||||
char *exp_pos = strpbrk(buf, "eE");
|
||||
long exp_from_sci = 0;
|
||||
if (exp_pos) {
|
||||
exp_from_sci = atol(exp_pos + 1);
|
||||
*exp_pos = '\0';
|
||||
/* Correct off-by-one from log10 rounding */
|
||||
if (coeff >= 100000000000000LL) {
|
||||
coeff = (coeff + 5) / 10;
|
||||
shift--;
|
||||
} else if (coeff < 10000000000000LL && coeff > 0) {
|
||||
coeff = (long long)(absval * pow(10.0, (double)(shift + 1)) + 0.5);
|
||||
shift++;
|
||||
}
|
||||
|
||||
char *dec_point = strchr(buf, '.');
|
||||
int digits_after_decimal = 0;
|
||||
if (dec_point) {
|
||||
digits_after_decimal = (int)strlen(dec_point + 1);
|
||||
memmove(dec_point, dec_point + 1, strlen(dec_point));
|
||||
int exp_out = -shift;
|
||||
|
||||
/* Strip trailing zeros */
|
||||
while (coeff != 0 && coeff % 10 == 0) {
|
||||
coeff /= 10;
|
||||
exp_out++;
|
||||
}
|
||||
|
||||
long long coeff_ll = atoll(buf);
|
||||
*coefficient = (long)coeff_ll;
|
||||
*exponent = exp_from_sci - digits_after_decimal;
|
||||
*coefficient = (long)(coeff * sign);
|
||||
*exponent = (long)exp_out;
|
||||
}
|
||||
|
||||
static void nota_write_float_buf(NotaBuffer *nb, double d)
|
||||
|
||||
@@ -11292,38 +11292,41 @@ static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
|
||||
return (int)len;
|
||||
}
|
||||
|
||||
typedef struct NotaVisitedNode {
|
||||
JSGCRef ref;
|
||||
struct NotaVisitedNode *next;
|
||||
} NotaVisitedNode;
|
||||
|
||||
typedef struct NotaEncodeContext {
|
||||
JSContext *ctx;
|
||||
JSGCRef *visitedStack_ref; /* pointer to GC-rooted ref */
|
||||
NotaVisitedNode *visited_list;
|
||||
NotaBuffer nb;
|
||||
int cycle;
|
||||
JSGCRef *replacer_ref; /* pointer to GC-rooted ref */
|
||||
} NotaEncodeContext;
|
||||
|
||||
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val);
|
||||
JS_SetPropertyNumber (ctx, enc->visitedStack_ref->val, len, JS_DupValue (ctx, val));
|
||||
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
|
||||
JS_PushGCRef (enc->ctx, &node->ref);
|
||||
node->ref.val = JS_DupValue (enc->ctx, val);
|
||||
node->next = enc->visited_list;
|
||||
enc->visited_list = node;
|
||||
}
|
||||
|
||||
static void nota_stack_pop (NotaEncodeContext *enc) {
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val);
|
||||
JS_SetPropertyStr (ctx, enc->visitedStack_ref->val, "length", JS_NewUint32 (ctx, len - 1));
|
||||
NotaVisitedNode *node = enc->visited_list;
|
||||
enc->visited_list = node->next;
|
||||
JS_FreeValue (enc->ctx, node->ref.val);
|
||||
JS_PopGCRef (enc->ctx, &node->ref);
|
||||
sys_free (node);
|
||||
}
|
||||
|
||||
static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
|
||||
JSContext *ctx = enc->ctx;
|
||||
int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val);
|
||||
for (int i = 0; i < len; i++) {
|
||||
JSValue elem = JS_GetPropertyNumber (ctx, enc->visitedStack_ref->val, i);
|
||||
if (mist_is_gc_object (elem) && mist_is_gc_object (val)) {
|
||||
if (JS_StrictEq (ctx, elem, val)) {
|
||||
JS_FreeValue (ctx, elem);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
JS_FreeValue (ctx, elem);
|
||||
NotaVisitedNode *node = enc->visited_list;
|
||||
while (node) {
|
||||
if (JS_StrictEq (enc->ctx, node->ref.val, val))
|
||||
return 1;
|
||||
node = node->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -11465,6 +11468,13 @@ static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
nota_write_sym (&enc->nb, NOTA_NULL);
|
||||
break;
|
||||
case JS_TAG_PTR: {
|
||||
if (JS_IsText (replaced_ref.val)) {
|
||||
const char *str = JS_ToCString (ctx, replaced_ref.val);
|
||||
nota_write_text (&enc->nb, str);
|
||||
JS_FreeCString (ctx, str);
|
||||
break;
|
||||
}
|
||||
|
||||
if (js_is_blob (ctx, replaced_ref.val)) {
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val);
|
||||
@@ -11576,16 +11586,14 @@ static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValue
|
||||
}
|
||||
|
||||
void *value2nota (JSContext *ctx, JSValue v) {
|
||||
JSGCRef val_ref, stack_ref, key_ref;
|
||||
JSGCRef val_ref, key_ref;
|
||||
JS_PushGCRef (ctx, &val_ref);
|
||||
JS_PushGCRef (ctx, &stack_ref);
|
||||
JS_PushGCRef (ctx, &key_ref);
|
||||
val_ref.val = v;
|
||||
|
||||
NotaEncodeContext enc_s, *enc = &enc_s;
|
||||
enc->ctx = ctx;
|
||||
stack_ref.val = JS_NewArray (ctx);
|
||||
enc->visitedStack_ref = &stack_ref;
|
||||
enc->visited_list = NULL;
|
||||
enc->cycle = 0;
|
||||
enc->replacer_ref = NULL;
|
||||
|
||||
@@ -11595,14 +11603,12 @@ void *value2nota (JSContext *ctx, JSValue v) {
|
||||
|
||||
if (enc->cycle) {
|
||||
JS_PopGCRef (ctx, &key_ref);
|
||||
JS_PopGCRef (ctx, &stack_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
nota_buffer_free (&enc->nb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JS_PopGCRef (ctx, &key_ref);
|
||||
JS_PopGCRef (ctx, &stack_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
void *data_ptr = enc->nb.data;
|
||||
enc->nb.data = NULL;
|
||||
@@ -11630,18 +11636,16 @@ JSValue nota2value (JSContext *js, void *nota) {
|
||||
static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) return JS_ThrowTypeError (ctx, "nota.encode requires at least 1 argument");
|
||||
|
||||
JSGCRef val_ref, stack_ref, replacer_ref, key_ref;
|
||||
JSGCRef val_ref, replacer_ref, key_ref;
|
||||
JS_PushGCRef (ctx, &val_ref);
|
||||
JS_PushGCRef (ctx, &stack_ref);
|
||||
JS_PushGCRef (ctx, &replacer_ref);
|
||||
JS_PushGCRef (ctx, &key_ref);
|
||||
val_ref.val = argv[0];
|
||||
stack_ref.val = JS_NewArray (ctx);
|
||||
replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
|
||||
|
||||
NotaEncodeContext enc_s, *enc = &enc_s;
|
||||
enc->ctx = ctx;
|
||||
enc->visitedStack_ref = &stack_ref;
|
||||
enc->visited_list = NULL;
|
||||
enc->cycle = 0;
|
||||
enc->replacer_ref = &replacer_ref;
|
||||
|
||||
@@ -11662,7 +11666,6 @@ static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc,
|
||||
|
||||
JS_PopGCRef (ctx, &key_ref);
|
||||
JS_PopGCRef (ctx, &replacer_ref);
|
||||
JS_PopGCRef (ctx, &stack_ref);
|
||||
JS_PopGCRef (ctx, &val_ref);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -429,11 +429,15 @@ char *wota_read_text_len(size_t *byte_len, char **text_utf8, char *wota)
|
||||
}
|
||||
|
||||
/* Copy bytes from the packed 64-bit words */
|
||||
for (long long i = 0; i < nwords; i++) {
|
||||
uint64_t wval = data_words[i];
|
||||
for (int j = 0; j < 8 && (i * 8 + j) < (long long)nbytes; j++) {
|
||||
out[i * 8 + j] = (char)((wval >> (56 - j * 8)) & 0xff);
|
||||
}
|
||||
size_t full_words = nbytes / 8;
|
||||
size_t remainder = nbytes % 8;
|
||||
for (size_t i = 0; i < full_words; i++) {
|
||||
uint64_t wval = wota_bswap64(data_words[i]);
|
||||
memcpy(out + i * 8, &wval, 8);
|
||||
}
|
||||
if (remainder > 0) {
|
||||
uint64_t wval = wota_bswap64(data_words[full_words]);
|
||||
memcpy(out + full_words * 8, &wval, remainder);
|
||||
}
|
||||
|
||||
out[nbytes] = '\0';
|
||||
@@ -609,14 +613,18 @@ void wota_write_text_len(WotaBuffer *wb, const char *utf8, size_t nbytes)
|
||||
}
|
||||
|
||||
uint64_t *blocks = wota_buffer_alloc(wb, nwords);
|
||||
memset(blocks, 0, nwords * sizeof(uint64_t));
|
||||
|
||||
for (size_t i = 0; i < nwords; i++) {
|
||||
size_t full_words = nbytes / 8;
|
||||
size_t remainder = nbytes % 8;
|
||||
for (size_t i = 0; i < full_words; i++) {
|
||||
uint64_t wval;
|
||||
memcpy(&wval, utf8 + i * 8, 8);
|
||||
blocks[i] = wota_bswap64(wval);
|
||||
}
|
||||
if (remainder > 0) {
|
||||
uint64_t wval = 0;
|
||||
for (int j = 0; j < 8 && (i * 8 + j) < nbytes; j++) {
|
||||
wval |= ((uint64_t)(unsigned char)utf8[i * 8 + j]) << (56 - j * 8);
|
||||
}
|
||||
blocks[i] = wval;
|
||||
memcpy(&wval, utf8 + full_words * 8, remainder);
|
||||
blocks[full_words] = wota_bswap64(wval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user