From 8615cbeab8c43ea0ad4b5bf865792813ebcb06f0 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Thu, 12 Mar 2020 20:00:35 -0500 Subject: [PATCH] Make final changes and add write up --- README.md | 38 +++++++++++++ benchmark/benchmark.c | 38 +++++++++++++ docs/meson.build => benchmark/benchmark.h | 0 benchmark/meson.build | 28 ++++++++++ include/toption/toption.h | 3 ++ lib/toption.c | 26 +++++---- meson.build | 4 +- meson_options.txt | 4 +- tests/toption.c | 66 +++++++++++------------ 9 files changed, 156 insertions(+), 51 deletions(-) create mode 100644 benchmark/benchmark.c rename docs/meson.build => benchmark/benchmark.h (100%) create mode 100644 benchmark/meson.build diff --git a/README.md b/README.md index 1be0fdb..77dd800 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,44 @@ GLib lacks a good way to represent an optional type, so this library provides an optional type to use in conjunction with GLib. +## Purpose + +This library is kind of theoretical. It stemmed from me writing an API wrapper +using GLib. API requests typically have notions of nullability whether it is +request bodies, response bodies, or query parameters. GLib by itself does not +include a good way to have include nullable types. Insert `TOption`. I wanted to +see if this could be a good way to implement an optional type for GLib, but it +isn't at least according to the extremely naiive benchmark I wrote which seems +to report `TOption` as 50% to 60% slower than its raw C alternative. + +### The Raw C Alternative + +The raw C alternative is opinionated I would think, but I have come to the +conclustion that the closest thing to `TOption` functionality is this: + +```c +g_autofree int *x = NULL; +if (the_value_exists) { + x = g_malloc(sizeof(int)); + memcpy(x, &i, sizeof(int)); + g_assert(i == *x); +} else { + // Do things with NULLability +} +``` + +Why I believe that this is equivalent functionally is that, in the case of a +none, you simply wouldn't do the `g_malloc/memcpy`, while in the case of +existence, you simply allocate memory and copy the value to it which is +essentially what `TOption` is doing at a higher-level of abstraction. At the +very least this library served as a testbed for an idea, and helped me come up +with a better alternative. + +## Lessons Learned + +1. Don't take higher-level type systems for granted (Rust for example) +2. Abstractions can bite you in the butt real quick if not used properly + ## Usage ```c diff --git a/benchmark/benchmark.c b/benchmark/benchmark.c new file mode 100644 index 0000000..1e59b8d --- /dev/null +++ b/benchmark/benchmark.c @@ -0,0 +1,38 @@ +#include + +#include + +#include "toption/toption.h" + +#define ITERATIONS 10000 + +int +main(void) +{ + GTimer *timer = g_timer_new(); + g_timer_stop(timer); + + g_timer_start(timer); + for (int i = 0; i < ITERATIONS; i++) { + g_autofree int *x = g_malloc(sizeof(int)); + memcpy(x, &i, sizeof(int)); + g_assert(i == *x); + } + g_timer_stop(timer); + + gdouble elapsed = g_timer_elapsed(timer, NULL); + g_print("Using a pointer as an optional type: %f\n", elapsed); + + g_timer_start(timer); + for (int i = 0; i < ITERATIONS; i++) { + TOption *o = t_option_from(i); + g_assert(i == t_option_get_int(o)); + t_option_unref(o); + } + g_timer_stop(timer); + + elapsed = g_timer_elapsed(timer, NULL); + g_print("Using a TOption as an optional type: %f\n", elapsed); + + return 0; +} diff --git a/docs/meson.build b/benchmark/benchmark.h similarity index 100% rename from docs/meson.build rename to benchmark/benchmark.h diff --git a/benchmark/meson.build b/benchmark/meson.build new file mode 100644 index 0000000..3c57921 --- /dev/null +++ b/benchmark/meson.build @@ -0,0 +1,28 @@ +dependencies = [ + libgio_dep, + toption_dep, +] + +c_args = [ + '-DG_LOG_DOMAIN="toption-testing"', + '-flto', +] + +if get_option('buildtype') == 'release' + c_args += [ + '-DG_DISABLE_CHECKS', + '-DG_DISABLE_CAST_CHECKS', + ] +elif get_option('buildtype').startswith('debug') + c_args += [ + '-DG_ENABLE_DEBUG', + ] +endif + +executable( + 'toption-benchmark', + 'benchmark.c', + dependencies: dependencies, + c_args: c_args, + include_directories: include_directories(join_paths(include_root)), +) diff --git a/include/toption/toption.h b/include/toption/toption.h index a565b7d..2ecbe2e 100644 --- a/include/toption/toption.h +++ b/include/toption/toption.h @@ -58,4 +58,7 @@ gfloat t_option_get_float(TOption *self); gdouble t_option_get_double(TOption *self); gpointer t_option_get_pointer(TOption *self); +G_DEFINE_BOXED_TYPE(TOption, t_option, t_option_ref, t_option_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC(TOption, t_option_unref) + G_END_DECLS diff --git a/lib/toption.c b/lib/toption.c index cfab906..4967b1c 100644 --- a/lib/toption.c +++ b/lib/toption.c @@ -28,8 +28,6 @@ struct _TOption volatile gint ref_count; }; -G_DEFINE_BOXED_TYPE(TOption, t_option, t_option_ref, t_option_unref) - TOption * t_option_ref(TOption *self) { @@ -99,7 +97,7 @@ t_option_contains(TOption *self, const GType type) TOption * t_option_none() { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -113,7 +111,7 @@ out: TOption * t_option_from_boolean(gboolean value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -128,7 +126,7 @@ out: TOption * t_option_from_char(gchar value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -143,7 +141,7 @@ out: TOption * t_option_from_uchar(guchar value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -158,7 +156,7 @@ out: TOption * t_option_from_string(gchararray value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -173,7 +171,7 @@ out: TOption * t_option_from_int(gint value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -188,7 +186,7 @@ out: TOption * t_option_from_int64(gint64 value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -203,7 +201,7 @@ out: TOption * t_option_from_uint(guint value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -218,7 +216,7 @@ out: TOption * t_option_from_uint64(guint64 value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -233,7 +231,7 @@ out: TOption * t_option_from_float(gfloat value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -248,7 +246,7 @@ out: TOption * t_option_from_double(gdouble value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; @@ -263,7 +261,7 @@ out: TOption * t_option_from_pointer(gpointer value) { - TOption *self = g_try_new0(TOption, 1); + TOption *self = g_slice_alloc0(sizeof(TOption)); if (!self) goto out; diff --git a/meson.build b/meson.build index 4b644eb..d1134ce 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,6 @@ subdir('lib') if get_option('tests') subdir('tests') endif -if get_option('docs') - subdir('docs') +if get_option('benchmark') + subdir('benchmark') endif diff --git a/meson_options.txt b/meson_options.txt index dd12c16..ee6b856 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,4 +1,4 @@ option('tests', type: 'boolean', value: true, description: 'Build the test suite') -option('docs', type: 'boolean', value: true, - description: 'Build the documentation') +option('benchmark', type: 'boolean', value: false, + description: 'Build the benchmark') diff --git a/tests/toption.c b/tests/toption.c index 46be24f..ef6df83 100644 --- a/tests/toption.c +++ b/tests/toption.c @@ -8,9 +8,9 @@ static void test_boolean() { - gboolean v = TRUE; - TOption *t = t_option_from(v); - const gboolean n = t_option_get_boolean(t); + gboolean v = TRUE; + g_autoptr(TOption) t = t_option_from(v); + const gboolean n = t_option_get_boolean(t); g_assert_true(t && n); } @@ -18,9 +18,9 @@ test_boolean() static void test_char() { - gchar v = TEST_NUMBER; - TOption *t = t_option_from(v); - const int n = t_option_get_char(t); + gchar v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const int n = t_option_get_char(t); g_assert_true(t && n == v); } @@ -28,9 +28,9 @@ test_char() static void test_uchar() { - guchar v = TRUE; - TOption *t = t_option_from(v); - const guchar n = t_option_get_uchar(t); + guchar v = TRUE; + g_autoptr(TOption) t = t_option_from(v); + const guchar n = t_option_get_uchar(t); g_assert_true(t && n == v); } @@ -38,9 +38,9 @@ test_uchar() static void test_string() { - gchararray v = TEST_STRING; - TOption *t = t_option_from(v); - const gchararray n = t_option_get_string(t); + gchararray v = TEST_STRING; + g_autoptr(TOption) t = t_option_from(v); + const gchararray n = t_option_get_string(t); g_assert_true(t && n == v); } @@ -48,9 +48,9 @@ test_string() static void test_int() { - gint v = TEST_NUMBER; - TOption *t = t_option_from(v); - const gint n = t_option_get_int(t); + gint v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const gint n = t_option_get_int(t); g_assert_true(t && n == v); } @@ -58,9 +58,9 @@ test_int() static void test_int64() { - guint64 v = TEST_NUMBER; - TOption *t = t_option_from(v); - const guint64 n = t_option_get_uint64(t); + guint64 v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const guint64 n = t_option_get_uint64(t); g_assert_true(t && n == v); } @@ -68,9 +68,9 @@ test_int64() static void test_uint() { - guint v = TEST_NUMBER; - TOption *t = t_option_from(v); - const guint n = t_option_get_uint(t); + guint v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const guint n = t_option_get_uint(t); g_assert_true(t && n == v); } @@ -78,9 +78,9 @@ test_uint() static void test_uint64() { - guint64 v = TEST_NUMBER; - TOption *t = t_option_from(v); - const guint64 n = t_option_get_uint64(t); + guint64 v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const guint64 n = t_option_get_uint64(t); g_assert_true(t && n == v); } @@ -88,9 +88,9 @@ test_uint64() static void test_float() { - gfloat v = TEST_NUMBER; - TOption *t = t_option_from(v); - const gfloat n = t_option_get_float(t); + gfloat v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const gfloat n = t_option_get_float(t); g_assert_true(t && n == TEST_NUMBER); } @@ -98,9 +98,9 @@ test_float() static void test_double() { - gdouble v = TEST_NUMBER; - TOption *t = t_option_from(v); - const gdouble n = t_option_get_double(t); + gdouble v = TEST_NUMBER; + g_autoptr(TOption) t = t_option_from(v); + const gdouble n = t_option_get_double(t); g_assert_true(t && n == TEST_NUMBER); } @@ -108,9 +108,9 @@ test_double() static void test_pointer() { - gpointer v = &test_pointer; - TOption *t = t_option_from(v); - gconstpointer n = t_option_get_pointer(t); + gpointer v = &test_pointer; + g_autoptr(TOption) t = t_option_from(v); + gconstpointer n = t_option_get_pointer(t); g_assert_true(t && n == test_pointer); } -- 2.45.2