mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-04 17:35:42 +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:
@@ -482,27 +482,26 @@ again:
|
|||||||
|
|
||||||
json_object_ptr n = add_object(j, JSON_NUMBER);
|
json_object_ptr n = add_object(j, JSON_NUMBER);
|
||||||
if (n != nullptr) {
|
if (n != nullptr) {
|
||||||
n->number() = atof(j->number_buffer.c_str());
|
double d = atof(j->number_buffer.c_str());
|
||||||
n->large_signed() = 0;
|
n->set_number(d);
|
||||||
n->large_unsigned() = 0;
|
|
||||||
|
|
||||||
#define MAX_SAFE_INTEGER 9007199254740991.0
|
#define MAX_SAFE_INTEGER 9007199254740991.0
|
||||||
#define MIN_SAFE_INTEGER -9007199254740991.0
|
#define MIN_SAFE_INTEGER -9007199254740991.0
|
||||||
|
|
||||||
if (!decimal && n->number() > MAX_SAFE_INTEGER) {
|
if (!decimal && d > MAX_SAFE_INTEGER) {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
char *err = nullptr;
|
char *err = nullptr;
|
||||||
unsigned long long ull = strtoull(j->number_buffer.c_str(), &err, 10);
|
unsigned long long ull = strtoull(j->number_buffer.c_str(), &err, 10);
|
||||||
if (errno == 0 && (err == nullptr || *err == '\0')) {
|
if (errno == 0 && (err == nullptr || *err == '\0')) {
|
||||||
n->large_unsigned() = ull;
|
n->set_large_unsigned(ull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!decimal && n->number() < MIN_SAFE_INTEGER) {
|
if (!decimal && d < MIN_SAFE_INTEGER) {
|
||||||
errno = 0;
|
errno = 0;
|
||||||
char *err = nullptr;
|
char *err = nullptr;
|
||||||
long long ll = strtoll(j->number_buffer.c_str(), &err, 10);
|
long long ll = strtoll(j->number_buffer.c_str(), &err, 10);
|
||||||
if (errno == 0 && (err == nullptr || *err == '\0')) {
|
if (errno == 0 && (err == nullptr || *err == '\0')) {
|
||||||
n->large_signed() = ll;
|
n->set_large_signed(ll);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-21
@@ -82,12 +82,17 @@ struct json_object {
|
|||||||
inline std::string &string();
|
inline std::string &string();
|
||||||
inline const std::string &string() const;
|
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 double number() const;
|
||||||
inline unsigned long long &large_unsigned();
|
|
||||||
inline unsigned long long large_unsigned() const;
|
inline unsigned long long large_unsigned() const;
|
||||||
inline long long &large_signed();
|
|
||||||
inline long long large_signed() const;
|
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 std::vector<json_object_ptr> &array();
|
||||||
inline const std::vector<json_object_ptr> &array() const;
|
inline const std::vector<json_object_ptr> &array() const;
|
||||||
@@ -97,9 +102,17 @@ struct json_object {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct json_number : json_object {
|
struct json_number : json_object {
|
||||||
double number_value = 0;
|
enum repr_t { REPR_DOUBLE,
|
||||||
unsigned long long large_unsigned_value = 0;
|
REPR_LARGE_UNSIGNED,
|
||||||
long long large_signed_value = 0;
|
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(JSON_NUMBER) {}
|
||||||
json_number(json_object *p, json_pull *pl) : json_object(JSON_NUMBER, p, pl) {}
|
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;
|
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 {
|
inline double json_object::number() const {
|
||||||
assert(type == JSON_NUMBER);
|
assert(type == JSON_NUMBER);
|
||||||
return static_cast<const json_number *>(this)->number_value;
|
auto *n = static_cast<const json_number *>(this);
|
||||||
}
|
switch (n->repr) {
|
||||||
inline unsigned long long &json_object::large_unsigned() {
|
case json_number::REPR_LARGE_UNSIGNED:
|
||||||
assert(type == JSON_NUMBER);
|
return static_cast<double>(n->value.u);
|
||||||
return static_cast<json_number *>(this)->large_unsigned_value;
|
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 {
|
inline unsigned long long json_object::large_unsigned() const {
|
||||||
assert(type == JSON_NUMBER);
|
assert(type == JSON_NUMBER);
|
||||||
return static_cast<const json_number *>(this)->large_unsigned_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() {
|
|
||||||
assert(type == JSON_NUMBER);
|
|
||||||
return static_cast<json_number *>(this)->large_signed_value;
|
|
||||||
}
|
}
|
||||||
inline long long json_object::large_signed() const {
|
inline long long json_object::large_signed() const {
|
||||||
assert(type == JSON_NUMBER);
|
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() {
|
inline std::vector<json_object_ptr> &json_object::array() {
|
||||||
|
|||||||
+1
-1
@@ -326,7 +326,7 @@ void join_csv(json_object_ptr j) {
|
|||||||
vo = s;
|
vo = s;
|
||||||
} else if (attr_type == JSON_NUMBER) {
|
} else if (attr_type == JSON_NUMBER) {
|
||||||
auto n = std::make_shared<json_number>(properties.get(), properties->parser);
|
auto n = std::make_shared<json_number>(properties.get(), properties->parser);
|
||||||
n->number_value = atof(v.c_str());
|
n->set_number(atof(v.c_str()));
|
||||||
vo = n;
|
vo = n;
|
||||||
} else {
|
} else {
|
||||||
abort();
|
abort();
|
||||||
|
|||||||
Reference in New Issue
Block a user