efl/src/lib/eina/eina_cow.c

641 lines
14 KiB
C
Raw Normal View History

/* Eina - EFL data type library
* Copyright (C) 2013 Cedric Bail
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library;
* if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif
#ifndef NVALGRIND
# include <memcheck.h>
#endif
#include "eina_config.h"
#include "eina_private.h"
#include "eina_log.h"
#include "eina_mempool.h"
#include "eina_types.h"
#include "eina_safety_checks.h"
#include "eina_list.h"
#include "eina_hash.h"
#include "eina_cow.h"
#ifdef EINA_COW_MAGIC_ON
#define EINA_COW_MAGIC 0xDEADBEEF
# define EINA_COW_PTR_MAGIC 0xBEEFE00
#endif
typedef struct _Eina_Cow_Ptr Eina_Cow_Ptr;
typedef struct _Eina_Cow_GC Eina_Cow_GC;
#ifdef HAVE_BACKTRACE
#define EINA_DEBUG_BT_NUM 64
typedef void (*Eina_Bt_Func) ();
#endif
struct _Eina_Cow_Ptr
{
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC;
# ifdef HAVE_BACKTRACE
Eina_Bt_Func writer_bt[EINA_DEBUG_BT_NUM];
int writer_bt_num;
# endif
#endif
int refcount;
#ifdef EINA_COW_MAGIC_ON
unsigned int writing;
#endif
Eina_Bool hashed : 1;
Eina_Bool togc : 1;
};
struct _Eina_Cow_GC
{
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC;
#endif
Eina_Cow_Ptr *ref;
const void **dst;
};
struct _Eina_Cow
{
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC;
#endif
Eina_Hash *togc;
Eina_Hash *match;
Eina_Mempool *pool;
2013-11-25 14:33:51 -08:00
const Eina_Cow_Data *default_value;
unsigned int struct_size;
unsigned int total_size;
};
typedef int (*Eina_Cow_Hash)(const void *, int);
#ifdef EINA_COW_MAGIC_ON
# define EINA_COW_MAGIC_CHECK(d) \
do { \
if (!EINA_MAGIC_CHECK((d), EINA_COW_MAGIC)) \
EINA_MAGIC_FAIL((d), EINA_COW_MAGIC); \
} while (0);
# define EINA_COW_PTR_MAGIC_CHECK(d) \
do { \
if (!EINA_MAGIC_CHECK((d), EINA_COW_PTR_MAGIC)) \
EINA_MAGIC_FAIL((d), EINA_COW_PTR_MAGIC); \
} while (0);
#else
# define EINA_COW_MAGIC_CHECK(d)
# define EINA_COW_PTR_MAGIC_CHECK(d)
#endif
#define EINA_COW_PTR_SIZE \
eina_mempool_alignof(sizeof (Eina_Cow_Ptr))
#define EINA_COW_PTR_GET(d) \
(((Eina_Cow_Ptr *)d) - 1)
#define EINA_COW_DATA_GET(d) \
2013-11-25 14:33:51 -08:00
(((Eina_Cow_Ptr *)d) + 1)
static int _eina_cow_log_dom = -1;
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_cow_log_dom, __VA_ARGS__)
2013-04-04 07:43:23 -07:00
#ifdef INF
#undef INF
#endif
#define INF(...) EINA_LOG_DOM_INFO(_eina_cow_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_cow_log_dom, __VA_ARGS__)
2013-11-25 14:33:51 -08:00
static Eina_Mempool *gc_pool = NULL;
2013-11-25 14:33:51 -08:00
static inline int
_eina_cow_hash_gen(const void *key, int key_length,
Eina_Cow_Hash hash,
int size)
{
const unsigned char *walk = key;
int r = 0xDEADBEEF;
while (key_length > 0)
{
r ^= hash(walk, size);
walk += size;
key_length -= size;
}
return r;
}
2013-11-09 02:48:31 -08:00
#ifdef EFL64
static int
_eina_cow_hash64(const void *key, int key_length)
{
return _eina_cow_hash_gen(key, key_length,
2013-11-09 03:43:20 -08:00
(Eina_Cow_Hash) eina_hash_int64, sizeof (unsigned long long int));
}
#else
static int
_eina_cow_hash32(const void *key, int key_length)
{
return _eina_cow_hash_gen(key, key_length,
(Eina_Cow_Hash) eina_hash_int32, sizeof (int));
}
#endif
static int current_cow_size = 0;
static unsigned int
_eina_cow_length(const void *key EINA_UNUSED)
{
2013-11-25 14:33:51 -08:00
/* nasty hack, since only gc needs to access the hash, it will be in charge
of that global. access to the hash should be considered global.
*/
return current_cow_size;
}
static int
_eina_cow_cmp(const void *key1, int key1_length,
const void *key2, int key2_length EINA_UNUSED)
{
return memcmp(key1, key2, key1_length);
}
static inline void
_eina_cow_hash_del(Eina_Cow *cow,
const void *data,
Eina_Cow_Ptr *ref)
{
/* eina_cow_gc is not supposed to be thread safe */
2013-06-20 04:28:18 -07:00
if (!ref->hashed) return;
current_cow_size = cow->struct_size;
eina_hash_del(cow->match, data, data);
ref->hashed = EINA_FALSE;
}
static void
_eina_cow_gc_free(void *data)
{
eina_mempool_free(gc_pool, data);
}
static inline void
_eina_cow_togc_del(Eina_Cow *cow, Eina_Cow_Ptr *ref)
{
/* eina_cow_gc is not supposed to be thread safe */
2013-06-20 04:28:18 -07:00
if (!ref->togc) return;
eina_hash_del(cow->togc, &ref, NULL);
ref->togc = EINA_FALSE;
}
static void
_eina_cow_togc_add(Eina_Cow *cow,
Eina_Cow_Ptr *ref,
const Eina_Cow_Data ** dst)
{
Eina_Cow_GC *gc;
/* needed if we want to make cow gc safe */
2013-06-20 04:28:18 -07:00
if (ref->togc) return;
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
gc = eina_mempool_malloc(gc_pool, sizeof (Eina_Cow_GC));
2013-06-20 04:28:18 -07:00
if (!gc) return; /* That one will not get gced this time */
gc->ref = ref;
gc->dst = dst;
eina_hash_direct_add(cow->togc, &gc->ref, gc);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
ref->togc = EINA_TRUE;
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
}
static void
2013-11-25 14:33:51 -08:00
_eina_cow_gc(Eina_Cow *cow, Eina_Cow_GC *gc)
{
2013-11-25 14:33:51 -08:00
Eina_Cow_Data *data;
Eina_Cow_Data *match;
2013-11-25 14:33:51 -08:00
data = EINA_COW_DATA_GET(gc->ref);
current_cow_size = cow->struct_size;
match = eina_hash_find(cow->match, data);
if (match)
{
2013-11-25 14:33:51 -08:00
Eina_Cow_Ptr *ref = EINA_COW_PTR_GET(match);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
2013-11-25 14:33:51 -08:00
ref->refcount += gc->ref->refcount;
2013-11-25 14:33:51 -08:00
*gc->dst = match;
eina_cow_free(cow, (const Eina_Cow_Data**) &data);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
}
else
{
eina_hash_direct_add(cow->match, data, data);
2013-11-25 14:33:51 -08:00
gc->ref->hashed = EINA_TRUE;
gc->ref->togc = EINA_FALSE;
eina_hash_del(cow->togc, &gc->ref, gc);
}
}
Eina_Bool
eina_cow_init(void)
{
const char *choice, *tmp;
_eina_cow_log_dom = eina_log_domain_register("eina_cow", EINA_LOG_COLOR_DEFAULT);
if (_eina_cow_log_dom < 0)
{
EINA_LOG_ERR("Could not register log domain: eina_cow");
return EINA_FALSE;
}
#ifdef EINA_DEFAULT_MEMPOOL
choice = "pass_through";
#else
choice = "chained_mempool";
#endif
tmp = getenv("EINA_MEMPOOL");
if (tmp && tmp[0])
choice = tmp;
gc_pool = eina_mempool_add(choice, "gc", NULL, sizeof (Eina_Cow_GC), 32);
if (!gc_pool)
{
ERR("Mempool for cow gc cannot be allocated.");
return EINA_FALSE;
}
return EINA_TRUE;
}
Eina_Bool
eina_cow_shutdown(void)
{
eina: remove usless newline Summary: ecore_evas: remove debug eina: unregister log level when done with Fixes a constant memory leak. eina: introduce EINA_HOT and EINA_COLD These attributes respectivelly expand to __attribute__ ((hot)) and __attribute__ ((cold)) when available. They allow to mark functions are being hot/cold (frequently used or not) as well as to qualify labels within a function (likely/unlikely branches). eo: speed-up generated calls by removing call cache The call cache needed to by thread-local, to avoid concurrency issues. Problem with TLS is that is adds an extra overhead, which appears to be greater than the optimization the cache provides. Op is naturally atomic, because it is an unsigned integer. As such, it cannot be tempered with while another thread is reading it. When entering the generated function, the first operation done is reading 'op'. If we have concurrency, we will have access sequences returning either EFL_NOOP or a VALID op, because 'op' is not set until the very end of the function, when everything has been computed. As such, we now use the 'op' atomic integer to instore a lock-free/wait-free mechanism, which allows to drop the TLS nature of the cache, speeding up the access to the cache, and therefore making functions execute faster. We don't test anymore the generation count. This can be put as a limitation. If means that if you call efl_object_shutdown() and re-initialize it later with different data, opcodes will be invalid. I am not sure there is any usecase for this to ever happen. We could move all the caches in a dedicated section, that can be overwritten after a call to efl_object_shutdown(), but I am not sure it will be very portable. Benchmark: mean over 3 executions of ELM_TEST_AUTOBOUNCE=100 time elementary_test -to genlist ``` BEFORE AFTER ------------------------------------------------------------ time (ns) 11114111647.0 9147676220.0 frames 2872.3333333333335 2904.6666666666665 time per frame (ns) 3869364.6666666665 3149535.3333333335 user time (s) 11.096666666666666 9.22 cpu (%) 22.666666666666668 18.333333333333332 ``` Ref T6580 Reviewers: raster, cedric Subscribers: cedric, jpeg Maniphest Tasks: T6580 Differential Revision: https://phab.enlightenment.org/D5738
2018-01-15 21:58:38 -08:00
eina_log_domain_unregister(_eina_cow_log_dom);
eina_mempool_del(gc_pool);
return EINA_TRUE;
}
EAPI Eina_Cow *
eina_cow_add(const char *name, unsigned int struct_size, unsigned int step, const void *default_value, Eina_Bool gc)
{
const char *choice, *tmp;
Eina_Cow *cow;
unsigned int total_size;
EINA_SAFETY_ON_NULL_RETURN_VAL(default_value, NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL(struct_size, NULL);
EINA_SAFETY_ON_FALSE_RETURN_VAL(step, NULL);
cow = malloc(sizeof (Eina_Cow));
if (!cow) return NULL;
#ifdef EINA_DEFAULT_MEMPOOL
choice = "pass_through";
#else
choice = "chained_mempool";
#endif
tmp = getenv("EINA_MEMPOOL");
if (tmp && tmp[0])
choice = tmp;
2013-04-04 07:43:23 -07:00
INF("Creating Cow '%s' with mempool of type '%s'", name, choice);
total_size = eina_mempool_alignof(struct_size + EINA_COW_PTR_SIZE);
cow->pool = eina_mempool_add(choice, name, NULL, total_size, step);
if (!cow->pool)
{
ERR("Mempool for cow '%s' cannot be allocated.", name);
goto on_error;
}
2013-11-09 02:48:31 -08:00
#ifdef EFL64
cow->match = eina_hash_new(_eina_cow_length,
_eina_cow_cmp,
_eina_cow_hash64,
NULL,
6);
#else
cow->match = eina_hash_new(_eina_cow_length,
_eina_cow_cmp,
_eina_cow_hash32,
NULL,
2013-11-25 14:33:51 -08:00
6);
#endif
if (gc)
cow->togc = eina_hash_pointer_new(_eina_cow_gc_free);
else
cow->togc = NULL;
cow->default_value = default_value;
cow->struct_size = struct_size;
cow->total_size = total_size;
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC_SET(cow, EINA_COW_MAGIC);
#endif
return cow;
on_error:
free(cow);
return NULL;
}
EAPI void
eina_cow_del(Eina_Cow *cow)
{
2013-06-20 04:28:18 -07:00
if (!cow) return;
#ifdef EINA_COW_MAGIC_ON
EINA_COW_MAGIC_CHECK(cow);
#endif
eina_mempool_del(cow->pool);
eina_hash_free(cow->match);
if (cow->togc) eina_hash_free(cow->togc);
free(cow);
}
EAPI const Eina_Cow_Data *
eina_cow_alloc(Eina_Cow *cow)
{
#ifdef EINA_COW_MAGIC_ON
EINA_COW_MAGIC_CHECK(cow);
#endif
return cow->default_value;
}
EAPI void
eina_cow_free(Eina_Cow *cow, const Eina_Cow_Data **data)
{
Eina_Cow_Ptr *ref;
#ifdef EINA_COW_MAGIC_ON
EINA_COW_MAGIC_CHECK(cow);
#endif
if (!data || !*data) return;
if (cow->default_value == *data) return;
ref = EINA_COW_PTR_GET(*data);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
ref->refcount--;
if (ref->refcount == 0) _eina_cow_hash_del(cow, *data, ref);
*data = (Eina_Cow_Data*) cow->default_value;
if (ref->refcount > 0)
{
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
2013-06-20 04:28:18 -07:00
return;
}
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC_SET(ref, EINA_MAGIC_NONE);
#endif
_eina_cow_togc_del(cow, ref);
eina_mempool_free(cow->pool, (void*) ref);
}
EAPI void *
eina_cow_write(Eina_Cow *cow,
const Eina_Cow_Data * const *data)
{
Eina_Cow_Ptr *ref;
2013-11-25 14:33:51 -08:00
Eina_Cow_Data *r;
#ifdef EINA_COW_MAGIC_ON
EINA_COW_MAGIC_CHECK(cow);
#endif
if (!*data) return NULL; /* cow pointer is always != NULL */
if (*data == cow->default_value)
goto allocate;
ref = EINA_COW_PTR_GET(*data);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
if (ref->refcount == 1)
{
#ifdef EINA_COW_MAGIC_ON
EINA_COW_PTR_MAGIC_CHECK(ref);
if (ref->writing && ref->togc && ref->hashed)
{
ERR("Request writing on a GC-ed pointer that is already in a writing process %p\n", data);
#ifdef HAVE_BACKTRACE
backtrace_symbols_fd((void **) ref->writer_bt,
ref->writer_bt_num, 1);
#endif
return NULL;
}
#endif
2013-11-25 14:33:51 -08:00
if (cow->togc)
_eina_cow_hash_del(cow, *data, ref);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
goto end;
}
ref->refcount--;
allocate:
ref = eina_mempool_malloc(cow->pool, cow->total_size);
ref->refcount = 1;
#ifdef EINA_COW_MAGIC_ON
ref->writing = 0;
#endif
ref->hashed = EINA_FALSE;
ref->togc = EINA_FALSE;
#ifdef EINA_COW_MAGIC_ON
EINA_MAGIC_SET(ref, EINA_COW_PTR_MAGIC);
#endif
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
r = EINA_COW_DATA_GET(ref);
memcpy(r, *data, cow->struct_size);
2013-11-25 14:33:51 -08:00
*((Eina_Cow_Data**) data) = r;
end:
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
#ifdef EINA_COW_MAGIC_ON
# ifdef HAVE_BACKTRACE
ref->writer_bt_num = backtrace((void **)(ref->writer_bt),
EINA_DEBUG_BT_NUM);
# endif
ref->writing++;
#endif
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
return (void *) *data;
}
EAPI void
eina_cow_done(Eina_Cow *cow,
const Eina_Cow_Data * const * dst,
const void *data,
Eina_Bool needed_gc)
{
Eina_Cow_Ptr *ref;
EINA_COW_MAGIC_CHECK(cow);
ref = EINA_COW_PTR_GET(data);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
EINA_COW_PTR_MAGIC_CHECK(ref);
#ifdef EINA_COW_MAGIC_ON
if (!ref->writing)
ERR("Pointer %p is not in a writable state !", dst);
ref->writing--;
#endif
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
2013-06-20 04:28:18 -07:00
if (!cow->togc || !needed_gc) return;
2013-03-20 13:18:49 -07:00
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
_eina_cow_togc_add(cow, ref, (const Eina_Cow_Data **) dst);
}
EAPI void
eina_cow_memcpy(Eina_Cow *cow,
const Eina_Cow_Data * const *dst,
const Eina_Cow_Data *src)
{
Eina_Cow_Ptr *ref;
EINA_COW_MAGIC_CHECK(cow);
2013-06-20 04:28:18 -07:00
if (*dst == src) return;
if (src != cow->default_value)
{
ref = EINA_COW_PTR_GET(src);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
EINA_COW_PTR_MAGIC_CHECK(ref);
ref->refcount++;
if (cow->togc)
_eina_cow_togc_del(cow, ref);
#ifndef NVALGRIND
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
}
eina_cow_free(cow, (const Eina_Cow_Data**) dst);
*((const void**)dst) = src;
}
EAPI Eina_Bool
eina_cow_gc(Eina_Cow *cow)
{
Eina_Cow_GC *gc;
Eina_Iterator *it;
Eina_Bool r;
#ifndef NVALGRIND
2013-11-25 14:33:51 -08:00
Eina_Cow_Ptr *ref;
#endif
EINA_COW_MAGIC_CHECK(cow);
if (!cow->togc || !eina_hash_population(cow->togc))
return EINA_FALSE;
it = eina_hash_iterator_data_new(cow->togc);
r = eina_iterator_next(it, (void**) &gc);
eina_iterator_free(it);
2013-11-25 14:33:51 -08:00
if (!r) return EINA_FALSE; /* Something did go wrong here */
#ifndef NVALGRIND
2013-11-25 14:33:51 -08:00
/* Do handle hash and all funky merge thing here */
ref = gc->ref;
2013-11-25 14:33:51 -08:00
VALGRIND_MAKE_MEM_DEFINED(ref, sizeof (*ref));
#endif
2013-11-25 14:33:51 -08:00
_eina_cow_gc(cow, gc);
#ifndef NVALGRIND
2013-11-25 14:33:51 -08:00
VALGRIND_MAKE_MEM_NOACCESS(ref, sizeof (*ref));
#endif
return EINA_TRUE;
}