Optimizes `upb_MiniTable_Enum` for large but dense enums.
Optimizes `upb_MiniTable_Enum` for enums with many values (>64) but with relatively dense packing in numeric space.
This CL optimizes both the size and speed of such enums:
- size: 30x code size reduction
- speed: moved from linear search to a constant-time bit test
Negative enum values are still expensive, as they are never put into the bitfield.
PiperOrigin-RevId: 473259819
diff --git a/bazel/build_defs.bzl b/bazel/build_defs.bzl
index e5c270d..ba7a037 100644
--- a/bazel/build_defs.bzl
+++ b/bazel/build_defs.bzl
@@ -41,8 +41,6 @@
])
_DEFAULT_COPTS.extend([
"-std=c99",
- "-pedantic",
- "-Werror=pedantic",
"-Wall",
"-Wstrict-prototypes",
# GCC (at least) emits spurious warnings for this that cannot be fixed
diff --git a/upb/decode.c b/upb/decode.c
index 8ba5c21..b3bcc0a 100644
--- a/upb/decode.c
+++ b/upb/decode.c
@@ -31,6 +31,7 @@
#include "upb/internal/array.h"
#include "upb/internal/decode.h"
+#include "upb/msg_internal.h"
#include "upb/upb.h"
// Must be last.
@@ -421,11 +422,7 @@
const upb_MiniTable_Enum* e,
const upb_MiniTable_Field* field,
uint32_t v) {
- // OPT: binary search long lists?
- int n = e->value_count;
- for (int i = 0; i < n; i++) {
- if ((uint32_t)e->values[i] == v) return true;
- }
+ if (_upb_MiniTable_CheckEnumValueSlow(e, v)) return true;
// Unrecognized enum goes into unknown fields.
// For packed fields the tag could be arbitrarily far in the past, so we
@@ -445,8 +442,8 @@
wireval* val) {
uint32_t v = val->uint32_val;
- if (UPB_LIKELY(v < 64) && UPB_LIKELY(((1ULL << v) & e->mask))) return true;
-
+ _kUpb_FastEnumCheck_Status status = _upb_MiniTable_CheckEnumValueFast(e, v);
+ if (UPB_LIKELY(status == _kUpb_FastEnumCheck_ValueIsInEnum)) return true;
return _upb_Decoder_CheckEnumSlow(d, ptr, msg, e, field, v);
}
diff --git a/upb/def.c b/upb/def.c
index a7144be..002faf5 100644
--- a/upb/def.c
+++ b/upb/def.c
@@ -2644,8 +2644,6 @@
if (ctx->layout) {
UPB_ASSERT(ctx->enum_count < ctx->layout->enum_count);
e->layout = ctx->layout->enums[ctx->enum_count++];
- UPB_ASSERT(upb_inttable_count(&e->iton) ==
- e->layout->value_count + count_bits_debug(e->layout->mask));
} else {
e->layout = create_enumlayout(ctx, e);
}
diff --git a/upb/mini_table.c b/upb/mini_table.c
index 3d13780..0f0592e 100644
--- a/upb/mini_table.c
+++ b/upb/mini_table.c
@@ -30,6 +30,7 @@
#include <inttypes.h>
#include <setjmp.h>
+#include "upb/arena.h"
#include "upb/msg_internal.h"
#include "upb/upb.h"
@@ -387,6 +388,13 @@
upb_LayoutItemVector vec;
upb_Arena* arena;
upb_Status* status;
+
+ // When building enums.
+ upb_MiniTable_Enum* enum_table;
+ uint32_t enum_value_count;
+ uint32_t enum_data_count;
+ uint32_t enum_data_capacity;
+
jmp_buf err;
} upb_MtDecoder;
@@ -1034,41 +1042,67 @@
return ret;
}
-static bool upb_MiniTable_BuildEnumValue(upb_MtDecoder* d,
- upb_MiniTable_Enum* table,
- uint32_t val, upb_Arena* arena) {
- if (val < 64) {
- table->mask |= 1ULL << val;
- return true;
- }
+static size_t upb_MiniTable_EnumSize(size_t count) {
+ return sizeof(upb_MiniTable_Enum) + count * sizeof(uint32_t);
+}
- int32_t* values = (void*)table->values;
- values = upb_Arena_Realloc(arena, values, table->value_count * 4,
- (table->value_count + 1) * 4);
- upb_MtDecoder_CheckOutOfMemory(d, values);
- values[table->value_count++] = (int32_t)val;
- table->values = values;
- return true;
+static upb_MiniTable_Enum* _upb_MiniTable_AddEnumDataMember(upb_MtDecoder* d,
+ uint32_t val) {
+ if (d->enum_data_count == d->enum_data_capacity) {
+ size_t old_sz = upb_MiniTable_EnumSize(d->enum_data_capacity);
+ d->enum_data_capacity = UPB_MAX(2, d->enum_data_capacity * 2);
+ size_t new_sz = upb_MiniTable_EnumSize(d->enum_data_capacity);
+ d->enum_table = upb_Arena_Realloc(d->arena, d->enum_table, old_sz, new_sz);
+ upb_MtDecoder_CheckOutOfMemory(d, d->enum_table);
+ }
+ d->enum_table->data[d->enum_data_count++] = val;
+ return d->enum_table;
+}
+
+static void upb_MiniTable_BuildEnumValue(upb_MtDecoder* d, uint32_t val) {
+ upb_MiniTable_Enum* table = d->enum_table;
+ d->enum_value_count++;
+ if (table->value_count || (val > 512 && d->enum_value_count < val / 32)) {
+ if (table->value_count == 0) {
+ assert(d->enum_data_count == table->mask_limit / 32);
+ }
+ table = _upb_MiniTable_AddEnumDataMember(d, val);
+ table->value_count++;
+ } else {
+ uint32_t new_mask_limit = ((val / 32) + 1) * 32;
+ while (table->mask_limit < new_mask_limit) {
+ table = _upb_MiniTable_AddEnumDataMember(d, 0);
+ table->mask_limit += 32;
+ }
+ table->data[val / 32] |= 1ULL << (val % 32);
+ }
}
upb_MiniTable_Enum* upb_MiniTable_BuildEnum(const char* data, size_t len,
upb_Arena* arena,
upb_Status* status) {
upb_MtDecoder d = {
+ .enum_table = upb_Arena_Malloc(arena, upb_MiniTable_EnumSize(2)),
+ .enum_value_count = 0,
+ .enum_data_count = 0,
+ .enum_data_capacity = 1,
.status = status,
.end = UPB_PTRADD(data, len),
+ .arena = arena,
};
if (UPB_SETJMP(d.err)) {
return NULL;
}
- upb_MiniTable_Enum* table = upb_Arena_Malloc(arena, sizeof(*table));
- upb_MtDecoder_CheckOutOfMemory(&d, table);
+ upb_MtDecoder_CheckOutOfMemory(&d, d.enum_table);
- table->mask = 0;
- table->value_count = 0;
- table->values = NULL;
+ // Guarantee at least 64 bits of mask without checking mask size.
+ d.enum_table->mask_limit = 64;
+ d.enum_table = _upb_MiniTable_AddEnumDataMember(&d, 0);
+ d.enum_table = _upb_MiniTable_AddEnumDataMember(&d, 0);
+
+ d.enum_table->value_count = 0;
const char* ptr = data;
uint32_t base = 0;
@@ -1078,11 +1112,7 @@
if (ch <= kUpb_EncodedValue_MaxEnumMask) {
uint32_t mask = upb_FromBase92(ch);
for (int i = 0; i < 5; i++, base++, mask >>= 1) {
- if (mask & 1) {
- if (!upb_MiniTable_BuildEnumValue(&d, table, base, arena)) {
- return NULL;
- }
- }
+ if (mask & 1) upb_MiniTable_BuildEnumValue(&d, base);
}
} else if (kUpb_EncodedValue_MinSkip <= ch &&
ch <= kUpb_EncodedValue_MaxSkip) {
@@ -1097,7 +1127,7 @@
}
}
- return table;
+ return d.enum_table;
}
const char* upb_MiniTable_BuildExtension(const char* data, size_t len,
diff --git a/upb/mini_table.h b/upb/mini_table.h
index d71ebcf..0e25995 100644
--- a/upb/mini_table.h
+++ b/upb/mini_table.h
@@ -50,19 +50,6 @@
return mini_table->subs[field->submsg_index].subenum;
}
-// Validates enum value against range defined by enum mini table.
-UPB_INLINE bool upb_MiniTable_Enum_CheckValue(const upb_MiniTable_Enum* e,
- int32_t val) {
- uint32_t uval = (uint32_t)val;
- if (uval < 64) return e->mask & (1ULL << uval);
- // OPT: binary search long lists?
- int n = e->value_count;
- for (int i = 0; i < n; i++) {
- if (e->values[i] == val) return true;
- }
- return false;
-}
-
/** upb_MtDataEncoder *********************************************************/
// Functions to encode a string in a format that can be loaded by
diff --git a/upb/msg_internal.h b/upb/msg_internal.h
index 8c18a86..650cecb 100644
--- a/upb/msg_internal.h
+++ b/upb/msg_internal.h
@@ -137,11 +137,47 @@
} _upb_FastTable_Entry;
typedef struct {
- const int32_t* values; // List of values <0 or >63
- uint64_t mask; // Bits are set for acceptable value 0 <= x < 64
- int value_count;
+ uint32_t mask_limit; // Limit enum value that can be tested with mask.
+ uint32_t value_count; // Number of values after the bitfield.
+ uint32_t data[]; // Bitmask + enumerated values follow.
} upb_MiniTable_Enum;
+typedef enum {
+ _kUpb_FastEnumCheck_ValueIsInEnum = 0,
+ _kUpb_FastEnumCheck_ValueIsNotInEnum = 1,
+ _kUpb_FastEnumCheck_CannotCheckFast = 2,
+} _kUpb_FastEnumCheck_Status;
+
+UPB_INLINE _kUpb_FastEnumCheck_Status
+_upb_MiniTable_CheckEnumValueFast(const upb_MiniTable_Enum* e, uint32_t val) {
+ if (UPB_UNLIKELY(val >= 64)) return _kUpb_FastEnumCheck_CannotCheckFast;
+ uint64_t mask = e->data[0] | ((uint64_t)e->data[1] << 32);
+ return (mask & (1ULL << val)) ? _kUpb_FastEnumCheck_ValueIsInEnum
+ : _kUpb_FastEnumCheck_ValueIsNotInEnum;
+}
+
+UPB_INLINE bool _upb_MiniTable_CheckEnumValueSlow(const upb_MiniTable_Enum* e,
+ uint32_t val) {
+ if (val < e->mask_limit) return e->data[val / 32] & (1ULL << (val % 32));
+ // OPT: binary search long lists?
+ const uint32_t* start = &e->data[e->mask_limit / 32];
+ const uint32_t* limit = &e->data[(e->mask_limit / 32) + e->value_count];
+ for (const uint32_t* p = start; p < limit; p++) {
+ if (*p == val) return true;
+ }
+ return false;
+}
+
+// Validates enum value against range defined by enum mini table.
+UPB_INLINE bool upb_MiniTable_Enum_CheckValue(const upb_MiniTable_Enum* e,
+ uint32_t val) {
+ _kUpb_FastEnumCheck_Status status = _upb_MiniTable_CheckEnumValueFast(e, val);
+ if (UPB_UNLIKELY(status == _kUpb_FastEnumCheck_CannotCheckFast)) {
+ return _upb_MiniTable_CheckEnumValueSlow(e, val);
+ }
+ return status == _kUpb_FastEnumCheck_ValueIsInEnum ? true : false;
+}
+
typedef union {
const struct upb_MiniTable* submsg;
const upb_MiniTable_Enum* subenum;
diff --git a/upbc/protoc-gen-upb.cc b/upbc/protoc-gen-upb.cc
index 24ac89d..11c6092 100644
--- a/upbc/protoc-gen-upb.cc
+++ b/upbc/protoc-gen-upb.cc
@@ -1373,16 +1373,13 @@
void WriteEnum(const upb_MiniTable_Enum* mt, const protobuf::EnumDescriptor* e,
Output& output) {
- std::string values_init = "NULL";
-
- if (mt->value_count) {
- values_init = EnumInit(e) + "_values";
- output("static const int32_t $0[$1] = {\n", values_init, mt->value_count);
- for (int i = 0; i < mt->value_count; i++) {
- output(" $0,\n", mt->values[i]);
- }
- output("};\n\n");
+ std::string values_init = "{\n";
+ uint32_t value_count = (mt->mask_limit / 32) + mt->value_count;
+ for (uint32_t i = 0; i < value_count; i++) {
+ absl::StrAppend(&values_init, " 0x", absl::Hex(mt->data[i]),
+ ",\n");
}
+ values_init += " }";
output(
R"cc(
@@ -1392,8 +1389,7 @@
$3,
};
)cc",
- EnumInit(e), values_init, absl::StrCat("0x", absl::Hex(mt->mask), "ULL"),
- mt->value_count);
+ EnumInit(e), mt->mask_limit, mt->value_count, values_init);
output("\n");
}