arraylist: add GArrayList

GArrayList is a datastructure that allows you to store data internally as
an array for fast index based access. However, it maintains that array as
an array of GList so that you can also use the data in compatability APIs
as a GList.

This has some additional costs in keeping the array based GList pointers
in sync, but overhead is mitigated in the common cases of small lists.

You can also embed the structure removing malloc overhead for small cases
such as [0..2].
This commit is contained in:
Christian Hergert 2015-09-13 03:03:26 -07:00
parent a51a877d27
commit 094785b790
8 changed files with 623 additions and 0 deletions

View File

@ -2524,6 +2524,20 @@ g_ptr_array_foreach
</SECTION>
<SECTION>
<TITLE>List Arrays</TITLE>
<FILE>arraylist</FILE>
GArrayList
g_array_list_new
g_array_list_init
g_array_list_peek
g_array_list_index
g_array_list_add
g_array_list_remove
g_array_list_remove_index
g_array_list_destroy
</SECTION>
<SECTION>
<TITLE>Byte Arrays</TITLE>
<FILE>arrays_byte</FILE>

View File

@ -103,6 +103,7 @@ libglib_2_0_la_SOURCES = \
$(deprecated_sources) \
glib_probes.d \
garray.c \
garraylist.c \
gasyncqueue.c \
gasyncqueueprivate.h \
gatomic.c \
@ -245,6 +246,7 @@ glibsubinclude_HEADERS = \
glib-autocleanups.h \
galloca.h \
garray.h \
garraylist.h \
gasyncqueue.h \
gatomic.h \
gbacktrace.h \

428
glib/garraylist.c Normal file
View File

@ -0,0 +1,428 @@
/* garraylist.c
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* This file 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 file 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include "garraylist.h"
#include "gmessages.h"
#include "gslice.h"
/**
* SECTION:garraylist
* @title: GArrayList
* @short_description: Linked lists implemented as arrays.
*
* Sometimes, when building APIs we make mistakes about the undelrying
* data structure that should have been used. GArrayList is a data structure
* that allows read-only compatability with #GList but is implemented as an
* array underneath. This means fast forward and backward iteration using
* typical array style index access, while providing read-only #GList access
* for compatability APIs.
*
* There is a cost associated with doing this, but the trade-off can be worth
* it under certain scenarios.
*
* Mutating the array list is potentially more expensive than a #GList.
* However, given no array growth is required, appending to the array list
* can be performed in O(1).
*
* The ideal use case for this data structure is a read-heavy data set
* where reverse iteration may be necessary and compatability with #GList
* must be maintained.
*
* It is unlikely that you should be writing new APIs that use this
* data structure.
*
* Since: 2.48
*/
#if 0
# define DEBUG_ASSERT(x) g_assert(x)
#else
# define DEBUG_ASSERT(x)
#endif
#define G_ARRAY_LIST_DEFAULT_ALLOC 8
/*
* The following structures give us a data-structure that can be dynamic,
* by changing it's implementation based on the number of items in the
* #GArrayList. When we have 2 or fewer items, we simply embed the #GList
* items in the toplevel structure.
*
* When this grows, we allocate an array to contain the #GList items and
* update prev/next pionters as necessary.
*
* This structure allows for fast iteration forwards and backwards from any
* arbitrary point in the structure, just like an array. It also allows for
* returning a read-only #GList for when API compatability is needed.
*/
typedef struct
{
guint len;
guint flags;
GDestroyNotify destroy;
GList items[2];
} GArrayListEmbed;
typedef struct
{
guint len;
guint flags;
GDestroyNotify destroy;
/*
* This is an array of GList items, which we keep in sync by updating
* the next/prev pointers when necessary.
*/
GList *items;
gsize items_len;
gpointer padding[4];
} GArrayListAlloc;
typedef struct
{
guint len;
guint on_heap : 1;
guint mode : 31;
GDestroyNotify destroy;
gpointer padding[6];
} GArrayListAny;
G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayList));
G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListAlloc));
G_STATIC_ASSERT (sizeof (GArrayListAny) == sizeof (GArrayListEmbed));
enum {
MODE_EMBED,
MODE_ALLOC,
};
GArrayList *
g_array_list_new (GDestroyNotify destroy)
{
GArrayListAny *any;
any = g_slice_new0 (GArrayListAny);
any->mode = MODE_EMBED;
any->len = 0;
any->destroy = destroy;
any->on_heap = TRUE;
return (GArrayList *)any;
}
void
g_array_list_init (GArrayList *self,
GDestroyNotify destroy)
{
GArrayListAny *any = (GArrayListAny *)self;
g_return_if_fail (self != NULL);
memset (any, 0, sizeof *any);
any->mode = MODE_EMBED;
any->len = 0;
any->destroy = destroy;
any->on_heap = FALSE;
}
const GList *
g_array_list_peek (GArrayList *self)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
g_return_val_if_fail (self != NULL, NULL);
if (self->len == 0)
return NULL;
return (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items;
}
gpointer
g_array_list_index (GArrayList *self,
guint index)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *items;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (index < self->len, NULL);
DEBUG_ASSERT ((any->len < G_N_ELEMENTS (embed->items)) ||
(any->mode == MODE_ALLOC));
items = (any->mode == MODE_EMBED) ? embed->items : (GList *)alloc->items;
return items [index].data;
}
static void
_g_array_list_grow (GArrayList *self)
{
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
gpointer old_items;
gsize i;
DEBUG_ASSERT (self != NULL);
DEBUG_ASSERT (alloc->mode == MODE_ALLOC);
DEBUG_ASSERT (alloc->len < G_MAXINT);
DEBUG_ASSERT (alloc->len > 0);
old_items = alloc->items;
alloc->items_len <<= 1;
alloc->items = g_realloc_n (alloc->items, alloc->items_len, sizeof (GList));
if (G_LIKELY (old_items == alloc->items))
return;
if (alloc->len > 0)
alloc->items [0].next = &alloc->items [1];
for (i = 1; i < (alloc->len - 1); i++)
{
alloc->items [i].prev = &alloc->items [i-1];
alloc->items [i].next = &alloc->items [i+1];
}
if (alloc->len > 1)
alloc->items [alloc->len - 1].prev = &alloc->items [alloc->len - 1 - 1];
}
static void
_g_array_list_transition (GArrayList *self)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *items;
gsize i;
DEBUG_ASSERT (self != NULL);
DEBUG_ASSERT (embed->mode == MODE_EMBED);
DEBUG_ASSERT (embed->len == G_N_ELEMENTS (embed->items));
items = g_malloc_n (G_ARRAY_LIST_DEFAULT_ALLOC, sizeof (GList));
items [0].prev = NULL;
items [0].data = embed->items [0].data;
items [0].next = &items [1];
for (i = 1; i < (G_N_ELEMENTS (embed->items) - 1); i++)
{
items [i].prev = &items [i - 1];
items [i].next = &items [i + 1];
items [i].data = &embed->items [i].data;
}
items [G_N_ELEMENTS (embed->items) - 1].prev = &items [G_N_ELEMENTS (embed->items) - 1 - 1];
items [G_N_ELEMENTS (embed->items) - 1].next = NULL;
items [G_N_ELEMENTS (embed->items) - 1].data = embed->items [G_N_ELEMENTS (embed->items) - 1].data;
any->mode = MODE_ALLOC;
alloc->items_len = G_ARRAY_LIST_DEFAULT_ALLOC;
alloc->items = items;
}
void
g_array_list_add (GArrayList *self,
gpointer data)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *item;
GList *prev;
g_return_if_fail (self != NULL);
if (G_LIKELY (any->mode == MODE_EMBED))
{
if (G_LIKELY (embed->len < G_N_ELEMENTS (embed->items)))
{
if (G_LIKELY (embed->len == 0))
{
embed->items [0].prev = NULL;
embed->items [0].next = NULL;
embed->items [0].data = data;
}
else
{
prev = &embed->items [embed->len - 1];
item = &embed->items [embed->len];
item->prev = prev;
item->next = NULL;
item->data = data;
prev->next = item;
}
embed->len++;
DEBUG_ASSERT (embed->len <= G_N_ELEMENTS (embed->items));
DEBUG_ASSERT (embed->len > 0);
DEBUG_ASSERT (embed->items [embed->len - 1].data == data);
return;
}
_g_array_list_transition (self);
}
DEBUG_ASSERT (any->mode == MODE_ALLOC);
if (G_UNLIKELY (alloc->len == alloc->items_len))
_g_array_list_grow (self);
item = &alloc->items [alloc->len];
prev = (alloc->len > 0) ? &alloc->items [alloc->len - 1] : NULL;
item->data = data;
item->prev = NULL;
item->next = NULL;
if (prev)
prev->next = item;
alloc->len++;
DEBUG_ASSERT (alloc->len <= alloc->items_len);
DEBUG_ASSERT (alloc->len > 0);
DEBUG_ASSERT (alloc->items [alloc->len - 1].data == data);
}
void
g_array_list_remove_index (GArrayList *self,
guint index)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *items;
gpointer data = NULL;
gsize i;
g_return_if_fail (self != NULL);
g_return_if_fail (index < self->len);
if (G_LIKELY (any->mode == MODE_EMBED))
{
DEBUG_ASSERT (index < G_N_ELEMENTS (embed->items));
data = embed->items [index].data;
embed->len--;
items = embed->items;
}
else
{
DEBUG_ASSERT (any->mode == MODE_ALLOC);
DEBUG_ASSERT (index < alloc->items_len);
data = alloc->items [index].data;
alloc->len--;
items = alloc->items;
}
if (index != alloc->len)
memmove (&items [index],
&items [index + 1],
sizeof (GList) * (any->len - index));
for (i = index; i < any->len; i++)
{
if (i > 0)
items [i].prev = &items [i - 1];
else
items [i].prev = NULL;
if (i < (any->len - 1))
items [i].next = &items [i + 1];
else
items [i].next = NULL;
}
if (any->destroy)
any->destroy (data);
}
void
g_array_list_remove (GArrayList *self,
gpointer data)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *items;
gsize i;
g_return_if_fail (self != NULL);
items = (any->mode == MODE_EMBED) ? embed->items : alloc->items;
for (i = 0; i < any->len; i++)
{
if (items [i].data == data)
{
g_array_list_remove_index (self, i);
return;
}
}
g_warning ("Failed to locate %p in GArrayList", data);
}
void
g_array_list_destroy (GArrayList *self)
{
GArrayListAny *any = (GArrayListAny *)self;
GArrayListEmbed *embed = (GArrayListEmbed *)self;
GArrayListAlloc *alloc = (GArrayListAlloc *)self;
GList *items;
gsize i;
g_return_if_fail (self != NULL);
if (any->mode == MODE_EMBED)
items = embed->items;
else
items = alloc->items;
if (any->destroy)
for (i = 0; i < any->len; i++)
any->destroy (items [i].data);
if (any->mode == MODE_ALLOC)
g_free (alloc->items);
if (any->on_heap)
g_slice_free (GArrayListAny, any);
}

76
glib/garraylist.h Normal file
View File

@ -0,0 +1,76 @@
/* garraylist.h
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* This file 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 file 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 General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __G_ARRAY_LIST_H__
#define __G_ARRAY_LIST_H__
#if !defined (__GLIB_H_INSIDE__) && !defined (GLIB_COMPILATION)
#error "Only <glib.h> can be included directly."
#endif
#include <glib/gtypes.h>
#include <glib/glist.h>
G_BEGIN_DECLS
typedef struct
{
guint len;
guint padding;
gpointer padding1;
gpointer padding2;
gpointer padding3;
gpointer padding4;
gpointer padding5;
gpointer padding6;
gpointer padding7;
} GArrayList;
GLIB_AVAILABLE_IN_2_46
GArrayList *g_array_list_new (GDestroyNotify notify);
GLIB_AVAILABLE_IN_2_46
void g_array_list_init (GArrayList *list,
GDestroyNotify notify);
GLIB_AVAILABLE_IN_2_46
const GList *g_array_list_peek (GArrayList *list);
GLIB_AVAILABLE_IN_2_46
gpointer g_array_list_index (GArrayList *list,
guint index);
GLIB_AVAILABLE_IN_2_46
void g_array_list_add (GArrayList *list,
gpointer data);
GLIB_AVAILABLE_IN_2_46
void g_array_list_remove (GArrayList *list,
gpointer data);
GLIB_AVAILABLE_IN_2_46
void g_array_list_remove_index (GArrayList *list,
guint index);
GLIB_AVAILABLE_IN_2_46
void g_array_list_destroy (GArrayList *list);
G_END_DECLS
#endif /* __G_ARRAY_LIST_H__ */

View File

@ -29,6 +29,7 @@
#include <glib/galloca.h>
#include <glib/garray.h>
#include <glib/garraylist.h>
#include <glib/gasyncqueue.h>
#include <glib/gatomic.h>
#include <glib/gbacktrace.h>

View File

@ -3,6 +3,7 @@
642026
642026-ec
array-test
arraylist
asyncqueue
atomic
autoptr

View File

@ -31,6 +31,7 @@ test_extra_programs = \
test_programs = \
array-test \
arraylist \
asyncqueue \
base64 \
bitlock \

100
glib/tests/arraylist.c Normal file
View File

@ -0,0 +1,100 @@
/* arraylist.c
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* This 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 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include <glib.h>
static int test_basic_counter;
static void
test_basic_destroy (gpointer data)
{
test_basic_counter++;
}
static void
test_basic (GArrayList *al)
{
const GList *iter, *list;
gsize i;
gsize counter = 0;
g_assert (al != NULL);
g_assert_cmpint (al->len, ==, 0);
for (i = 1; i <= 1000; i++)
{
g_array_list_add (al, GSIZE_TO_POINTER (i));
g_assert_cmpint (al->len, ==, i);
}
list = g_array_list_peek (al);
for (iter = list; iter; iter = iter->next)
{
counter++;
g_assert_cmpint (counter, ==, GPOINTER_TO_SIZE (iter->data));
}
g_assert_cmpint (counter, ==, 1000);
for (i = 1; i <= 500; i++)
{
gpointer item = GSIZE_TO_POINTER (i);
gpointer val = g_array_list_index (al, 0);
g_assert_cmpint (GPOINTER_TO_SIZE (val), ==, i);
g_array_list_remove (al, item);
}
g_assert_cmpint (al->len, ==, 500);
g_assert_cmpint (test_basic_counter, ==, 500);
g_array_list_destroy (al);
g_assert_cmpint (test_basic_counter, ==, 1000);
test_basic_counter = 0;
}
static void
test_basic_alloc (void)
{
test_basic (g_array_list_new (test_basic_destroy));
}
static void
test_basic_stack (void)
{
GArrayList al;
g_array_list_init (&al, test_basic_destroy);
test_basic (&al);
}
int
main (int argc,
char *argv[])
{
g_test_init (&argc, &argv, NULL);
g_test_bug_base ("http://bugzilla.gnome.org/");
g_test_add_func ("/GArrayList/heap", test_basic_alloc);
g_test_add_func ("/GArrayList/stack", test_basic_stack);
return g_test_run ();
}