mruby-pack: optimize template parsing with O(1) lookup table

Replace massive 40+ case switch statement in read_tmpl() with direct
format_table[256] lookup for standard format characters. This eliminates
branch prediction overhead and reduces function size from 290 to ~90 lines.

Key improvements:
- O(1) format character resolution vs O(n) switch traversal
- Preserved runtime-dependent format handling (I, i, J, j)
- Maintained full backward compatibility with all existing tests
- Better instruction cache usage with smaller function size
- Consistent template parsing performance across format types

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-14 08:04:02 +09:00
parent 650cdcecbc
commit f4d2117d7d
+71 -176
View File
@@ -193,6 +193,54 @@ static const uint8_t char_class[256] = {
['\f'] = CHAR_SPACE,
['\r'] = CHAR_SPACE
};
/* template parsing optimization structures */
typedef struct {
enum pack_dir dir;
enum pack_type type;
int size;
unsigned int base_flags;
} format_info_t;
/* direct O(1) lookup table for format characters */
static const format_info_t format_table[256] = {
['A'] = {PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2},
['a'] = {PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_a},
['B'] = {PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2},
['b'] = {PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB},
['C'] = {PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, 0},
['c'] = {PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, PACK_FLAG_SIGNED},
['D'] = {PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED},
['d'] = {PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED},
['E'] = {PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_LT},
['e'] = {PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_LT},
['F'] = {PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED},
['f'] = {PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED},
['G'] = {PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_GT},
['g'] = {PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_GT},
['H'] = {PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2},
['h'] = {PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB},
/* I, i, J, j are handled specially based on sizeof() */
['L'] = {PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, 0},
['l'] = {PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_SIGNED},
['M'] = {PACK_DIR_QENC, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2},
['m'] = {PACK_DIR_BASE64, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2},
['N'] = {PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_GT},
['n'] = {PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_GT},
['Q'] = {PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, 0},
['q'] = {PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, PACK_FLAG_SIGNED},
['S'] = {PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, 0},
['s'] = {PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_SIGNED},
['U'] = {PACK_DIR_UTF8, PACK_TYPE_INTEGER, 0, 0},
['V'] = {PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_LT},
['v'] = {PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_LT},
['w'] = {PACK_DIR_BER, PACK_TYPE_INTEGER, 0, PACK_FLAG_SIGNED},
['x'] = {PACK_DIR_NUL, PACK_TYPE_NONE, 0, 0},
['X'] = {PACK_DIR_BACK, PACK_TYPE_NONE, 0, 0},
['@'] = {PACK_DIR_ABS, PACK_TYPE_NONE, 0, 0},
['Z'] = {PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_Z}
/* All other entries default to {0,0,0,0} indicating invalid format */
};
#define IS_PADDING_CHAR_A(c) (char_class[(unsigned char)(c)] & (CHAR_NULL | CHAR_SPACE))
#define IS_PADDING_CHAR_Z(c) (char_class[(unsigned char)(c)] & CHAR_NULL)
@@ -1334,84 +1382,14 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz
if (tmpl->idx >= tlen) return PACK_DIR_NONE;
t = tptr[tmpl->idx++];
alias:
/* Handle whitespace - skip and restart */
if (ISSPACE((char)t)) {
goto restart;
}
/* Special handling for runtime-dependent formats and special characters */
switch (t) {
case 'A':
dir = PACK_DIR_STR;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2;
break;
case 'a':
dir = PACK_DIR_STR;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_a;
break;
case 'C':
dir = PACK_DIR_CHAR;
type = PACK_TYPE_INTEGER;
size = 1;
break;
case 'c':
dir = PACK_DIR_CHAR;
type = PACK_TYPE_INTEGER;
size = 1;
flags |= PACK_FLAG_SIGNED;
break;
case 'D': case 'd':
dir = PACK_DIR_DOUBLE;
type = PACK_TYPE_FLOAT;
size = 8;
flags |= PACK_FLAG_SIGNED;
break;
case 'F': case 'f':
dir = PACK_DIR_FLOAT;
type = PACK_TYPE_FLOAT;
size = 4;
flags |= PACK_FLAG_SIGNED;
break;
case 'E':
dir = PACK_DIR_DOUBLE;
type = PACK_TYPE_FLOAT;
size = 8;
flags |= PACK_FLAG_SIGNED | PACK_FLAG_LT;
break;
case 'e':
dir = PACK_DIR_FLOAT;
type = PACK_TYPE_FLOAT;
size = 4;
flags |= PACK_FLAG_SIGNED | PACK_FLAG_LT;
break;
case 'G':
dir = PACK_DIR_DOUBLE;
type = PACK_TYPE_FLOAT;
size = 8;
flags |= PACK_FLAG_SIGNED | PACK_FLAG_GT;
break;
case 'g':
dir = PACK_DIR_FLOAT;
type = PACK_TYPE_FLOAT;
size = 4;
flags |= PACK_FLAG_SIGNED | PACK_FLAG_GT;
break;
case 'H':
dir = PACK_DIR_HEX;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_COUNT2;
break;
case 'h':
dir = PACK_DIR_HEX;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_COUNT2 | PACK_FLAG_LSB;
break;
case 'B':
dir = PACK_DIR_BSTR;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_COUNT2;
break;
case 'b':
dir = PACK_DIR_BSTR;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_COUNT2 | PACK_FLAG_LSB;
break;
case 'I':
switch (sizeof(int)) {
case 2: t = 'S'; goto alias;
@@ -1446,115 +1424,32 @@ read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *siz
mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(intptr_t) == %d", (int)sizeof(intptr_t));
}
break;
case 'L':
dir = PACK_DIR_LONG;
type = PACK_TYPE_INTEGER;
size = 4;
break;
case 'l':
dir = PACK_DIR_LONG;
type = PACK_TYPE_INTEGER;
size = 4;
flags |= PACK_FLAG_SIGNED;
break;
case 'w':
dir = PACK_DIR_BER;
type = PACK_TYPE_INTEGER;
flags |= PACK_FLAG_SIGNED;
break;
case 'm':
dir = PACK_DIR_BASE64;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2;
break;
case 'M':
dir = PACK_DIR_QENC;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2;
break;
case 'N': /* = "L>" */
dir = PACK_DIR_LONG;
type = PACK_TYPE_INTEGER;
size = 4;
flags |= PACK_FLAG_GT;
break;
case 'n': /* = "S>" */
dir = PACK_DIR_SHORT;
type = PACK_TYPE_INTEGER;
size = 2;
flags |= PACK_FLAG_GT;
break;
case 'Q':
dir = PACK_DIR_QUAD;
type = PACK_TYPE_INTEGER;
size = 8;
break;
case 'q':
dir = PACK_DIR_QUAD;
type = PACK_TYPE_INTEGER;
size = 8;
flags |= PACK_FLAG_SIGNED;
break;
case 'S':
dir = PACK_DIR_SHORT;
type = PACK_TYPE_INTEGER;
size = 2;
break;
case 's':
dir = PACK_DIR_SHORT;
type = PACK_TYPE_INTEGER;
size = 2;
flags |= PACK_FLAG_SIGNED;
break;
case 'U':
dir = PACK_DIR_UTF8;
type = PACK_TYPE_INTEGER;
break;
case 'V': /* = "L<" */
dir = PACK_DIR_LONG;
type = PACK_TYPE_INTEGER;
size = 4;
flags |= PACK_FLAG_LT;
break;
case 'v': /* = "S<" */
dir = PACK_DIR_SHORT;
type = PACK_TYPE_INTEGER;
size = 2;
flags |= PACK_FLAG_LT;
break;
case 'x':
dir = PACK_DIR_NUL;
type = PACK_TYPE_NONE;
break;
case 'X':
dir = PACK_DIR_BACK;
type = PACK_TYPE_NONE;
break;
case '@':
dir = PACK_DIR_ABS;
type = PACK_TYPE_NONE;
break;
case 'Z':
dir = PACK_DIR_STR;
type = PACK_TYPE_STRING;
flags |= PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_Z;
break;
case '#':
while (++tmpl->idx < tlen && tptr[tmpl->idx] != '\n')
;
goto restart;
case 'p': case 'P':
case '%':
mrb_raisef(mrb, E_ARGUMENT_ERROR, "%c is not supported", (char)t);
break;
default:
if (!ISSPACE((char)t)) {
char c = (char)t;
mrb_value s = mrb_str_new(mrb, &c, 1);
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown unpack directive %!v", s);
/* Use O(1) lookup table for standard format characters */
if (t >= 0 && t < 256) {
const format_info_t *info = &format_table[t];
if (info->dir != PACK_DIR_NONE) {
/* Valid format character found in lookup table */
dir = info->dir;
type = info->type;
size = info->size;
flags = info->base_flags;
break;
}
}
goto restart;
/* Handle invalid characters */
char c = (char)t;
mrb_value s = mrb_str_new(mrb, &c, 1);
mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown unpack directive %!v", s);
}
/* read suffix [0-9*_!<>] */