mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 08:25:40 +02:00
Discriminate json_number's three numeric slots into one union
json_number used to carry three parallel 8-byte fields (a double plus
both a 64-bit unsigned and a 64-bit signed slot for the large-integer
cases) even though at most one of the integer slots is ever the
canonical value for any given number. Collapse them into a
discriminated union:
enum repr_t { REPR_DOUBLE, REPR_LARGE_UNSIGNED, REPR_LARGE_SIGNED };
repr_t repr;
union { double d; unsigned long long u; long long s; } value;
Callers keep the same read API: number() returns the appropriate
double, large_unsigned() returns the ull (or 0 if not currently stored
that way), large_signed() likewise. Writes go through new set_number /
set_large_unsigned / set_large_signed methods that keep the
discriminator and the union value in sync.
This was prompted by an observation that moving json_type to the end
of the object should shrink things via tail-padding reuse. Empirically
the type-at-end rearrangement saves nothing on its own (every
subclass payload is 8-byte aligned so it can't slot into the 4-byte
tail), but the discriminated-number redesign hits the same idea from
a different direction: adding the 4-byte `repr` to json_number makes
the class non-standard-layout, which lets the Itanium ABI pack `repr`
into the base's 4-byte tail padding at offset 20. The union value
then starts at the natural offset 24, and json_number ends at offset
32 -- a 33% reduction.
Per-node sizes:
json_object (TRUE/FALSE/NULL) 24 bytes
json_number 32 bytes (was 48)
json_string 48 bytes
json_array 48 bytes
json_hash 48 bytes
Numbers dominate real GeoJSON (every coordinate is one), so the net
memory win on a typical parse is substantial.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+51
-21
@@ -82,12 +82,17 @@ struct json_object {
|
||||
inline std::string &string();
|
||||
inline const std::string &string() const;
|
||||
|
||||
inline double &number();
|
||||
// Numbers are stored in a discriminated union (double / unsigned /
|
||||
// signed) so a json_number is only 40 bytes instead of 48. The
|
||||
// large_*() accessors return 0 when the number is not currently
|
||||
// stored in that representation, matching the prior convention
|
||||
// where "0" meant "not set, fall through to the next slot".
|
||||
inline double number() const;
|
||||
inline unsigned long long &large_unsigned();
|
||||
inline unsigned long long large_unsigned() const;
|
||||
inline long long &large_signed();
|
||||
inline long long large_signed() const;
|
||||
inline void set_number(double d);
|
||||
inline void set_large_unsigned(unsigned long long u);
|
||||
inline void set_large_signed(long long s);
|
||||
|
||||
inline std::vector<json_object_ptr> &array();
|
||||
inline const std::vector<json_object_ptr> &array() const;
|
||||
@@ -97,9 +102,17 @@ struct json_object {
|
||||
};
|
||||
|
||||
struct json_number : json_object {
|
||||
double number_value = 0;
|
||||
unsigned long long large_unsigned_value = 0;
|
||||
long long large_signed_value = 0;
|
||||
enum repr_t { REPR_DOUBLE,
|
||||
REPR_LARGE_UNSIGNED,
|
||||
REPR_LARGE_SIGNED };
|
||||
|
||||
repr_t repr = REPR_DOUBLE;
|
||||
union value_t {
|
||||
double d;
|
||||
unsigned long long u;
|
||||
long long s;
|
||||
value_t() : d(0) {}
|
||||
} value;
|
||||
|
||||
json_number() : json_object(JSON_NUMBER) {}
|
||||
json_number(json_object *p, json_pull *pl) : json_object(JSON_NUMBER, p, pl) {}
|
||||
@@ -135,29 +148,46 @@ inline const std::string &json_object::string() const {
|
||||
return static_cast<const json_string *>(this)->string_value;
|
||||
}
|
||||
|
||||
inline double &json_object::number() {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<json_number *>(this)->number_value;
|
||||
}
|
||||
inline double json_object::number() const {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<const json_number *>(this)->number_value;
|
||||
}
|
||||
inline unsigned long long &json_object::large_unsigned() {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<json_number *>(this)->large_unsigned_value;
|
||||
auto *n = static_cast<const json_number *>(this);
|
||||
switch (n->repr) {
|
||||
case json_number::REPR_LARGE_UNSIGNED:
|
||||
return static_cast<double>(n->value.u);
|
||||
case json_number::REPR_LARGE_SIGNED:
|
||||
return static_cast<double>(n->value.s);
|
||||
case json_number::REPR_DOUBLE:
|
||||
default:
|
||||
return n->value.d;
|
||||
}
|
||||
}
|
||||
inline unsigned long long json_object::large_unsigned() const {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<const json_number *>(this)->large_unsigned_value;
|
||||
}
|
||||
inline long long &json_object::large_signed() {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<json_number *>(this)->large_signed_value;
|
||||
auto *n = static_cast<const json_number *>(this);
|
||||
return n->repr == json_number::REPR_LARGE_UNSIGNED ? n->value.u : 0;
|
||||
}
|
||||
inline long long json_object::large_signed() const {
|
||||
assert(type == JSON_NUMBER);
|
||||
return static_cast<const json_number *>(this)->large_signed_value;
|
||||
auto *n = static_cast<const json_number *>(this);
|
||||
return n->repr == json_number::REPR_LARGE_SIGNED ? n->value.s : 0;
|
||||
}
|
||||
inline void json_object::set_number(double d) {
|
||||
assert(type == JSON_NUMBER);
|
||||
auto *n = static_cast<json_number *>(this);
|
||||
n->repr = json_number::REPR_DOUBLE;
|
||||
n->value.d = d;
|
||||
}
|
||||
inline void json_object::set_large_unsigned(unsigned long long u) {
|
||||
assert(type == JSON_NUMBER);
|
||||
auto *n = static_cast<json_number *>(this);
|
||||
n->repr = json_number::REPR_LARGE_UNSIGNED;
|
||||
n->value.u = u;
|
||||
}
|
||||
inline void json_object::set_large_signed(long long s) {
|
||||
assert(type == JSON_NUMBER);
|
||||
auto *n = static_cast<json_number *>(this);
|
||||
n->repr = json_number::REPR_LARGE_SIGNED;
|
||||
n->value.s = s;
|
||||
}
|
||||
|
||||
inline std::vector<json_object_ptr> &json_object::array() {
|
||||
|
||||
Reference in New Issue
Block a user