perf(writer): streamline v2 binary writes and benchmarks
This commit is contained in:
328
demo/unit_test.c
328
demo/unit_test.c
@@ -1,6 +1,7 @@
|
|||||||
#include "ikv.h"
|
#include "ikv.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
char message[256];
|
char message[256];
|
||||||
char benchmark_summary[256];
|
char benchmark_summary[4096];
|
||||||
} test_context_t;
|
} test_context_t;
|
||||||
|
|
||||||
typedef int (*test_fn_t)(test_context_t *context);
|
typedef int (*test_fn_t)(test_context_t *context);
|
||||||
@@ -87,11 +88,13 @@ static double benchmark_now_us(void)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static ikv_node_t *create_benchmark_root(void)
|
static ikv_node_t *create_benchmark_root(unsigned int scale)
|
||||||
{
|
{
|
||||||
ikv_node_t *root = ikv_create_object("benchmark");
|
ikv_node_t *root = ikv_create_object("benchmark");
|
||||||
ikv_node_t *inventory = NULL;
|
ikv_node_t *inventory = NULL;
|
||||||
ikv_node_t *stats = NULL;
|
ikv_node_t *stats = NULL;
|
||||||
|
unsigned int item_count = 0u;
|
||||||
|
unsigned int stat_count = 0u;
|
||||||
|
|
||||||
if (!root)
|
if (!root)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -109,16 +112,44 @@ static ikv_node_t *create_benchmark_root(void)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ikv_array_add_string(inventory, "wrench");
|
item_count = scale * 8u;
|
||||||
ikv_array_add_string(inventory, "battery");
|
if (item_count < 5u)
|
||||||
ikv_array_add_string(inventory, "map");
|
item_count = 5u;
|
||||||
ikv_array_add_string(inventory, "radio");
|
for (unsigned int i = 0; i < item_count; ++i)
|
||||||
ikv_array_add_string(inventory, "flares");
|
{
|
||||||
|
char item_name[64];
|
||||||
|
snprintf(item_name, sizeof(item_name), "item-%u-payload-%u", scale, i);
|
||||||
|
ikv_array_add_string(inventory, item_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
stat_count = scale * 16u;
|
||||||
|
if (stat_count < 4u)
|
||||||
|
stat_count = 4u;
|
||||||
|
for (unsigned int i = 0; i < stat_count; ++i)
|
||||||
|
{
|
||||||
|
char key[64];
|
||||||
|
snprintf(key, sizeof(key), "metric_%u", i);
|
||||||
|
switch (i % 4u)
|
||||||
|
{
|
||||||
|
case 0u:
|
||||||
|
ikv_object_set_int(stats, key, (int64_t)(scale * 100u + i));
|
||||||
|
break;
|
||||||
|
case 1u:
|
||||||
|
ikv_object_set_float(stats, key, (double)scale * 0.5 + (double)i * 0.25);
|
||||||
|
break;
|
||||||
|
case 2u:
|
||||||
|
ikv_object_set_bool(stats, key, (i & 1u) != 0u);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
char value[64];
|
||||||
|
snprintf(value, sizeof(value), "value-%u-%u", scale, i);
|
||||||
|
ikv_object_set_string(stats, key, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ikv_object_set_int(stats, "health", 100);
|
|
||||||
ikv_object_set_int(stats, "armor", 75);
|
|
||||||
ikv_object_set_float(stats, "speed", 12.75);
|
|
||||||
ikv_object_set_bool(stats, "indoors", false);
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1524,28 +1555,207 @@ static int test_hook_alloc_fail_internal_v2_memory_copy(test_context_t *context)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned int scale;
|
||||||
|
unsigned int iterations;
|
||||||
|
double avg_index_us;
|
||||||
|
double avg_read_us;
|
||||||
|
double avg_write_us;
|
||||||
|
} benchmark_sample_t;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
BENCHMARK_FORMAT_TEXT_V1 = 0,
|
||||||
|
BENCHMARK_FORMAT_TEXT_V2,
|
||||||
|
BENCHMARK_FORMAT_BINARY_V1,
|
||||||
|
BENCHMARK_FORMAT_BINARY_V2
|
||||||
|
} benchmark_format_t;
|
||||||
|
|
||||||
|
static const char *benchmark_big_o_label(double exponent)
|
||||||
|
{
|
||||||
|
if (exponent < 0.15)
|
||||||
|
return "O(1)";
|
||||||
|
if (exponent < 0.50)
|
||||||
|
return "O(log n)";
|
||||||
|
if (exponent < 1.20)
|
||||||
|
return "O(n)";
|
||||||
|
if (exponent < 1.60)
|
||||||
|
return "O(n log n)";
|
||||||
|
if (exponent < 2.40)
|
||||||
|
return "O(n^2)";
|
||||||
|
return "O(n^k)";
|
||||||
|
}
|
||||||
|
|
||||||
|
static double benchmark_fit_exponent(const benchmark_sample_t *samples, size_t count, int metric)
|
||||||
|
{
|
||||||
|
double sum_x = 0.0;
|
||||||
|
double sum_y = 0.0;
|
||||||
|
double sum_xx = 0.0;
|
||||||
|
double sum_xy = 0.0;
|
||||||
|
size_t used = 0u;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
double y = 0.0;
|
||||||
|
double x = 0.0;
|
||||||
|
|
||||||
|
if (!samples || samples[i].scale == 0u)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (metric)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
y = samples[i].avg_index_us;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
y = samples[i].avg_read_us;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
y = samples[i].avg_write_us;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y <= 0.0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
x = log((double)samples[i].scale);
|
||||||
|
y = log(y);
|
||||||
|
sum_x += x;
|
||||||
|
sum_y += y;
|
||||||
|
sum_xx += x * x;
|
||||||
|
sum_xy += x * y;
|
||||||
|
++used;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (used < 2u)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
if (fabs((double)used * sum_xx - sum_x * sum_x) < 1e-9)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
return (((double)used * sum_xy) - (sum_x * sum_y)) / (((double)used * sum_xx) - (sum_x * sum_x));
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *benchmark_format_name(benchmark_format_t format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V1:
|
||||||
|
return "v1-text";
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V2:
|
||||||
|
return "v2-text";
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V1:
|
||||||
|
return "v1-bin";
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V2:
|
||||||
|
return "v2-bin";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *benchmark_format_path(benchmark_format_t format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V1:
|
||||||
|
return "demo_benchmark_v1.ikv";
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V2:
|
||||||
|
return "demo_benchmark_v2.ikv";
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V1:
|
||||||
|
return "demo_benchmark_v1.ikvb";
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V2:
|
||||||
|
return "demo_benchmark_v2.ikvb";
|
||||||
|
default:
|
||||||
|
return "demo_benchmark.ikv";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool benchmark_write_file(benchmark_format_t format, const char *path, const ikv_node_t *root)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V1:
|
||||||
|
return ikv_write_file_version(path, root, IKV_VERSION_1);
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V2:
|
||||||
|
return ikv_write_file_version(path, root, IKV_VERSION_2);
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V1:
|
||||||
|
return ikvb_write_file_version(path, root, IKV_VERSION_1);
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V2:
|
||||||
|
return ikvb_write_file_version(path, root, IKV_VERSION_2);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ikv_node_t *benchmark_parse_file(benchmark_format_t format, const char *path)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V1:
|
||||||
|
return ikv_parse_file_version(path, IKV_VERSION_1);
|
||||||
|
case BENCHMARK_FORMAT_TEXT_V2:
|
||||||
|
return ikv_parse_file_version(path, IKV_VERSION_2);
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V1:
|
||||||
|
return ikvb_parse_file_version(path, IKV_VERSION_1);
|
||||||
|
case BENCHMARK_FORMAT_BINARY_V2:
|
||||||
|
return ikvb_parse_file_version(path, IKV_VERSION_2);
|
||||||
|
default:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int test_binary_v2_file_benchmark(test_context_t *context)
|
static int test_binary_v2_file_benchmark(test_context_t *context)
|
||||||
{
|
{
|
||||||
enum { benchmark_iterations = 1000 };
|
static const unsigned int benchmark_scales[] = {1u, 4u, 16u, 64u};
|
||||||
const char *path = "demo_benchmark_v2.ikvb";
|
static const benchmark_format_t benchmark_formats[] = {
|
||||||
ikv_node_t *root = NULL;
|
BENCHMARK_FORMAT_TEXT_V1,
|
||||||
|
BENCHMARK_FORMAT_TEXT_V2,
|
||||||
|
BENCHMARK_FORMAT_BINARY_V1,
|
||||||
|
BENCHMARK_FORMAT_BINARY_V2
|
||||||
|
};
|
||||||
|
enum { benchmark_scale_count = 4, benchmark_iterations_small = 1000, benchmark_iterations_medium = 400, benchmark_iterations_large = 150 };
|
||||||
|
benchmark_sample_t samples[4][benchmark_scale_count];
|
||||||
|
size_t summary_offset = 0u;
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
memset(samples, 0, sizeof(samples));
|
||||||
|
context->benchmark_summary[0] = 0;
|
||||||
|
|
||||||
|
for (unsigned int format_index = 0; result == 0 && format_index < 4u; ++format_index)
|
||||||
|
{
|
||||||
|
benchmark_format_t format = benchmark_formats[format_index];
|
||||||
|
const char *path = benchmark_format_path(format);
|
||||||
|
|
||||||
|
cleanup_file(path);
|
||||||
|
|
||||||
|
for (unsigned int sample_index = 0; result == 0 && sample_index < benchmark_scale_count; ++sample_index)
|
||||||
|
{
|
||||||
|
unsigned int scale = benchmark_scales[sample_index];
|
||||||
|
unsigned int iterations = (scale <= 4u) ? benchmark_iterations_small : ((scale <= 16u) ? benchmark_iterations_medium : benchmark_iterations_large);
|
||||||
|
unsigned int expected_inventory_size = (scale * 8u < 5u) ? 5u : (scale * 8u);
|
||||||
|
ikv_node_t *root = create_benchmark_root(scale);
|
||||||
double total_index_us = 0.0;
|
double total_index_us = 0.0;
|
||||||
double total_read_us = 0.0;
|
double total_read_us = 0.0;
|
||||||
double total_write_us = 0.0;
|
double total_write_us = 0.0;
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
cleanup_file(path);
|
|
||||||
root = create_benchmark_root();
|
|
||||||
if (!root)
|
if (!root)
|
||||||
return fail(context, "failed to create benchmark root");
|
{
|
||||||
|
result = fail(context, "failed to create benchmark root");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ikvb_write_file(path, root))
|
if (!benchmark_write_file(format, path, root))
|
||||||
|
{
|
||||||
|
ikv_free(root);
|
||||||
result = fail(context, "failed to prepare benchmark file");
|
result = fail(context, "failed to prepare benchmark file");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned int i = 0; result == 0 && i < benchmark_iterations; ++i)
|
for (unsigned int i = 0; result == 0 && i < iterations; ++i)
|
||||||
{
|
{
|
||||||
double start_time = benchmark_now_us();
|
double start_time = benchmark_now_us();
|
||||||
ikv_node_t *loaded = ikv_parse_file(path);
|
ikv_node_t *loaded = benchmark_parse_file(format, path);
|
||||||
double end_time = benchmark_now_us();
|
double end_time = benchmark_now_us();
|
||||||
|
|
||||||
if (!loaded)
|
if (!loaded)
|
||||||
@@ -1556,9 +1766,9 @@ static int test_binary_v2_file_benchmark(test_context_t *context)
|
|||||||
ikv_free(loaded);
|
ikv_free(loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned int i = 0; result == 0 && i < benchmark_iterations; ++i)
|
for (unsigned int i = 0; result == 0 && i < iterations; ++i)
|
||||||
{
|
{
|
||||||
ikv_node_t *loaded = ikv_parse_file(path);
|
ikv_node_t *loaded = benchmark_parse_file(format, path);
|
||||||
ikv_node_t *inventory = NULL;
|
ikv_node_t *inventory = NULL;
|
||||||
double start_time = 0.0;
|
double start_time = 0.0;
|
||||||
double end_time = 0.0;
|
double end_time = 0.0;
|
||||||
@@ -1574,7 +1784,7 @@ static int test_binary_v2_file_benchmark(test_context_t *context)
|
|||||||
end_time = benchmark_now_us();
|
end_time = benchmark_now_us();
|
||||||
|
|
||||||
if ((result = expect_true(context, inventory != NULL, "benchmark read inventory lookup failed")) == 0 &&
|
if ((result = expect_true(context, inventory != NULL, "benchmark read inventory lookup failed")) == 0 &&
|
||||||
(result = expect_true(context, ikv_array_size(inventory) == 5u, "benchmark read inventory size mismatch")) == 0)
|
(result = expect_true(context, ikv_array_size(inventory) == expected_inventory_size, "benchmark read inventory size mismatch")) == 0)
|
||||||
{
|
{
|
||||||
total_read_us += end_time - start_time;
|
total_read_us += end_time - start_time;
|
||||||
}
|
}
|
||||||
@@ -1582,10 +1792,10 @@ static int test_binary_v2_file_benchmark(test_context_t *context)
|
|||||||
ikv_free(loaded);
|
ikv_free(loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (unsigned int i = 0; result == 0 && i < benchmark_iterations; ++i)
|
for (unsigned int i = 0; result == 0 && i < iterations; ++i)
|
||||||
{
|
{
|
||||||
double start_time = benchmark_now_us();
|
double start_time = benchmark_now_us();
|
||||||
bool ok = ikvb_write_file(path, root);
|
bool ok = benchmark_write_file(format, path, root);
|
||||||
double end_time = benchmark_now_us();
|
double end_time = benchmark_now_us();
|
||||||
|
|
||||||
if (!ok)
|
if (!ok)
|
||||||
@@ -1596,17 +1806,65 @@ static int test_binary_v2_file_benchmark(test_context_t *context)
|
|||||||
|
|
||||||
if (result == 0)
|
if (result == 0)
|
||||||
{
|
{
|
||||||
snprintf(context->benchmark_summary,
|
samples[format_index][sample_index].scale = scale;
|
||||||
sizeof(context->benchmark_summary),
|
samples[format_index][sample_index].iterations = iterations;
|
||||||
"avg index %.2fus avg read %.2fus avg write %.2fus over %u iterations",
|
samples[format_index][sample_index].avg_index_us = total_index_us / (double)iterations;
|
||||||
total_index_us / (double)benchmark_iterations,
|
samples[format_index][sample_index].avg_read_us = total_read_us / (double)iterations;
|
||||||
total_read_us / (double)benchmark_iterations,
|
samples[format_index][sample_index].avg_write_us = total_write_us / (double)iterations;
|
||||||
total_write_us / (double)benchmark_iterations,
|
|
||||||
(unsigned int)benchmark_iterations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_file(path);
|
|
||||||
ikv_free(root);
|
ikv_free(root);
|
||||||
|
cleanup_file(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
for (unsigned int format_index = 0; format_index < 4u; ++format_index)
|
||||||
|
{
|
||||||
|
const benchmark_sample_t *format_samples = samples[format_index];
|
||||||
|
double index_exp = benchmark_fit_exponent(format_samples, benchmark_scale_count, 0);
|
||||||
|
double read_exp = benchmark_fit_exponent(format_samples, benchmark_scale_count, 1);
|
||||||
|
double write_exp = benchmark_fit_exponent(format_samples, benchmark_scale_count, 2);
|
||||||
|
int wrote = snprintf(
|
||||||
|
context->benchmark_summary + summary_offset,
|
||||||
|
sizeof(context->benchmark_summary) - summary_offset,
|
||||||
|
"%s index %s^%.2f [n=%u %.2fus, n=%u %.2fus, n=%u %.2fus, n=%u %.2fus] "
|
||||||
|
"read %s^%.2f [n=%u %.2fus, n=%u %.2fus, n=%u %.2fus, n=%u %.2fus] "
|
||||||
|
"write %s^%.2f [n=%u %.2fus, n=%u %.2fus, n=%u %.2fus, n=%u %.2fus]%s",
|
||||||
|
benchmark_format_name(benchmark_formats[format_index]),
|
||||||
|
benchmark_big_o_label(index_exp), index_exp,
|
||||||
|
format_samples[0].scale, format_samples[0].avg_index_us,
|
||||||
|
format_samples[1].scale, format_samples[1].avg_index_us,
|
||||||
|
format_samples[2].scale, format_samples[2].avg_index_us,
|
||||||
|
format_samples[3].scale, format_samples[3].avg_index_us,
|
||||||
|
benchmark_big_o_label(read_exp), read_exp,
|
||||||
|
format_samples[0].scale, format_samples[0].avg_read_us,
|
||||||
|
format_samples[1].scale, format_samples[1].avg_read_us,
|
||||||
|
format_samples[2].scale, format_samples[2].avg_read_us,
|
||||||
|
format_samples[3].scale, format_samples[3].avg_read_us,
|
||||||
|
benchmark_big_o_label(write_exp), write_exp,
|
||||||
|
format_samples[0].scale, format_samples[0].avg_write_us,
|
||||||
|
format_samples[1].scale, format_samples[1].avg_write_us,
|
||||||
|
format_samples[2].scale, format_samples[2].avg_write_us,
|
||||||
|
format_samples[3].scale, format_samples[3].avg_write_us,
|
||||||
|
(format_index + 1u < 4u) ? "\n" : "");
|
||||||
|
|
||||||
|
if (wrote < 0)
|
||||||
|
{
|
||||||
|
result = fail(context, "failed to format benchmark summary");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size_t)wrote >= sizeof(context->benchmark_summary) - summary_offset)
|
||||||
|
{
|
||||||
|
summary_offset = sizeof(context->benchmark_summary) - 1u;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary_offset += (size_t)wrote;
|
||||||
|
}
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1711,7 +1969,7 @@ static void log_case(bool passed, unsigned int index, unsigned int total, long e
|
|||||||
putchar('\n');
|
putchar('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
static char benchmark_summary[256];
|
static char benchmark_summary[4096];
|
||||||
|
|
||||||
static int run_test_case(unsigned int index, unsigned int total)
|
static int run_test_case(unsigned int index, unsigned int total)
|
||||||
{
|
{
|
||||||
|
|||||||
27
src/ikv.c
27
src/ikv.c
@@ -1754,6 +1754,33 @@ bool ikv__write_binary_node_memory(const ikv_node_t *node, uint8_t **out_data, u
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ikv__write_binary_node_append(const ikv_node_t *node, uint8_t **data, size_t *size, size_t *capacity)
|
||||||
|
{
|
||||||
|
bw_mem_t mem = {0};
|
||||||
|
bw_t w = {0};
|
||||||
|
size_t start_size = 0u;
|
||||||
|
|
||||||
|
if (!node || !data || !size || !capacity)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mem.data = *data;
|
||||||
|
mem.size = *size;
|
||||||
|
mem.cap = *capacity;
|
||||||
|
start_size = mem.size;
|
||||||
|
w.ctx = &mem;
|
||||||
|
w.ok = true;
|
||||||
|
w.write_bytes_fn = bw_mem_write_bytes;
|
||||||
|
bw_node(&w, node);
|
||||||
|
|
||||||
|
if (!w.ok || mem.size == start_size || mem.size > 0xFFFFFFFFu)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*data = mem.data;
|
||||||
|
*size = mem.size;
|
||||||
|
*capacity = mem.cap;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version)
|
bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version)
|
||||||
{
|
{
|
||||||
if (!path || !root)
|
if (!path || !root)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size
|
|||||||
|
|
||||||
bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version);
|
bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version);
|
||||||
bool ikv__write_binary_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, uint32_t version);
|
bool ikv__write_binary_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, uint32_t version);
|
||||||
|
bool ikv__write_binary_node_append(const ikv_node_t *node, uint8_t **data, size_t *size, size_t *capacity);
|
||||||
ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version);
|
ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version);
|
||||||
ikv_node_t *ikv__parse_binary_memory_version(const void *data, size_t size, uint32_t version);
|
ikv_node_t *ikv__parse_binary_memory_version(const void *data, size_t size, uint32_t version);
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,10 @@ typedef struct
|
|||||||
uint32_t key_length;
|
uint32_t key_length;
|
||||||
uint32_t key_hash;
|
uint32_t key_hash;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
|
const ikv_node_t *source_node;
|
||||||
|
uint32_t metadata_offset;
|
||||||
uint32_t payload_offset;
|
uint32_t payload_offset;
|
||||||
uint32_t payload_size;
|
uint32_t payload_size;
|
||||||
uint8_t *payload_data;
|
|
||||||
uint32_t next_in_bucket;
|
uint32_t next_in_bucket;
|
||||||
} ikv2_index_entry_t;
|
} ikv2_index_entry_t;
|
||||||
|
|
||||||
@@ -162,6 +163,16 @@ static void ikv2_buffer_write_u32le(ikv2_buffer_t *buffer, uint32_t value)
|
|||||||
ikv2_buffer_write_bytes(buffer, bytes, sizeof(bytes));
|
ikv2_buffer_write_bytes(buffer, bytes, sizeof(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ikv2_patch_u32le(uint8_t *data, uint32_t value)
|
||||||
|
{
|
||||||
|
if (!data)
|
||||||
|
return;
|
||||||
|
data[0] = (uint8_t)(value & 0xFFu);
|
||||||
|
data[1] = (uint8_t)((value >> 8) & 0xFFu);
|
||||||
|
data[2] = (uint8_t)((value >> 16) & 0xFFu);
|
||||||
|
data[3] = (uint8_t)((value >> 24) & 0xFFu);
|
||||||
|
}
|
||||||
|
|
||||||
static void ikv2_buffer_write_varu32(ikv2_buffer_t *buffer, uint32_t value)
|
static void ikv2_buffer_write_varu32(ikv2_buffer_t *buffer, uint32_t value)
|
||||||
{
|
{
|
||||||
while (value >= 0x80u)
|
while (value >= 0x80u)
|
||||||
@@ -273,17 +284,8 @@ static bool ikv2_collect_root_entries(const ikv_node_t *root, ikv2_index_entry_t
|
|||||||
{
|
{
|
||||||
for (ikv_node_t *node = root->value.object.buckets[bucket]; node; node = node->next)
|
for (ikv_node_t *node = root->value.object.buckets[bucket]; node; node = node->next)
|
||||||
{
|
{
|
||||||
uint8_t *payload = NULL;
|
if (index >= count)
|
||||||
uint32_t payload_size = 0u;
|
|
||||||
|
|
||||||
if (index >= count || !ikv__write_binary_node_memory(node, &payload, &payload_size))
|
|
||||||
{
|
{
|
||||||
if (payload)
|
|
||||||
IKV_FREE(payload);
|
|
||||||
for (uint32_t i = 0; i < index; ++i)
|
|
||||||
{
|
|
||||||
IKV_FREE(entries[i].payload_data);
|
|
||||||
}
|
|
||||||
IKV_FREE(entries);
|
IKV_FREE(entries);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -292,8 +294,7 @@ static bool ikv2_collect_root_entries(const ikv_node_t *root, ikv2_index_entry_t
|
|||||||
entries[index].key_length = (uint32_t)strlen(entries[index].key);
|
entries[index].key_length = (uint32_t)strlen(entries[index].key);
|
||||||
entries[index].key_hash = ikv2_hash_key(entries[index].key);
|
entries[index].key_hash = ikv2_hash_key(entries[index].key);
|
||||||
entries[index].type = (uint8_t)node->type;
|
entries[index].type = (uint8_t)node->type;
|
||||||
entries[index].payload_data = payload;
|
entries[index].source_node = node;
|
||||||
entries[index].payload_size = payload_size;
|
|
||||||
entries[index].next_in_bucket = IKV2_INDEX_NONE;
|
entries[index].next_in_bucket = IKV2_INDEX_NONE;
|
||||||
|
|
||||||
++index;
|
++index;
|
||||||
@@ -311,8 +312,6 @@ static bool ikv2_build_indexed_binary(const ikv_node_t *root, uint8_t **out_data
|
|||||||
ikv2_index_entry_t *entries = NULL;
|
ikv2_index_entry_t *entries = NULL;
|
||||||
uint32_t entry_count = 0u;
|
uint32_t entry_count = 0u;
|
||||||
uint32_t header_size = 0u;
|
uint32_t header_size = 0u;
|
||||||
uint32_t payload_base = 0u;
|
|
||||||
uint32_t total_size = 0u;
|
|
||||||
const char *root_key = NULL;
|
const char *root_key = NULL;
|
||||||
uint32_t root_key_length = 0u;
|
uint32_t root_key_length = 0u;
|
||||||
ikv2_buffer_t buffer = {0};
|
ikv2_buffer_t buffer = {0};
|
||||||
@@ -335,17 +334,10 @@ static bool ikv2_build_indexed_binary(const ikv_node_t *root, uint8_t **out_data
|
|||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
for (uint32_t i = 0; i < entry_count; ++i)
|
||||||
header_size += ikv2_varu32_size(entries[i].key_length) + entries[i].key_length;
|
header_size += ikv2_varu32_size(entries[i].key_length) + entries[i].key_length;
|
||||||
header_size += entry_count * (1u + 4u + 4u);
|
header_size += entry_count * (1u + 4u + 4u);
|
||||||
payload_base = header_size;
|
buffer.capacity = header_size;
|
||||||
total_size = header_size;
|
buffer.data = header_size ? (uint8_t *)IKV_MALLOC(header_size) : NULL;
|
||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
if (header_size > 0u && !buffer.data)
|
||||||
total_size += entries[i].payload_size;
|
|
||||||
|
|
||||||
buffer.data = total_size ? (uint8_t *)IKV_MALLOC(total_size) : NULL;
|
|
||||||
buffer.capacity = total_size;
|
|
||||||
if (total_size > 0u && !buffer.data)
|
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
|
||||||
IKV_FREE(entries[i].payload_data);
|
|
||||||
IKV_FREE(entries);
|
IKV_FREE(entries);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -362,21 +354,40 @@ static bool ikv2_build_indexed_binary(const ikv_node_t *root, uint8_t **out_data
|
|||||||
|
|
||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
for (uint32_t i = 0; i < entry_count; ++i)
|
||||||
{
|
{
|
||||||
|
entries[i].metadata_offset = (uint32_t)buffer.size;
|
||||||
ikv2_buffer_write_u8(&buffer, entries[i].type);
|
ikv2_buffer_write_u8(&buffer, entries[i].type);
|
||||||
ikv2_buffer_write_u32le(&buffer, payload_base);
|
ikv2_buffer_write_u32le(&buffer, 0u);
|
||||||
ikv2_buffer_write_u32le(&buffer, entries[i].payload_size);
|
ikv2_buffer_write_u32le(&buffer, 0u);
|
||||||
payload_base += entries[i].payload_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
for (uint32_t i = 0; i < entry_count; ++i)
|
||||||
{
|
{
|
||||||
const uint8_t *payload = entries[i].payload_data;
|
size_t payload_start = buffer.size;
|
||||||
ikv2_buffer_write_bytes(&buffer, payload, entries[i].payload_size);
|
|
||||||
|
if (!ikv__write_binary_node_append(entries[i].source_node, &buffer.data, &buffer.size, &buffer.capacity))
|
||||||
|
{
|
||||||
|
buffer.ok = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload_start > 0xFFFFFFFFu || buffer.size - payload_start > 0xFFFFFFFFu)
|
||||||
|
{
|
||||||
|
buffer.ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[i].payload_offset = (uint32_t)payload_start;
|
||||||
|
entries[i].payload_size = (uint32_t)(buffer.size - payload_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.ok)
|
||||||
|
{
|
||||||
for (uint32_t i = 0; i < entry_count; ++i)
|
for (uint32_t i = 0; i < entry_count; ++i)
|
||||||
{
|
{
|
||||||
IKV_FREE(entries[i].payload_data);
|
uint8_t *metadata = buffer.data + entries[i].metadata_offset + 1u;
|
||||||
|
ikv2_patch_u32le(metadata, entries[i].payload_offset);
|
||||||
|
ikv2_patch_u32le(metadata + 4u, entries[i].payload_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IKV_FREE(entries);
|
IKV_FREE(entries);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user