diff --git a/benchmark/benchmark.c b/benchmark/benchmark.c index 43ec2978d..1c1be724f 100644 --- a/benchmark/benchmark.c +++ b/benchmark/benchmark.c @@ -357,6 +357,8 @@ main(int argc, char** argv) // Object client benchmark_distributed_object(); benchmark_object(); + // additional read-write benches + benchmark_object_rw(); // DB client benchmark_db_entry(); diff --git a/benchmark/benchmark.h b/benchmark/benchmark.h index 091c6b914..5a74be43c 100644 --- a/benchmark/benchmark.h +++ b/benchmark/benchmark.h @@ -56,6 +56,7 @@ void benchmark_kv(void); void benchmark_distributed_object(void); void benchmark_object(void); +void benchmark_object_rw(void); void benchmark_db_entry(void); void benchmark_db_iterator(void); diff --git a/benchmark/object/bench-object-rw.c b/benchmark/object/bench-object-rw.c new file mode 100644 index 000000000..e0178f3b9 --- /dev/null +++ b/benchmark/object/bench-object-rw.c @@ -0,0 +1,500 @@ +/* + * JULEA - Flexible storage framework + * Copyright (C) 2017-2024 Michael Kuhn + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +#include + +#include + +#include + +#include +#include + +#include "benchmark.h" + +#define NUM_SIZES 7 + +#define NUM_RW_RATIOS 5 + +#define READ_FLAG 0 + +#define WRITE_FLAG 1 + +#define min(x, y) (((x) <= (y)) ? (x) : (y)) + +static guint index = 0; + +static guint rw_index = 0; + +static const guint BLOCK_SIZES[NUM_SIZES] = { + 1 * 1024, + 2 * 1024, + 4 * 1024, + 16 * 1024, + 64 * 1024, + 512 * 1024, + 1 * 1024 * 1024, +}; + +static const gdouble RW_RATIOS[NUM_RW_RATIOS] = { 0.0, 0.25, 0.5, 0.75, 1.0 }; + +static const guint N = 1000; + +static void +_benchmark_object_read_write(BenchmarkRun* run, gboolean use_batch, guint* pattern, guint* rw_pattern, guint block_size, guint n) +{ + g_autoptr(JObject) object = NULL; + g_autoptr(JBatch) batch = NULL; + g_autoptr(JSemantics) semantics = NULL; + g_autofree gchar* dummy = NULL; + guint64 nb = 0; + gboolean ret; + + dummy = g_malloc0(block_size); + + semantics = j_benchmark_get_semantics(); + batch = j_batch_new(semantics); + + object = j_object_new("benchmark", "benchmark"); + j_object_create(object, batch); + + for (guint i = 0; i < n; i++) + { + j_object_write(object, dummy, block_size, i * block_size, &nb, batch); + } + + ret = j_batch_execute(batch); + g_assert_true(ret); + g_assert_cmpuint(nb, ==, ((guint64)n) * block_size); + + j_benchmark_timer_start(run); + + while (j_benchmark_iterate(run)) + { + for (guint i = 0; i < n; i++) + { + // decide read or write + if (rw_pattern[i] == 0) + { + j_object_read(object, dummy, block_size, pattern[i] * block_size, &nb, batch); + } + else + { + j_object_write(object, dummy, block_size, pattern[i] * block_size, &nb, batch); + } + + if (!use_batch) + { + ret = j_batch_execute(batch); + g_assert_true(ret); + g_assert_cmpuint(nb, ==, block_size); + } + } + + if (use_batch) + { + guint64 expected = n; + ret = j_batch_execute(batch); + expected *= block_size; + g_assert_true(ret); + g_assert_cmpuint(nb, ==, expected); + } + } + + j_benchmark_timer_stop(run); + + j_object_delete(object, batch); + ret = j_batch_execute(batch); + g_assert_true(ret); + + run->operations = n; + run->bytes = n * block_size; +} + +static void +_benchmark_object_write(BenchmarkRun* run, gboolean use_batch, guint* pattern, guint block_size, guint n) +{ + g_autoptr(JObject) object = NULL; + g_autoptr(JBatch) batch = NULL; + g_autoptr(JSemantics) semantics = NULL; + g_autofree gchar* dummy = NULL; + guint64 nb = 0; + gboolean ret; + + dummy = g_malloc0(block_size); + + semantics = j_benchmark_get_semantics(); + batch = j_batch_new(semantics); + + object = j_object_new("benchmark", "benchmark"); + j_object_create(object, batch); + ret = j_batch_execute(batch); + g_assert_true(ret); + + j_benchmark_timer_start(run); + + while (j_benchmark_iterate(run)) + { + for (guint i = 0; i < n; i++) + { + j_object_write(object, dummy, block_size, pattern[i] * block_size, &nb, batch); + + if (!use_batch) + { + ret = j_batch_execute(batch); + g_assert_true(ret); + g_assert_cmpuint(nb, ==, block_size); + } + } + + if (use_batch) + { + ret = j_batch_execute(batch); + g_assert_true(ret); + g_assert_cmpuint(nb, ==, ((guint64)n) * block_size); + } + } + + j_benchmark_timer_stop(run); + + j_object_delete(object, batch); + ret = j_batch_execute(batch); + g_assert_true(ret); + + run->operations = n; + run->bytes = n * block_size; +} + +/* +Fills a buffer of a given length with incrementing values starting at 0. +*/ +static void +generate_pattern_seq(guint* buf, guint len) +{ + guint i; + for (i = 0; i < len; i++) + { + buf[i] = i; + } +} + +static void +shuffle(guint* buf, guint len) +{ + // set seed + srand(42); + + // shuffle + for (guint i = 0; i < len; i++) + { + guint j = rand() % (i + 1); + + guint temp = buf[j]; + buf[j] = buf[i]; + buf[i] = temp; + } +} + +static void +generate_rw_pattern(guint* buf, guint n, gdouble rw_ratio) +{ + guint read_ops = 0; + if (rw_ratio < 0.0001) + { + read_ops = n; + } + else if (rw_ratio > 0.9999) + { + read_ops = 0; + } + else + { + read_ops = (int)(rw_ratio * n); + } + + for (guint i = 0; i < n; i++) + { + if (i < read_ops) + { + buf[i] = READ_FLAG; + } + else + { + buf[i] = WRITE_FLAG; + } + } + + shuffle(buf, n); +} + +/* +Fills a buffer of a given length with the values in the range [0, len) shuffled using a set seed. +*/ +static void +generate_pattern_rand(guint* buf, guint len) +{ + // insert sequence + generate_pattern_seq(buf, len); + + shuffle(buf, len); +} + +/* +Calculates the nth harmonic number +`H_N = 𝚺(k=1, N) 1/k`. +*/ +static double +calc_harmonic(guint n) +{ + double sum = 0.0; + for (guint i = 1; i <= n; i++) + { + sum += (1.0 / i); + } + + return sum; +} + +/* +Fills a buffer of a given length with values in the range of [0, len] +using a distribution following Zipf's law. + +This is realized by adding the same value multiple times +based on the frequency derived from the value's rank. + +The range [0, len) serves as a basis with the range being considered to already be ordered. + +The resulting range is then shuffled. +*/ +static void +generate_pattern_zipf(guint* buf, guint len) +{ + guint j = 0; + const double HARMONIC = calc_harmonic(len); + + // generate template of randomly ordered indices + g_autofree guint* template = g_malloc0(len * sizeof(guint)); + generate_pattern_rand(template, len); + + // apply distribution + for (guint i = 0; i < len; i++) + { + double f = (1 / HARMONIC) * (1.0 / (i + 1)); + + // always round up to get atleast 1 occurence + guint n_occurences = (f * len) + 1; + + for (guint k = j; k < min(len, j + n_occurences); k++) + { + buf[k] = template[i]; + } + + j += n_occurences; + + if (j >= len) + { + break; + } + } + + // shuffle distribution + shuffle(buf, len); +} + +static void +benchmark_object_write(BenchmarkRun* run, gboolean use_batch, void (*generator)(guint*, guint)) +{ + guint n = (use_batch) ? 10 * N : N; + guint block_size = BLOCK_SIZES[index]; + g_autofree guint* pattern = g_malloc0(n * sizeof(guint)); + + index = (index + 1) % NUM_SIZES; + + (*generator)(pattern, n); + + _benchmark_object_write(run, use_batch, pattern, block_size, n); +} + +static void +benchmark_object_read_write(BenchmarkRun* run, gboolean use_batch, void (*generator)(guint*, guint)) +{ + guint n = (use_batch) ? 10 * N : N; + guint block_size = BLOCK_SIZES[index]; + gfloat ratio = RW_RATIOS[rw_index]; + g_autofree guint* pattern = g_malloc0(n * sizeof(guint)); + g_autofree guint* rw_pattern = g_malloc0(n * sizeof(guint)); + rw_index = (rw_index + 1) % NUM_RW_RATIOS; + + if (rw_index == 0) + { + index = (index + 1) % NUM_SIZES; + } + + generate_rw_pattern(rw_pattern, n, ratio); + (*generator)(pattern, n); + + _benchmark_object_read_write(run, use_batch, pattern, rw_pattern, block_size, n); +} + +// WRITE + +static void +benchmark_object_write_seq(BenchmarkRun* run) +{ + benchmark_object_write(run, FALSE, generate_pattern_seq); +} + +static void +benchmark_object_write_batch_seq(BenchmarkRun* run) +{ + benchmark_object_write(run, TRUE, generate_pattern_seq); +} + +static void +benchmark_object_write_rand(BenchmarkRun* run) +{ + benchmark_object_write(run, FALSE, generate_pattern_rand); +} + +static void +benchmark_object_write_rand_batch(BenchmarkRun* run) +{ + benchmark_object_write(run, TRUE, generate_pattern_rand); +} + +static void +benchmark_object_write_zipf(BenchmarkRun* run) +{ + benchmark_object_write(run, FALSE, generate_pattern_zipf); +} + +static void +benchmark_object_write_zipf_batch(BenchmarkRun* run) +{ + benchmark_object_write(run, TRUE, generate_pattern_zipf); +} + +// RW + +static void +benchmark_object_rw_seq(BenchmarkRun* run) +{ + benchmark_object_read_write(run, FALSE, generate_pattern_seq); +} + +static void +benchmark_object_rw_batch_seq(BenchmarkRun* run) +{ + benchmark_object_read_write(run, TRUE, generate_pattern_seq); +} + +static void +benchmark_object_rw_rand(BenchmarkRun* run) +{ + benchmark_object_read_write(run, FALSE, generate_pattern_rand); +} + +static void +benchmark_object_rw_rand_batch(BenchmarkRun* run) +{ + benchmark_object_read_write(run, TRUE, generate_pattern_rand); +} + +static void +benchmark_object_rw_zipf(BenchmarkRun* run) +{ + benchmark_object_read_write(run, FALSE, generate_pattern_zipf); +} + +static void +benchmark_object_rw_zipf_batch(BenchmarkRun* run) +{ + benchmark_object_read_write(run, TRUE, generate_pattern_zipf); +} + +static void +add_rw_benches(const gchar* path, BenchmarkFunc benchmark) +{ + for (guint i = 0; i < NUM_SIZES; i++) + { + for (guint j = 0; j < NUM_RW_RATIOS; j++) + { + g_autofree gchar* buf = g_malloc0(64 * sizeof(gchar)); + if (BLOCK_SIZES[i] >= 1024 * 1024) + { + g_snprintf(buf, 64ul, "%s <%.2fw> %dMiB", path, (double)RW_RATIOS[j], BLOCK_SIZES[i] / (1024 * 1024)); + } + else + { + g_snprintf(buf, 64ul, "%s <%.2fw> %dKiB", path, (double)RW_RATIOS[j], BLOCK_SIZES[i] / 1024); + } + + j_benchmark_add(buf, benchmark); + } + } +} + +static void +add_benches(const gchar* path, BenchmarkFunc benchmark) +{ + for (guint i = 0; i < NUM_SIZES; i++) + { + g_autofree gchar* buf = g_malloc0(64 * sizeof(gchar)); + if (BLOCK_SIZES[i] >= 1024 * 1024) + { + g_snprintf(buf, 64ul, "%s %dMiB", path, BLOCK_SIZES[i] / (1024 * 1024)); + } + else + { + g_snprintf(buf, 64ul, "%s %dKiB", path, BLOCK_SIZES[i] / 1024); + } + j_benchmark_add(buf, benchmark); + } +} + +/* + * The mixed benchmark pre-writes the entire file to guarantee that it can be read from. + * This has performance implications as the relevant data will be (partially) present in the page cache. + * Some interfaces are also affected. To illustrate the problem on an example: + * 1. mmap empty file (assume the application artificially increases the size of the file to N bytes to avoid remapping later on) + * 2. write N+1 bytes to file + * - 3a. without pre-writing remapping is required + * - 3b. with pre-writing the remapping has already occured or has been mmap'ed at the appropriate size to begin with. No remapping is required. + * + * Therefore, the benchmark also includes pure write operations where no pre-writing takes place. + * This can be used to estimate the impact of pre-writing in the mixed benchmark by comparing the write benchmark with the corresponding mixed benchmark at ratio=1.00. +*/ +void +benchmark_object_rw(void) +{ + // MIXED + add_rw_benches("/object/object/rw/seq", benchmark_object_rw_seq); + add_rw_benches("/object/object/rw/seq-batch", benchmark_object_rw_batch_seq); + add_rw_benches("/object/object/rw/rand", benchmark_object_rw_rand); + add_rw_benches("/object/object/rw/rand-batch", benchmark_object_rw_rand_batch); + add_rw_benches("/object/object/rw/zipf", benchmark_object_rw_zipf); + add_rw_benches("/object/object/rw/zipf-batch", benchmark_object_rw_zipf_batch); + + // WRITE + add_benches("/object/object/rw/write-seq", benchmark_object_write_seq); + add_benches("/object/object/rw/write-seq-batch", benchmark_object_write_batch_seq); + add_benches("/object/object/rw/write-rand", benchmark_object_write_rand); + add_benches("/object/object/rw/write-rand-batch", benchmark_object_write_rand_batch); + add_benches("/object/object/rw/write-zipf", benchmark_object_write_zipf); + add_benches("/object/object/rw/write-zipf-batch", benchmark_object_write_zipf_batch); +} diff --git a/benchmark/object/object.c b/benchmark/object/object.c index 8dd8df960..8af6dfc50 100644 --- a/benchmark/object/object.c +++ b/benchmark/object/object.c @@ -228,149 +228,6 @@ benchmark_object_status_batch(BenchmarkRun* run) _benchmark_object_status(run, TRUE); } -static void -_benchmark_object_read(BenchmarkRun* run, gboolean use_batch, guint block_size) -{ - guint const n = (use_batch) ? 10000 : 1000; - - g_autoptr(JObject) object = NULL; - g_autoptr(JBatch) batch = NULL; - g_autoptr(JSemantics) semantics = NULL; - g_autofree gchar* dummy = NULL; - guint64 nb = 0; - gboolean ret; - - dummy = g_malloc0(block_size); - - semantics = j_benchmark_get_semantics(); - batch = j_batch_new(semantics); - - object = j_object_new("benchmark", "benchmark"); - j_object_create(object, batch); - - for (guint i = 0; i < n; i++) - { - j_object_write(object, dummy, block_size, i * block_size, &nb, batch); - } - - ret = j_batch_execute(batch); - g_assert_true(ret); - g_assert_cmpuint(nb, ==, n * block_size); - - j_benchmark_timer_start(run); - - while (j_benchmark_iterate(run)) - { - for (guint i = 0; i < n; i++) - { - j_object_read(object, dummy, block_size, i * block_size, &nb, batch); - - if (!use_batch) - { - ret = j_batch_execute(batch); - g_assert_true(ret); - g_assert_cmpuint(nb, ==, block_size); - } - } - - if (use_batch) - { - ret = j_batch_execute(batch); - g_assert_true(ret); - g_assert_cmpuint(nb, ==, n * block_size); - } - } - - j_benchmark_timer_stop(run); - - j_object_delete(object, batch); - ret = j_batch_execute(batch); - g_assert_true(ret); - - run->operations = n; - run->bytes = n * block_size; -} - -static void -benchmark_object_read(BenchmarkRun* run) -{ - _benchmark_object_read(run, FALSE, 4 * 1024); -} - -static void -benchmark_object_read_batch(BenchmarkRun* run) -{ - _benchmark_object_read(run, TRUE, 4 * 1024); -} - -static void -_benchmark_object_write(BenchmarkRun* run, gboolean use_batch, guint block_size) -{ - guint const n = (use_batch) ? 10000 : 1000; - - g_autoptr(JObject) object = NULL; - g_autoptr(JBatch) batch = NULL; - g_autoptr(JSemantics) semantics = NULL; - g_autofree gchar* dummy = NULL; - guint64 nb = 0; - gboolean ret; - - dummy = g_malloc0(block_size); - - semantics = j_benchmark_get_semantics(); - batch = j_batch_new(semantics); - - object = j_object_new("benchmark", "benchmark"); - j_object_create(object, batch); - ret = j_batch_execute(batch); - g_assert_true(ret); - - j_benchmark_timer_start(run); - - while (j_benchmark_iterate(run)) - { - for (guint i = 0; i < n; i++) - { - j_object_write(object, dummy, block_size, i * block_size, &nb, batch); - - if (!use_batch) - { - ret = j_batch_execute(batch); - g_assert_true(ret); - g_assert_cmpuint(nb, ==, block_size); - } - } - - if (use_batch) - { - ret = j_batch_execute(batch); - g_assert_true(ret); - g_assert_cmpuint(nb, ==, n * block_size); - } - } - - j_benchmark_timer_stop(run); - - j_object_delete(object, batch); - ret = j_batch_execute(batch); - g_assert_true(ret); - - run->operations = n; - run->bytes = n * block_size; -} - -static void -benchmark_object_write(BenchmarkRun* run) -{ - _benchmark_object_write(run, FALSE, 4 * 1024); -} - -static void -benchmark_object_write_batch(BenchmarkRun* run) -{ - _benchmark_object_write(run, TRUE, 4 * 1024); -} - static void _benchmark_object_unordered_create_delete(BenchmarkRun* run, gboolean use_batch) { @@ -438,10 +295,6 @@ benchmark_object(void) j_benchmark_add("/object/object/status", benchmark_object_status); j_benchmark_add("/object/object/status-batch", benchmark_object_status_batch); /// \todo get - j_benchmark_add("/object/object/read", benchmark_object_read); - j_benchmark_add("/object/object/read-batch", benchmark_object_read_batch); - j_benchmark_add("/object/object/write", benchmark_object_write); - j_benchmark_add("/object/object/write-batch", benchmark_object_write_batch); j_benchmark_add("/object/object/unordered-create-delete", benchmark_object_unordered_create_delete); j_benchmark_add("/object/object/unordered-create-delete-batch", benchmark_object_unordered_create_delete_batch); } diff --git a/meson.build b/meson.build index e5133417d..2ad42e386 100644 --- a/meson.build +++ b/meson.build @@ -585,6 +585,7 @@ julea_benchmark_srcs = files([ 'benchmark/message.c', 'benchmark/object/distributed-object.c', 'benchmark/object/object.c', + 'benchmark/object/bench-object-rw.c' ]) executable('julea-benchmark', julea_benchmark_srcs,