eo: Use COW to save 8~16 bytes per object
authorJean-Philippe Andre <jp.andre@samsung.com>
Tue, 18 Apr 2017 05:16:31 +0000 (14:16 +0900)
committerJean-Philippe Andre <jp.andre@samsung.com>
Tue, 18 Apr 2017 09:52:27 +0000 (18:52 +0900)
Summary:
This uses Eina_Cow to implement support for rarely used features
in EO objects. This covers:
- composite objects (eg. UI widgets: combobox, text, video, win)
- vtable for efl_object_override
- del_intercept

All of these features are quite tricky to get right and while
very useful, should still be used with great care. With this patch,
the size of an _Eo_Object struct comes down from 80 bytes (rounded
up from 72b) to 64 bytes (rounded up from 56b) on 64 bits.

Also I haven't measured precisely but I don't expect any performance
impact since the COW data is more likely to remain in L1/L2 cache,
as the default one will be used most often. Unfortunately, the
results of "make benchmark" have been quite inconsistent over
multiple runs.

This saves ~64kb in elementary_test (>4k objects) at the cost of
~100 calls to COW write (del intercept on some events).

@optimization

Reviewers: raster, cedric

Differential Revision: https://phab.enlightenment.org/D4796

src/lib/eo/eo.c
src/lib/eo/eo_base_class.c
src/lib/eo/eo_private.h

index 9884878..ae06df7 100644 (file)
@@ -71,6 +71,9 @@ static Efl_Object_Op _eo_ops_last_id = 0;
 static Eina_Hash *_ops_storage = NULL;
 static Eina_Spinlock _ops_storage_lock;
 
+static const Efl_Object_Optional efl_object_optional_cow_default = {};
+Eina_Cow *efl_object_optional_cow = NULL;
+
 static size_t _eo_sz = 0;
 static size_t _eo_class_sz = 0;
 
@@ -432,7 +435,7 @@ _efl_object_call_resolve(Eo *eo_id, const char *func_name, Efl_Object_Op_Call_Da
 
         obj = _obj;
         klass = _obj->klass;
-        vtable = obj->vtable;
+        vtable = EO_VTABLE(obj);
 
         if (_obj_is_override(obj) && cur_klass &&
             (_eo_class_id_get(cur_klass) == EFL_OBJECT_OVERRIDE_CLASS))
@@ -536,12 +539,12 @@ end:
      {
         Eina_List *itr;
         Eo *emb_obj_id;
-        EINA_LIST_FOREACH(obj->composite_objects, itr, emb_obj_id)
+        EINA_LIST_FOREACH(obj->opt->composite_objects, itr, emb_obj_id)
           {
              EO_OBJ_POINTER_PROXY(emb_obj_id, emb_obj);
              if (EINA_UNLIKELY(!emb_obj)) continue;
 
-             func = _vtable_func_get(emb_obj->vtable, cache->op);
+             func = _vtable_func_get(EO_VTABLE(emb_obj), cache->op);
              if (func == NULL) goto composite_continue;
 
              if (EINA_LIKELY(func->func && func->src))
@@ -833,9 +836,9 @@ _efl_add_internal_start(const char *file, int line, const Efl_Class *klass_id, E
      }
    eina_spinlock_release(&klass->objects.trash_lock);
 
+   obj->opt = eina_cow_alloc(efl_object_optional_cow);
    obj->refcount++;
    obj->klass = klass;
-   obj->vtable = &klass->vtable;
 
 #ifndef HAVE_EO_ID
    EINA_MAGIC_SET((Eo_Header *) obj, EO_EINA_MAGIC);
@@ -966,7 +969,6 @@ efl_reuse(const Eo *_obj)
    _efl_object_parent_sink_set(obj, EINA_FALSE);
 }
 
-
 void
 _eo_free(_Eo_Object *obj, Eina_Bool manual_free EINA_UNUSED)
 {
@@ -1013,12 +1015,13 @@ _eo_free(_Eo_Object *obj, Eina_Bool manual_free EINA_UNUSED)
 #endif
    if (_obj_is_override(obj))
      {
-        _vtable_func_clean_all(obj->vtable);
-        eina_freeq_ptr_main_add(obj->vtable, free, 0);
-        obj->vtable = &klass->vtable;
+        _vtable_func_clean_all(obj->opt->vtable);
+        eina_freeq_ptr_main_add(obj->opt->vtable, free, 0);
+        EO_OPTIONAL_COW_SET(obj, vtable, NULL);
      }
 
    _eo_id_release((Eo_Id) _eo_obj_id_get(obj));
+   eina_cow_free(efl_object_optional_cow, (Eina_Cow_Data *) &obj->opt);
 
    eina_spinlock_take(&klass->objects.trash_lock);
    if ((klass->objects.trash_count <= 8) && (EINA_LIKELY(!_eo_trash_bypass)))
@@ -1576,44 +1579,45 @@ EAPI Eina_Bool
 efl_object_override(Eo *eo_id, const Efl_Object_Ops *ops)
 {
    EO_OBJ_POINTER_RETURN_VAL(eo_id, obj, EINA_FALSE);
-   EO_CLASS_POINTER_GOTO(EFL_OBJECT_OVERRIDE_CLASS, klass, err_done);
-   Eo_Vtable *previous = obj->vtable;
+   EO_CLASS_POINTER_GOTO(EFL_OBJECT_OVERRIDE_CLASS, klass, err);
 
    if (ops)
      {
-        if (obj->vtable == &obj->klass->vtable)
+        Eo_Vtable *vtable;
+
+        if (EINA_UNLIKELY(obj->opt->vtable != NULL))
           {
-             obj->vtable = calloc(1, sizeof(*obj->vtable));
-             _vtable_init(obj->vtable, previous->size);
-             _vtable_copy_all(obj->vtable, previous);
-             // rare so move error handling to end to save l1 instruction cache
-             if (!_eo_class_funcs_set(obj->vtable, ops, obj->klass,
-                                      klass, 0, EINA_TRUE))
-               goto err;
-             goto done;
+             ERR("Function table already overridden, not allowed to override again. "
+                 "Call with NULL to reset the function table first.");
+             goto err;
           }
-        // rare so move error handling to end to save l1 instruction cache
-        else goto err_already;
+
+        vtable = calloc(1, sizeof(*vtable));
+        _vtable_init(vtable, obj->klass->vtable.size);
+        _vtable_copy_all(vtable, &obj->klass->vtable);
+        if (!_eo_class_funcs_set(vtable, ops, obj->klass, klass, 0, EINA_TRUE))
+          {
+             // FIXME: Maybe leaking some chain stuff from copy above?
+             ERR("Failed to override functions for %p", eo_id);
+             free(vtable);
+             goto err;
+          }
+
+        EO_OPTIONAL_COW_SET(obj, vtable, vtable);
      }
    else
      {
-        if (obj->vtable != &obj->klass->vtable)
+        if (obj->opt->vtable)
           {
-             eina_freeq_ptr_main_add(obj->vtable, free, 0);
-             obj->vtable = (Eo_Vtable *) &obj->klass->vtable;
+             eina_freeq_ptr_main_add(obj->opt->vtable, free, 0);
+             EO_OPTIONAL_COW_SET(obj, vtable, NULL);
           }
      }
-done:
+
    EO_OBJ_DONE(eo_id);
    return EINA_TRUE;
 
-err_already:
-   ERR("Function table already overridden, not allowed to override again. "
-       "Call with NULL to reset the function table first.");
-   goto err_done;
 err:
-   ERR("Failed to override functions for %p", eo_id);
-err_done:
    EO_OBJ_DONE(eo_id);
    return EINA_FALSE;
 }
@@ -1644,7 +1648,7 @@ efl_isa(const Eo *eo_id, const Efl_Class *klass_id)
         EO_OBJ_POINTER_GOTO(eo_id, obj, err_obj);
         EO_CLASS_POINTER_GOTO(klass_id, klass, err_class);
         const op_type_funcs *func = _vtable_func_get
-          (obj->vtable, klass->base_id + klass->ops_count);
+          (EO_VTABLE(obj), klass->base_id + klass->ops_count);
 
         // Caching the result as we do a lot of serial efl_isa due to evas_object_image using it.
         tdata->cache.isa_id = eo_id;
@@ -1672,7 +1676,7 @@ efl_isa(const Eo *eo_id, const Efl_Class *klass_id)
         EO_OBJ_POINTER_GOTO(eo_id, obj, err_shared_obj);
         EO_CLASS_POINTER_GOTO(klass_id, klass, err_shared_class);
         const op_type_funcs *func = _vtable_func_get
-          (obj->vtable, klass->base_id + klass->ops_count);
+          (EO_VTABLE(obj), klass->base_id + klass->ops_count);
 
         // Caching the result as we do a lot of serial efl_isa due to evas_object_image using it.
         tdata->cache.isa_id = eo_id;
@@ -1826,16 +1830,17 @@ EAPI void
 efl_del_intercept_set(Eo *obj_id, Efl_Del_Intercept del_intercept_func)
 {
    EO_OBJ_POINTER_RETURN(obj_id, obj);
-   obj->del_intercept = del_intercept_func;
+   EO_OPTIONAL_COW_SET(obj, del_intercept, del_intercept_func);
    EO_OBJ_DONE(obj_id);
 }
 
 EAPI Efl_Del_Intercept
 efl_del_intercept_get(const Eo *obj_id)
 {
-   EO_OBJ_POINTER_RETURN_VAL(obj_id, obj, NULL);
    Efl_Del_Intercept func;
-   func = obj->del_intercept;
+
+   EO_OBJ_POINTER_RETURN_VAL(obj_id, obj, NULL);
+   func = obj->opt->del_intercept;
    EO_OBJ_DONE(obj_id);
    return func;
 }
@@ -2148,6 +2153,10 @@ efl_object_init(void)
    _eo_class_isa_func(NULL, NULL);
 #endif
 
+   efl_object_optional_cow =
+         eina_cow_add("Efl Object Optional Data", sizeof(Efl_Object_Optional),
+                      64, &efl_object_optional_cow_default, EINA_TRUE);
+
    _efl_add_fallback_init();
 
    eina_log_timing(_eo_log_dom,
@@ -2202,6 +2211,9 @@ efl_object_shutdown(void)
         _eo_table_data_shared_data = NULL;
      }
 
+   eina_cow_del(efl_object_optional_cow);
+   efl_object_optional_cow = NULL;
+
    _eo_log_obj_shutdown();
 
    eina_log_domain_unregister(_eo_log_dom);
index c6b4775..f8a12c9 100644 (file)
@@ -1610,6 +1610,7 @@ _efl_object_event_global_freeze_count_get(Eo *klass EINA_UNUSED, void *pd EINA_U
 EOLIAN static Eina_Bool
 _efl_object_composite_attach(Eo *parent_id, Efl_Object_Data *pd EINA_UNUSED, Eo *comp_obj_id)
 {
+   Efl_Object_Optional *opt;
    Eo *emb_obj_id = NULL;
 
    EO_OBJ_POINTER_RETURN_VAL(comp_obj_id, comp_obj, EINA_FALSE);
@@ -1620,7 +1621,7 @@ _efl_object_composite_attach(Eo *parent_id, Efl_Object_Data *pd EINA_UNUSED, Eo
    /* Don't composite if we already have a composite object of this type */
      {
         Eina_List *itr;
-        EINA_LIST_FOREACH(parent->composite_objects, itr, emb_obj_id)
+        EINA_LIST_FOREACH(parent->opt->composite_objects, itr, emb_obj_id)
           {
              EO_OBJ_POINTER_GOTO(emb_obj_id, emb_obj, err_klass);
              if (EINA_UNLIKELY(emb_obj->klass == comp_obj->klass)) goto err_klass;
@@ -1637,7 +1638,9 @@ _efl_object_composite_attach(Eo *parent_id, Efl_Object_Data *pd EINA_UNUSED, Eo
    _efl_object_extension_need(comp_pd);
    comp_pd->ext->composite_parent = parent_id;
 
-   parent->composite_objects = eina_list_prepend(parent->composite_objects, comp_obj_id);
+   opt = EO_OPTIONAL_COW_WRITE(parent);
+   opt->composite_objects = eina_list_prepend(opt->composite_objects, comp_obj_id);
+   EO_OPTIONAL_COW_END(opt, parent);
 
    if (emb_obj_id) EO_OBJ_DONE(emb_obj_id);
    EO_OBJ_DONE(parent_id);
@@ -1655,13 +1658,18 @@ err_parent:
 EOLIAN static Eina_Bool
 _efl_object_composite_detach(Eo *parent_id, Efl_Object_Data *pd EINA_UNUSED, Eo *comp_obj_id)
 {
+   Efl_Object_Optional *opt;
+
    EO_OBJ_POINTER_RETURN_VAL(comp_obj_id, comp_obj, EINA_FALSE);
    EO_OBJ_POINTER_GOTO(parent_id, parent, err_parent);
 
    // unlikely so improve l1 instr cache by using goto
    if (!efl_composite_part_is(comp_obj_id)) goto err_part;
 
-   parent->composite_objects = eina_list_remove(parent->composite_objects, comp_obj_id);
+   opt = EO_OPTIONAL_COW_WRITE(parent);
+   opt->composite_objects = eina_list_remove(opt->composite_objects, comp_obj_id);
+   EO_OPTIONAL_COW_END(opt, parent);
+
    /* Clear the comp parent on the child. */
      {
         Efl_Object_Data *comp_pd = efl_data_scope_get(comp_obj_id, EFL_OBJECT_CLASS);
@@ -1819,7 +1827,7 @@ children_back:
      {
         EO_OBJ_POINTER_RETURN(obj, obj_data);
         obj_data2 = obj_data;
-        if (obj_data->composite_objects) goto composite_obj;
+        if (obj_data->opt->composite_objects) goto composite_obj;
 composite_obj_back:
         EO_OBJ_DONE(obj);
      }
@@ -1867,7 +1875,8 @@ composite_obj:
      {
         Eina_List *itr, *next;
         Eo *emb_obj_id;
-        EINA_LIST_FOREACH_SAFE(obj_data2->composite_objects, itr, next, emb_obj_id)
+
+        EINA_LIST_FOREACH_SAFE(obj_data2->opt->composite_objects, itr, next, emb_obj_id)
           {
              efl_composite_detach(obj, emb_obj_id);
           }
index 84974da..e115451 100644 (file)
@@ -60,6 +60,7 @@ extern int _eo_log_dom;
 typedef uintptr_t Eo_Id;
 typedef struct _Efl_Class _Efl_Class;
 typedef struct _Eo_Header Eo_Header;
+typedef struct _Efl_Object_Optional Efl_Object_Optional;
 
 /* Allocates an entry for the given object */
 static inline Eo_Id _eo_id_allocate(const _Eo_Object *obj, const Eo *parent_id);
@@ -88,6 +89,13 @@ struct _Eo_Header
      Eo_Id id;
 };
 
+struct _Efl_Object_Optional
+{
+   Eo_Vtable          *vtable;
+   Eina_List          *composite_objects;
+   Efl_Del_Intercept   del_intercept;
+};
+
 struct _Eo_Object
 {
      Eo_Header header;
@@ -98,10 +106,7 @@ struct _Eo_Object
      Eina_Inlist *data_xrefs;
 #endif
 
-     Eo_Vtable *vtable;
-
-     Eina_List *composite_objects;
-     Efl_Del_Intercept del_intercept;
+     const Efl_Object_Optional *opt; // eina cow
 
      short refcount;
      short user_refcount;
@@ -123,6 +128,18 @@ struct _Eo_Object
 #define DICH_CHAIN1(x) ((x) >> DICH_CHAIN_LAST_BITS)
 #define DICH_CHAIN_LAST(x) ((x) & ((1 << DICH_CHAIN_LAST_BITS) - 1))
 
+extern Eina_Cow *efl_object_optional_cow;
+#define EO_OPTIONAL_COW_WRITE(_obj) ({ Efl_Object_Optional *_cow = eina_cow_write(efl_object_optional_cow, (const Eina_Cow_Data**)&(_obj->opt)); _cow; })
+#define EO_OPTIONAL_COW_END(_cow, _obj) eina_cow_done(efl_object_optional_cow, (const Eina_Cow_Data**)&(_obj->opt), _cow, EINA_TRUE)
+#define EO_OPTIONAL_COW_SET(_obj, _field, _value) do { \
+   typeof (_value) _val = _value; \
+   if (_obj->opt->_field != _val) {\
+      Efl_Object_Optional *_obj##_cow = EO_OPTIONAL_COW_WRITE(_obj); \
+      _obj##_cow->_field = _val; \
+      EO_OPTIONAL_COW_END(_obj##_cow, _obj); \
+   }} while (0)
+#define EO_VTABLE(_obj) ((_obj)->opt->vtable ?: &((_obj)->klass->vtable))
+
 typedef void (*Eo_Op_Func_Type)(Eo *, void *class_data);
 
 typedef struct
@@ -257,7 +274,7 @@ _efl_del_internal(_Eo_Object *obj, const char *func_name, const char *file, int
      {
         Eina_List *itr, *itr_n;
         Eo *emb_obj;
-        EINA_LIST_FOREACH_SAFE(obj->composite_objects, itr, itr_n, emb_obj)
+        EINA_LIST_FOREACH_SAFE(obj->opt->composite_objects, itr, itr_n, emb_obj)
           {
              efl_composite_detach(_eo_obj_id_get(obj), emb_obj);
           }
@@ -270,7 +287,7 @@ _efl_del_internal(_Eo_Object *obj, const char *func_name, const char *file, int
 static inline Eina_Bool
 _obj_is_override(_Eo_Object *obj)
 {
-   return (obj->vtable != &obj->klass->vtable);
+   return obj->opt->vtable != NULL;
 }
 
 void _eo_free(_Eo_Object *obj, Eina_Bool manual_free);
@@ -310,11 +327,11 @@ _efl_unref_internal(_Eo_Object *obj, const char *func_name, const char *file, in
              return;
           }
 
-        if (obj->del_intercept)
+        if (obj->opt->del_intercept)
           {
              Eo *obj_id = _eo_obj_id_get(obj);
              efl_ref(obj_id);
-             obj->del_intercept(obj_id);
+             obj->opt->del_intercept(obj_id);
              return;
           }