refstr: drop internal struct RefString and pack NMRefString

Previously, NMRefString was the public part of the struct, while
there was an internal RefString struct with private fields.
That might make sense if we would need to preserve some stable ABI, but
we don't because this is all internal (unstable) API. It also might
make sense to hide fields, but in practice that is not necessary
because the leading underscore is indicator enough that these are
private fields that are not supposed to be touched (unless you really
know what you do). So, drop RefString and move all fields in the public
NMRefString. The advantage is that we can later inline certain trivial
functions, that we otherwise couldn't.

Also, drop the "str" pointer and only use the "str" array field. The
pointer existed so that during nm_ref_string_new_len() we could create
a lookup needle with external str pointer. That is now solved
differently by using "len == G_MAXSIZE" as indicator that this is
a special lookup instance. The advantage is that we save one pointer
field per NMRefString, that we reduce the redundancy of the data, and
that we don't need the additional indirection.
This commit is contained in:
Thomas Haller 2021-03-17 13:12:46 +01:00
parent 51ff2865c3
commit bec8928341
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
2 changed files with 79 additions and 60 deletions

View file

@ -6,69 +6,71 @@
/*****************************************************************************/
typedef struct {
NMRefString r;
volatile int ref_count;
char str_data[];
} RefString;
G_LOCK_DEFINE_STATIC(gl_lock);
static GHashTable *gl_hash;
/* the first field of NMRefString is a pointer to the NUL terminated string.
* This also allows to compare strings with nm_pstr_equal(), although, pointer
* equality might be better. */
G_STATIC_ASSERT(G_STRUCT_OFFSET(NMRefString, str) == 0);
G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r) == 0);
G_STATIC_ASSERT(G_STRUCT_OFFSET(RefString, r.str) == 0);
/*****************************************************************************/
static void
_ref_string_get(const NMRefString *rstr, const char **out_str, gsize *out_len)
{
if (rstr->len == G_MAXSIZE) {
*out_len = rstr->_priv_lookup.l_len;
*out_str = rstr->_priv_lookup.l_str;
} else {
*out_len = rstr->len;
*out_str = rstr->str;
}
}
static guint
_ref_string_hash(gconstpointer ptr)
{
const RefString *a = ptr;
NMHashState h;
const char *cstr;
gsize len;
nm_hash_init(&h, 1463435489u);
nm_hash_update(&h, a->r.str, a->r.len);
return nm_hash_complete(&h);
_ref_string_get(ptr, &cstr, &len);
return nm_hash_mem(1463435489u, cstr, len);
}
static gboolean
_ref_string_equal(gconstpointer pa, gconstpointer pb)
_ref_string_equal(gconstpointer ptr_a, gconstpointer ptr_b)
{
const RefString *a = pa;
const RefString *b = pb;
const char *cstr_a;
const char *cstr_b;
gsize len_a;
gsize len_b;
return a->r.len == b->r.len && memcmp(a->r.str, b->r.str, a->r.len) == 0;
_ref_string_get(ptr_a, &cstr_a, &len_a);
_ref_string_get(ptr_b, &cstr_b, &len_b);
return len_a == len_b && memcmp(cstr_a, cstr_b, len_a) == 0;
}
/*****************************************************************************/
static void
_ASSERT(const RefString *rstr0)
_ASSERT(const NMRefString *rstr)
{
int r;
nm_assert(rstr0);
nm_assert(rstr);
if (NM_MORE_ASSERTS > 0) {
r = g_atomic_int_get(&rstr0->ref_count);
r = g_atomic_int_get(&rstr->_ref_count);
nm_assert(r > 0);
nm_assert(r < G_MAXINT);
}
nm_assert(rstr0->r.str == rstr0->str_data);
nm_assert(rstr0->r.str[rstr0->r.len] == '\0');
nm_assert(rstr->str[rstr->len] == '\0');
if (NM_MORE_ASSERTS > 10) {
G_LOCK(gl_lock);
r = g_atomic_int_get(&rstr0->ref_count);
r = g_atomic_int_get(&rstr->_ref_count);
nm_assert(r > 0);
nm_assert(r < G_MAXINT);
nm_assert(rstr0 == g_hash_table_lookup(gl_hash, rstr0));
nm_assert(rstr == g_hash_table_lookup(gl_hash, rstr));
G_UNLOCK(gl_lock);
}
}
@ -117,83 +119,88 @@ _ASSERT(const RefString *rstr0)
NMRefString *
nm_ref_string_new_len(const char *cstr, gsize len)
{
RefString *rstr0;
NMRefString *rstr;
/* @len cannot be close to G_MAXSIZE. For one, that would mean our call
* to malloc() below overflows. Also, we use G_MAXSIZE as special length
* to indicate using _priv_lookup. */
nm_assert(len < G_MAXSIZE - G_STRUCT_OFFSET(NMRefString, str) - 1u);
G_LOCK(gl_lock);
if (G_UNLIKELY(!gl_hash)) {
gl_hash = g_hash_table_new_full(_ref_string_hash, _ref_string_equal, g_free, NULL);
rstr0 = NULL;
rstr = NULL;
} else {
NMRefString rr_lookup = {
.len = len,
.str = cstr,
.len = G_MAXSIZE,
._priv_lookup =
{
.l_len = len,
.l_str = cstr,
},
};
rstr0 = g_hash_table_lookup(gl_hash, &rr_lookup);
rstr = g_hash_table_lookup(gl_hash, &rr_lookup);
}
if (rstr0) {
if (rstr) {
nm_assert(({
int r = g_atomic_int_get(&rstr0->ref_count);
int r = g_atomic_int_get(&rstr->_ref_count);
(r >= 0 && r < G_MAXINT);
}));
g_atomic_int_inc(&rstr0->ref_count);
g_atomic_int_inc(&rstr->_ref_count);
} else {
rstr0 = g_malloc(sizeof(RefString) + 1 + len);
rstr0->ref_count = 1;
*((gsize *) &rstr0->r.len) = len;
*((const char **) &rstr0->r.str) = rstr0->str_data;
rstr = g_malloc((G_STRUCT_OFFSET(NMRefString, str) + 1u) + len);
if (len > 0)
memcpy(rstr0->str_data, cstr, len);
rstr0->str_data[len] = '\0';
memcpy((char *) rstr->str, cstr, len);
((char *) rstr->str)[len] = '\0';
*((gsize *) &rstr->len) = len;
rstr->_ref_count = 1;
if (!g_hash_table_add(gl_hash, rstr0))
if (!g_hash_table_add(gl_hash, rstr))
nm_assert_not_reached();
}
G_UNLOCK(gl_lock);
return &rstr0->r;
return rstr;
}
NMRefString *
nm_ref_string_ref(NMRefString *rstr)
{
RefString *const rstr0 = (RefString *) rstr;
if (!rstr)
return NULL;
_ASSERT(rstr0);
_ASSERT(rstr);
g_atomic_int_inc(&rstr0->ref_count);
return &rstr0->r;
g_atomic_int_inc(&rstr->_ref_count);
return rstr;
}
void
_nm_ref_string_unref_non_null(NMRefString *rstr)
{
RefString *const rstr0 = (RefString *) rstr;
int r;
int r;
_ASSERT(rstr0);
_ASSERT(rstr);
/* fast-path: first try to decrement the ref-count without bringing it
* to zero. */
r = rstr0->ref_count;
if (G_LIKELY(r > 1 && g_atomic_int_compare_and_exchange(&rstr0->ref_count, r, r - 1)))
r = rstr->_ref_count;
if (G_LIKELY(r > 1 && g_atomic_int_compare_and_exchange(&rstr->_ref_count, r, r - 1)))
return;
/* We apparently are about to return the last reference. Take a lock. */
G_LOCK(gl_lock);
nm_assert(g_hash_table_lookup(gl_hash, rstr0) == rstr0);
nm_assert(g_hash_table_lookup(gl_hash, rstr) == rstr);
if (G_LIKELY(g_atomic_int_dec_and_test(&rstr0->ref_count))) {
if (!g_hash_table_remove(gl_hash, rstr0))
if (G_LIKELY(g_atomic_int_dec_and_test(&rstr->_ref_count))) {
if (!g_hash_table_remove(gl_hash, rstr))
nm_assert_not_reached();
}

View file

@ -6,8 +6,20 @@
/*****************************************************************************/
typedef struct _NMRefString {
const char *const str;
const gsize len;
const gsize len;
union {
struct {
volatile int _ref_count;
const char str[];
};
struct {
/* This union field is only used during lookup by external string.
* In that case, len will be set to G_MAXSIZE, and the actual len/str values
* are set in _priv_lookup. */
gsize l_len;
const char *l_str;
} _priv_lookup;
};
} NMRefString;
/*****************************************************************************/