optimize cxJsonObjGet() part 1 - binary search

Sun, 29 Dec 2024 18:03:21 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 29 Dec 2024 18:03:21 +0100
changeset 1067
7799addf475f
parent 1066
8610f87a6b14
child 1068
78ceee8e9b34

optimize cxJsonObjGet() part 1 - binary search

part 2 will be the index buffer to preserve the order

relates to #462

src/Makefile file | annotate | diff | comparison | revisions
src/cx/json.h file | annotate | diff | comparison | revisions
src/json.c file | annotate | diff | comparison | revisions
--- a/src/Makefile	Sun Dec 29 17:45:56 2024 +0100
+++ b/src/Makefile	Sun Dec 29 18:03:21 2024 +0100
@@ -98,7 +98,7 @@
 
 $(build_dir)/json$(OBJ_EXT): json.c cx/json.h cx/common.h cx/allocator.h \
  cx/string.h cx/buffer.h cx/array_list.h cx/list.h cx/collection.h \
- cx/iterator.h cx/compare.h
+ cx/iterator.h cx/compare.h cx/compare.h
 	@echo "Compiling $<"
 	$(CC) -o $@ $(CFLAGS)  -c $<
 
--- a/src/cx/json.h	Sun Dec 29 17:45:56 2024 +0100
+++ b/src/cx/json.h	Sun Dec 29 18:03:21 2024 +0100
@@ -345,6 +345,13 @@
     CxJsonValue *parsed;
 
     /**
+     * A pointer to an intermediate state of a currently parsed object member.
+     *
+     * Never access this value manually.
+     */
+    CxJsonObjValue uncompleted_member;
+
+    /**
      * State stack.
      */
     CX_ARRAY_DECLARE_SIZED(int, states, unsigned);
--- a/src/json.c	Sun Dec 29 17:45:56 2024 +0100
+++ b/src/json.c	Sun Dec 29 18:03:21 2024 +0100
@@ -27,6 +27,7 @@
  */
 
 #include "cx/json.h"
+#include "cx/compare.h"
 
 #include <string.h>
 #include <ctype.h>
@@ -41,6 +42,39 @@
 
 static CxJsonValue cx_json_value_nothing = {.type = CX_JSON_NOTHING};
 
+static int json_cmp_objvalue(const void *l, const void *r) {
+    const CxJsonObjValue *left = l;
+    const CxJsonObjValue *right = r;
+    return cx_strcmp(cx_strcast(left->name), cx_strcast(right->name));
+}
+
+static CxJsonObjValue *json_find_objvalue(const CxJsonValue *obj, cxstring name) {
+    assert(obj->type == CX_JSON_OBJECT);
+    CxJsonObjValue kv_dummy;
+    kv_dummy.name = cx_mutstrn((char*) name.ptr, name.length);
+    size_t index = cx_array_binary_search(
+            obj->value.object.values,
+            obj->value.object.values_size,
+            sizeof(CxJsonObjValue),
+            &kv_dummy,
+            json_cmp_objvalue
+    );
+    if (index == obj->value.object.values_size) {
+        return NULL;
+    } else {
+        return &obj->value.object.values[index];
+    }
+}
+
+static int json_add_objvalue(CxJsonValue *obj, CxJsonObjValue member) {
+    assert(obj->type == CX_JSON_OBJECT);
+    CxArrayReallocator value_realloc = cx_array_reallocator(obj->allocator, NULL);
+    return cx_array_simple_add_sorted_a(
+        &value_realloc, obj->value.object.values,
+        member, json_cmp_objvalue
+    );
+}
+
 static void token_destroy(CxJsonToken *token) {
     if (token->allocated) {
         cx_strfree(&token->content);
@@ -294,16 +328,10 @@
     // initialize the value
     if (type == CX_JSON_ARRAY) {
         cx_array_initialize_a(json->allocator, v->value.array.array, 16);
-        if (v->value.array.array == NULL) { // LCOV_EXCL_START
-            cxFree(json->allocator, v);
-            return NULL;
-        } // LCOV_EXCL_STOP
+        if (v->value.array.array == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
     } else if (type == CX_JSON_OBJECT) {
         cx_array_initialize_a(json->allocator, v->value.object.values, 16);
-        if (v->value.object.values == NULL) { // LCOV_EXCL_START
-            cxFree(json->allocator, v);
-            return NULL;
-        } // LCOV_EXCL_STOP
+        if (v->value.object.values == NULL) goto create_json_value_exit_error; // LCOV_EXCL_LINE
     } else {
         memset(v, 0, sizeof(CxJsonValue));
     }
@@ -311,15 +339,22 @@
     v->allocator = json->allocator;
 
     // add the new value to a possible parent
-    CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
     if (json->vbuf_size > 0) {
         CxJsonValue *parent = json->vbuf[json->vbuf_size - 1];
+        assert(parent != NULL);
         if (parent->type == CX_JSON_ARRAY) {
-            cx_array_simple_add_a(&value_realloc, parent->value.array.array, v);
+            CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
+            if (cx_array_simple_add_a(&value_realloc, parent->value.array.array, v)) {
+                goto create_json_value_exit_error; // LCOV_EXCL_LINE
+            }
         } else if (parent->type == CX_JSON_OBJECT) {
-            assert(parent->value.object.values_size > 0);
-            assert(parent->value.object.values[parent->value.object.values_size - 1].value == NULL);
-            parent->value.object.values[parent->value.object.values_size - 1].value = v;
+            // the member was already created after parsing the name
+            assert(json->uncompleted_member.name.ptr != NULL);
+            json->uncompleted_member.value = v;
+            if (json_add_objvalue(parent, json->uncompleted_member))  {
+                goto create_json_value_exit_error; // LCOV_EXCL_LINE
+            }
+            json->uncompleted_member.name = (cxmutstr) {NULL, 0};
         } else {
             assert(false); // LCOV_EXCL_LINE
         }
@@ -329,10 +364,8 @@
     if (type == CX_JSON_ARRAY || type == CX_JSON_OBJECT) {
         CxArrayReallocator vbuf_realloc = cx_array_reallocator(NULL, json->vbuf_internal);
         if (cx_array_simple_add_a(&vbuf_realloc, json->vbuf, v)) {
-            // LCOV_EXCL_START
-            cxFree(json->allocator, v);
-            return NULL;
-        } // LCOV_EXCL_STOP
+            goto create_json_value_exit_error; // LCOV_EXCL_LINE
+        }
     }
 
     // if currently no value is parsed, this is now the value of interest
@@ -341,6 +374,11 @@
     }
 
     return v;
+    // LCOV_EXCL_START
+create_json_value_exit_error:
+    cxFree(json->allocator, v);
+    return NULL;
+    // LCOV_EXCL_STOP
 }
 
 #define JP_STATE_VALUE_BEGIN         0
@@ -530,15 +568,9 @@
             if (name.ptr == NULL) {
                 return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
             }
-            CxJsonObjValue kv = {name, NULL};
+            assert(json->uncompleted_member.name.ptr == NULL);
+            json->uncompleted_member.name = name;
             assert(json->vbuf_size > 0);
-            CxJsonValue *parent = json->vbuf[json->vbuf_size - 1];
-            assert(parent != NULL);
-            assert(parent->type == CX_JSON_OBJECT);
-            CxArrayReallocator value_realloc = cx_array_reallocator(json->allocator, NULL);
-            if (cx_array_simple_add_a(&value_realloc, parent->value.object.values, kv)) {
-                return_rec(CX_JSON_VALUE_ALLOC_FAILED); // LCOV_EXCL_LINE
-            }
 
             // next state
             json_add_state(json, JP_STATE_OBJ_COLON);
@@ -795,12 +827,10 @@
         }
     }
 
-    CxArrayReallocator value_realloc = cx_array_reallocator(obj->allocator, NULL);
-    assert(obj->type == CX_JSON_OBJECT);
     cxmutstr k = cx_strdup_a(obj->allocator, name);
     if (k.ptr == NULL) return -1;
     CxJsonObjValue kv = {k, child};
-    return cx_array_simple_add_a(&value_realloc, obj->value.object.values, kv);
+    return json_add_objvalue(obj, kv);
 }
 
 CxJsonValue* cxJsonObjPutObj(CxJsonValue* obj, cxstring name) {
@@ -893,12 +923,10 @@
 }
 
 CxJsonValue *cx_json_obj_get_cxstr(const CxJsonValue *value, cxstring name) {
-    const CxJsonObject *obj = &(value->value.object);
-    // TODO: think about sorting the object so that we can use binary search here
-    for (size_t i = 0; i < obj->values_size; i++) {
-        if (0 == cx_strcmp(name, cx_strcast(obj->values[i].name))) {
-            return obj->values[i].value;
-        }
+    CxJsonObjValue *member = json_find_objvalue(value, name);
+    if (member == NULL) {
+        return &cx_json_value_nothing;
+    } else {
+        return member->value;
     }
-    return &cx_json_value_nothing;
 }

mercurial