Eo: reduce memory usage across applications.
authorTom Hacohen <tom@stosb.com>
Mon, 28 Sep 2015 16:18:43 +0000 (17:18 +0100)
committerTom Hacohen <tom@stosb.com>
Mon, 28 Sep 2015 17:39:15 +0000 (18:39 +0100)
As described by Carsten in his email to edev ML titled:
"[E-devel] eo stability - i think we need to postpone that"
with the switch to Eo2 we significantly increased our usage of RW memory
pages, and thus significantly increased our memory usage when running
multiple applications.

The problem was that during the migration to Eo2 the op id cache and the
op description arrays were merged, causing the op description arrays to
no longer be RO. This patch enables users of Eo (mainly Eolian) to
declare those arrays as const (RO) again, saving that memory.

There might be performance implications with this patch. I had to remove
the op desc array sorting, and I used a hash table for the lookup. I
think the op desc sorting doesn't really affect performance because that
array is seldom accessed and is usually pretty short. The hash table
is not a problem either, because it's  behind the scenes, so it can be
changed to a more efficient data structure if the hash table is not good
enough. The hash table itself is also rarely accessed, so it's mostly
about memory.

Please keep an eye for any bugs, performance or excessive memory usage.
I believe this should be better on all fronts.

This commit *BREAKS ABI*.

@fix

src/bin/eolian/eo_generator.c
src/lib/eo/Eo.h
src/lib/eo/eo.c
src/tests/eo/suite/eo_test_call_errors.c
src/tests/eo/suite/eo_test_class_errors.c
src/tests/eolian/data/class_simple_ref.c
src/tests/eolian/data/override_ref.c

index 85d3cb7..2e4f545 100644 (file)
@@ -19,7 +19,7 @@ static Eina_Hash *_funcs_params_init = NULL;
 
 static const char
 tmpl_eo_ops_desc[] = "\
-static Eo_Op_Description _@#class_op_desc[] = {@#list_op\n};\n\n";
+static const Eo_Op_Description _@#class_op_desc[] = {@#list_op\n};\n\n";
 
 static const char
 tmpl_events_desc[] = "\
index da16feb..3f551f4 100644 (file)
@@ -132,6 +132,8 @@ enum _Eo_Op_Type
    EO_OP_TYPE_INVALID = -1, /**< Invalid op. */
    EO_OP_TYPE_REGULAR = 0, /**< Regular op. */
    EO_OP_TYPE_CLASS, /**< Class op - a class op. Like static in Java/C++. */
+   EO_OP_TYPE_REGULAR_OVERRIDE, /**< Regular op override (previously defined) */
+   EO_OP_TYPE_CLASS_OVERRIDE, /**< Class op override (previously defined) */
 };
 
 /**
@@ -344,7 +346,6 @@ typedef struct _Eo_Op_Description
 {
    void *api_func;         /**< The EAPI function offering this op. (The name of the func on windows) */
    void *func;             /**< The static function to call for the op. */
-   Eo_Op op;               /**< The op. */
    Eo_Op_Type op_type;     /**< The type of the Op. */
 } Eo_Op_Description;
 
@@ -360,7 +361,7 @@ struct _Eo_Class_Description
    const char *name; /**< The name of the class. */
    Eo_Class_Type type; /**< The type of the class. */
    struct {
-        Eo_Op_Description *descs; /**< The op descriptions array of size count. */
+        const Eo_Op_Description *descs; /**< The op descriptions array of size count. */
         size_t count; /**< Number of op descriptions. */
    } ops; /**< The ops description, should be filled using #EO_CLASS_DESCRIPTION_OPS (later sorted by Eo). */
    const Eo_Event_Description **events; /**< The event descriptions for this class. */
@@ -469,7 +470,7 @@ EAPI extern Eo_Hook_Call eo_hook_call_post;
      Eina_Bool ___is_main_loop = eina_main_loop_is();                   \
      static Eo_Op ___op = EO_NOOP;                                      \
      if (___op == EO_NOOP)                                              \
-       ___op = _eo_api_op_id_get(EO_FUNC_COMMON_OP_FUNC(Name), ___is_main_loop, __FILE__, __LINE__); \
+       ___op = _eo_api_op_id_get(EO_FUNC_COMMON_OP_FUNC(Name)); \
      if (!_eo_call_resolve(#Name, ___op, &___call, ___is_main_loop, __FILE__, __LINE__)) return DefRet; \
      _Eo_##Name##_func _func_ = (_Eo_##Name##_func) ___call.func;       \
 
@@ -522,22 +523,19 @@ EAPI extern Eo_Hook_Call eo_hook_call_post;
      EO_HOOK_CALL_PREPAREV(eo_hook_call_post, #Name, Arguments);        \
   }
 
-// OP ID of an overriding function
-#define EO_OP_OVERRIDE ((Eo_Op) -1)
-
 #ifndef _WIN32
 # define _EO_OP_API_ENTRY(a) a
 #else
 # define _EO_OP_API_ENTRY(a) #a
 #endif
 
-#define EO_OP_FUNC(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_NOOP, EO_OP_TYPE_REGULAR }
-#define EO_OP_CLASS_FUNC(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_NOOP, EO_OP_TYPE_CLASS }
-#define EO_OP_FUNC_OVERRIDE(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_OVERRIDE, EO_OP_TYPE_REGULAR }
-#define EO_OP_CLASS_FUNC_OVERRIDE(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_OVERRIDE, EO_OP_TYPE_CLASS }
+#define EO_OP_FUNC(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_TYPE_REGULAR }
+#define EO_OP_CLASS_FUNC(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_TYPE_CLASS }
+#define EO_OP_FUNC_OVERRIDE(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_TYPE_REGULAR_OVERRIDE }
+#define EO_OP_CLASS_FUNC_OVERRIDE(_api, _private) { _EO_OP_API_ENTRY(_api), _private, EO_OP_TYPE_CLASS_OVERRIDE }
 
 // returns the OP id corresponding to the given api_func
-EAPI Eo_Op _eo_api_op_id_get(const void *api_func, Eina_Bool is_main_loop, const char *file, int line);
+EAPI Eo_Op _eo_api_op_id_get(const void *api_func);
 
 // gets the real function pointer and the object data
 EAPI Eina_Bool _eo_call_resolve(const char *func_name, const Eo_Op op, Eo_Op_Call_Data *call, Eina_Bool is_main_loop, const char *file, int line);
index b92aa8e..7e9b0a5 100644 (file)
@@ -25,6 +25,8 @@ static _Eo_Class **_eo_classes;
 static Eo_Id _eo_classes_last_id;
 static int _eo_init_count = 0;
 static Eo_Op _eo_ops_last_id = 0;
+static Eina_Hash *_ops_storage = NULL;
+static Eina_Spinlock _ops_storage_lock;
 
 static size_t _eo_sz = 0;
 static size_t _eo_class_sz = 0;
@@ -34,7 +36,6 @@ static inline void *_eo_data_scope_get(const _Eo_Object *obj, const _Eo_Class *k
 static inline void *_eo_data_xref_internal(const char *file, int line, _Eo_Object *obj, const _Eo_Class *klass, const _Eo_Object *ref_obj);
 static inline void _eo_data_xunref_internal(_Eo_Object *obj, void *data, const _Eo_Object *ref_obj);
 static const _Eo_Class *_eo_op_class_get(Eo_Op op);
-static const char * _eo_op_id_name_get(Eo_Op op);
 
 /* Start of Dich */
 
@@ -113,9 +114,8 @@ _dich_func_set(_Eo_Class *klass, Eo_Op op, eo_op_func_type func)
    if (fsrc->src == klass)
      {
         const _Eo_Class *op_kls = _eo_op_class_get(op);
-        const char *op_name = _eo_op_id_name_get(op);
-        ERR("Class '%s': Overriding func %p for op %d (%s:'%s') with %p.",
-              klass->desc->name, fsrc->func, op, op_kls->desc->name, op_name, func);
+        ERR("Class '%s': Overriding func %p for op %d (%s) with %p.",
+              klass->desc->name, fsrc->func, op, op_kls->desc->name, func);
         return EINA_FALSE;
      }
 
@@ -191,33 +191,6 @@ _eo_op_class_get(Eo_Op op)
    return NULL;
 }
 
-static const Eo_Op_Description *
-_eo_op_id_desc_get(Eo_Op op)
-{
-   unsigned int i;
-   const _Eo_Class *klass;
-   const Eo_Op_Description *op_descs;
-
-   if (op == EO_NOOP)
-      return NULL;
-
-   klass = _eo_op_class_get(op);
-
-   if (klass)
-     {
-        DBG("klass %p %s", klass, klass->desc->name);
-
-        op_descs = klass->desc->ops.descs;
-        for (i = 0; i <  klass->desc->ops.count; i++)
-          {
-             if (op_descs[i].op == op)
-               return &op_descs[i];
-          }
-     }
-
-   return NULL;
-}
-
 static const char *
 _eo_op_desc_name_get(const Eo_Op_Description *desc)
 {
@@ -240,13 +213,6 @@ _eo_op_desc_name_get(const Eo_Op_Description *desc)
    return fct_name;
 }
 
-static const char *
-_eo_op_id_name_get(Eo_Op op)
-{
-   const Eo_Op_Description *desc = _eo_op_id_desc_get(op);
-   return _eo_op_desc_name_get(desc);
-}
-
 static inline const op_type_funcs *
 _eo_kls_itr_next(const _Eo_Class *orig_kls, const _Eo_Class *cur_klass, Eo_Op op)
 {
@@ -582,15 +548,13 @@ _eo_do_end(void)
 }
 
 EAPI Eina_Bool
-  _eo_call_resolve(const char *func_name, const Eo_Op op, Eo_Op_Call_Data *call, Eina_Bool is_main_loop, const char *file, int line)
+_eo_call_resolve(const char *func_name, const Eo_Op op, Eo_Op_Call_Data *call, Eina_Bool is_main_loop, const char *file, int line)
 {
    Eo_Stack_Frame *fptr;
    const _Eo_Class *klass;
    const op_type_funcs *func;
    Eina_Bool is_obj;
 
-   if (op == EO_NOOP) return EINA_FALSE;
-
    fptr = _eo_call_stack_get(is_main_loop)->frame_ptr;
 
    if (EINA_UNLIKELY(!fptr->o.obj))
@@ -600,6 +564,15 @@ EAPI Eina_Bool
 
    klass = (is_obj) ? fptr->o.obj->klass : fptr->o.kls;
 
+   if (op == EO_NOOP)
+     {
+        ERR("%s:%d: unable to resolve %s api func '%s' in class '%s'.",
+            file, line, (!is_obj ? "class" : "regular"),
+            func_name, klass->desc->name);
+
+        return EINA_FALSE;
+     }
+
    /* If we have a current class, we need to itr to the next. */
    if (fptr->cur_klass)
      {
@@ -718,25 +691,17 @@ _eo_api_desc_get(const void *api_func, const _Eo_Class *klass, const _Eo_Class *
      {
         for (kls_itr = klass->mro ; *kls_itr ; kls_itr++)
           {
+             unsigned int i;
              cur_klass = *kls_itr;
              op_descs = cur_klass->desc->ops.descs;
 
 #ifndef _WIN32
-             int imin, imax, imid;
-             imin = 0;
-             imax = cur_klass->desc->ops.count - 1;
-
-             while (imax >= imin)
+             for (i = 0, op_desc = op_descs; i < cur_klass->desc->ops.count; i++, op_desc++)
                {
-                  imid = (imax + imin) / 2;
-                  op_desc = op_descs + imid;
-
-                  if (op_desc->api_func > api_func)
-                    imin = imid + 1;
-                  else if (op_desc->api_func < api_func)
-                    imax = imid - 1;
-                  else
-                    return op_desc;
+                  if (op_desc->api_func == api_func)
+                    {
+                       return op_desc;
+                    }
                }
 #else
              /* On Windows, DLL API's will be exported using the dllexport flag.
@@ -746,7 +711,7 @@ _eo_api_desc_get(const void *api_func, const _Eo_Class *klass, const _Eo_Class *
               * them. We fallback to plain string comparison based on the
               * function name itself. Slow, but this should rarely happen.
               */
-             for (unsigned int i = 0; i < cur_klass->desc->ops.count; i++)
+             for (i = 0; i < cur_klass->desc->ops.count; i++)
                {
                   if (((op_descs[i].api_func != NULL) && (op_descs[i].api_func != ((void (*)())-1))) &&
                         (api_func && !strcmp(api_func, op_descs[i].api_func)))
@@ -772,45 +737,13 @@ _eo_api_desc_get(const void *api_func, const _Eo_Class *klass, const _Eo_Class *
 }
 
 EAPI Eo_Op
-_eo_api_op_id_get(const void *api_func, Eina_Bool is_main_loop, const char *file, int line)
+_eo_api_op_id_get(const void *api_func)
 {
-   const Eo_Op_Description *desc;
-   const _Eo_Class *klass;
-   Eo_Call_Stack *stack = _eo_call_stack_get(is_main_loop);
-
-   Eina_Bool class_ref = _eo_is_a_class(stack->frame_ptr->eo_id);
-
-   if (EINA_UNLIKELY(!stack->frame_ptr->o.obj))
-      return EO_NOOP;
-
-   if (class_ref)
-     klass = stack->frame_ptr->o.kls;
-   else
-     klass = stack->frame_ptr->o.obj->klass;
-
-   desc = _eo_api_desc_get(api_func, klass, klass->extensions);
-
-   if (desc == NULL)
-     {
-        const char *fct_name = _eo_op_desc_name_get(desc);
-        ERR("in %s:%d: unable to resolve %s api func '%s' %p in class '%s'.",
-            file, line, (class_ref ? "class" : "regular"),
-            fct_name, api_func, klass->desc->name);
-        return EO_NOOP;
-     }
-
-   return desc->op;
-}
+   eina_spinlock_take(&_ops_storage_lock);
+   Eo_Op op = (uintptr_t) eina_hash_find(_ops_storage, &api_func);
+   eina_spinlock_release(&_ops_storage_lock);
 
-static int
-eo_api_funcs_cmp(const void *p1, const void *p2)
-{
-   const Eo_Op_Description *op1, *op2;
-   op1 = (Eo_Op_Description *) p1;
-   op2 = (Eo_Op_Description *) p2;
-   if (op1->api_func > op2->api_func) return -1;
-   else if (op1->api_func < op2->api_func) return 1;
-   else return 0;
+   return op;
 }
 
 static Eina_Bool
@@ -819,9 +752,8 @@ _eo_class_funcs_set(_Eo_Class *klass)
    unsigned int i;
    int op_id;
    const void *last_api_func;
-   const Eo_Op_Description *api_desc;
-   Eo_Op_Description *op_desc;
-   Eo_Op_Description *op_descs;
+   const Eo_Op_Description *op_desc;
+   const Eo_Op_Description *op_descs;
 
    op_id = klass->base_id;
    op_descs = klass->desc->ops.descs;
@@ -830,31 +762,37 @@ _eo_class_funcs_set(_Eo_Class *klass)
 
    if (!op_descs) return EINA_TRUE;
 
-   qsort((void*)op_descs, klass->desc->ops.count, sizeof(Eo_Op_Description), eo_api_funcs_cmp);
-
    last_api_func = NULL;
    for (i = 0, op_desc = op_descs; i < klass->desc->ops.count; i++, op_desc++)
      {
-        if(op_desc->api_func == NULL)
+        Eo_Op op = EO_NOOP;
+
+        if (op_desc->api_func == NULL)
           {
-             ERR("Class '%s': NULL API not allowed (%d NULL->%p '%s').",
-                 klass->desc->name, op_desc->op, op_desc->func, _eo_op_desc_name_get(op_desc));
+             ERR("Class '%s': NULL API not allowed (NULL->%p '%s').",
+                 klass->desc->name, op_desc->func, _eo_op_desc_name_get(op_desc));
              return EINA_FALSE;
           }
 
-        if (op_desc->op == EO_NOOP)
+        if ((op_desc->op_type == EO_OP_TYPE_REGULAR) || (op_desc->op_type == EO_OP_TYPE_CLASS))
           {
              if (op_desc->api_func == last_api_func)
                {
-                  ERR("Class '%s': API previously defined (%d %p->%p '%s').",
-                      klass->desc->name, op_desc->op, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
+                  ERR("Class '%s': API previously defined (%p->%p '%s').",
+                      klass->desc->name, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
                   return EINA_FALSE;
                }
-             op_desc->op = op_id;
+
+             op = op_id;
+             eina_spinlock_take(&_ops_storage_lock);
+             eina_hash_add(_ops_storage, &op_desc->api_func, (void *) (uintptr_t) op);
+             eina_spinlock_release(&_ops_storage_lock);
+
              op_id++;
           }
-        else if (op_desc->op == EO_OP_OVERRIDE)
+        else if ((op_desc->op_type == EO_OP_TYPE_REGULAR_OVERRIDE) || (op_desc->op_type == EO_OP_TYPE_CLASS_OVERRIDE))
           {
+             const Eo_Op_Description *api_desc;
              api_desc = _eo_api_desc_get(op_desc->api_func, klass->parent, klass->extensions);
 
              if (api_desc == NULL)
@@ -864,12 +802,19 @@ _eo_class_funcs_set(_Eo_Class *klass)
                   return EINA_FALSE;
                }
 
-             op_desc->op = api_desc->op;
+             op = _eo_api_op_id_get(op_desc->api_func);
+          }
+
+        if (op == EO_NOOP)
+          {
+             ERR("Class '%s': Invalid op 'EO_NOOP' (%p->%p '%s').",
+                 klass->desc->name, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
+             return EINA_FALSE;
           }
 
-        DBG(" %4d %p->%p '%s'", op_desc->op, op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
+        DBG("%p->%p '%s'", op_desc->api_func, op_desc->func, _eo_op_desc_name_get(op_desc));
 
-        if (!_dich_func_set(klass, op_desc->op, op_desc->func))
+        if (!_dich_func_set(klass, op, op_desc->func))
           return EINA_FALSE;
 
         last_api_func = op_desc->api_func;
@@ -1019,17 +964,6 @@ _eo_add_end(void)
 
 /*****************************************************************************/
 
-#define _EO_OP_ERR_NO_OP_PRINT(file, line, op, klass) \
-   do \
-      { \
-         const _Eo_Class *op_klass = _eo_op_class_get(op); \
-         const char *_dom_name = (op_klass) ? op_klass->desc->name : NULL; \
-         ERR("in %s:%d: Can't execute function %s:%s (op 0x%x) for class '%s'. Aborting.", \
-               file, line, _dom_name, _eo_op_id_name_get(op), op, \
-               (klass) ? klass->desc->name : NULL); \
-      } \
-   while (0)
-
 EAPI const Eo_Class *
 eo_class_get(const Eo *eo_id)
 {
@@ -1834,12 +1768,20 @@ eo_init(void)
         return EINA_FALSE;
      }
 
+   if (!eina_spinlock_new(&_ops_storage_lock))
+     {
+        EINA_LOG_ERR("Could not init lock.");
+        return EINA_FALSE;
+     }
+
    eina_magic_string_static_set(EO_EINA_MAGIC, EO_EINA_MAGIC_STR);
    eina_magic_string_static_set(EO_FREED_EINA_MAGIC,
                                 EO_FREED_EINA_MAGIC_STR);
    eina_magic_string_static_set(EO_CLASS_EINA_MAGIC,
                                 EO_CLASS_EINA_MAGIC_STR);
 
+   _ops_storage = eina_hash_pointer_new(NULL);
+
 #ifdef EO_DEBUG
    /* Call it just for coverage purposes. Ugly I know, but I like it better than
     * casting everywhere else. */
@@ -1890,6 +1832,9 @@ eo_shutdown(void)
    if (_eo_classes)
      free(_eo_classes);
 
+   eina_hash_free(_ops_storage);
+
+   eina_spinlock_free(&_ops_storage_lock);
    eina_spinlock_free(&_eo_class_creation_lock);
 
    if (_eo_call_stack_key != 0)
index 7e00fc1..e6a9883 100644 (file)
@@ -37,7 +37,7 @@ START_TEST(eo_api_not_implemented_call)
    Eo *obj = eo_add(SIMPLE_CLASS, NULL);
    fail_if(!obj);
 
-   TEST_EO_ERROR("_eo_api_op_id_get", "in %s:%d: unable to resolve %s api func '%s' %p in class '%s'.");
+   TEST_EO_ERROR("_eo_call_resolve", "%s:%d: unable to resolve %s api func '%s' in class '%s'.");
    eo_do(obj, simple_no_implementation());
    fail_unless(ctx.did);
 
index e108b5c..03f6eb8 100644 (file)
@@ -230,7 +230,7 @@ START_TEST(eo_null_api)
         NULL
    };
 
-   TEST_EO_ERROR("_eo_class_funcs_set", "Class '%s': NULL API not allowed (%d NULL->%p '%s').");
+   TEST_EO_ERROR("_eo_class_funcs_set", "Class '%s': NULL API not allowed (NULL->%p '%s').");
    klass = eo_class_new(&class_desc, NULL, NULL);
    fail_if(klass);
    fail_unless(ctx.did);
@@ -295,7 +295,7 @@ START_TEST(eo_api_redefined)
         NULL
    };
 
-   TEST_EO_ERROR("_eo_class_funcs_set", "Class '%s': API previously defined (%d %p->%p '%s').");
+   TEST_EO_ERROR("_eo_class_funcs_set", "Class '%s': API previously defined (%p->%p '%s').");
    klass = eo_class_new(&class_desc, NULL, NULL);
    fail_if(klass);
    fail_unless(ctx.did);
@@ -328,7 +328,7 @@ START_TEST(eo_dich_func_override)
         NULL
    };
 
-   TEST_EO_ERROR("_dich_func_set", "Class '%s': Overriding func %p for op %d (%s:'%s') with %p.");
+   TEST_EO_ERROR("_dich_func_set", "Class '%s': Overriding func %p for op %d (%s) with %p.");
    klass = eo_class_new(&class_desc, SIMPLE_CLASS, NULL);
    fail_if(klass);
    fail_unless(ctx.did);
index cd1b28a..0c6a86f 100644 (file)
@@ -25,7 +25,7 @@ int _class_simple_bar(Eo *obj, Evas_Simple_Data *pd, int x);
 
 EOAPI EO_FUNC_BODYV(evas_obj_simple_bar, int, 0, EO_FUNC_CALL(x), int x);
 
-static Eo_Op_Description _class_simple_op_desc[] = {
+static const Eo_Op_Description _class_simple_op_desc[] = {
      EO_OP_FUNC(evas_obj_simple_a_set, _class_simple_a_set),
      EO_OP_FUNC(evas_obj_simple_a_get, _class_simple_a_get),
      EO_OP_FUNC(evas_obj_simple_foo, __eolian_class_simple_foo),
@@ -42,4 +42,4 @@ static const Eo_Class_Description _class_simple_class_desc = {
      NULL
 };
 
-EO_DEFINE_CLASS(class_simple_class_get, &_class_simple_class_desc, NULL, NULL);
\ No newline at end of file
+EO_DEFINE_CLASS(class_simple_class_get, &_class_simple_class_desc, NULL, NULL);
index 2998aff..934e15e 100644 (file)
@@ -53,7 +53,7 @@ static void __eolian_override_base_z_set(Eo *obj EINA_UNUSED, Override_Data *pd
 }
 
 
-static Eo_Op_Description _override_op_desc[] = {
+static const Eo_Op_Description _override_op_desc[] = {
      EO_OP_FUNC_OVERRIDE(base_constructor, _override_base_constructor),
      EO_OP_FUNC_OVERRIDE(base_z_get, __eolian_override_base_z_get),
      EO_OP_FUNC_OVERRIDE(base_z_set, __eolian_override_base_z_set),
@@ -78,4 +78,4 @@ static const Eo_Class_Description _override_class_desc = {
      NULL
 };
 
-EO_DEFINE_CLASS(override_class_get, &_override_class_desc, BASE_CLASS, NULL);
\ No newline at end of file
+EO_DEFINE_CLASS(override_class_get, &_override_class_desc, BASE_CLASS, NULL);