[glib/wip/chergert/garraylist] arraylist: add GArrayList



commit 094785b790c3080ced41b0f7bec6e019b924dca0
Author: Christian Hergert <christian hergert me>
Date:   Sun Sep 13 03:03:26 2015 -0700

    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].

 docs/reference/glib/glib-sections.txt |   14 +
 glib/Makefile.am                      |    2 +
 glib/garraylist.c                     |  428 +++++++++++++++++++++++++++++++++
 glib/garraylist.h                     |   76 ++++++
 glib/glib.h                           |    1 +
 glib/tests/.gitignore                 |    1 +
 glib/tests/Makefile.am                |    1 +
 glib/tests/arraylist.c                |  100 ++++++++
 8 files changed, 623 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt
index d5fa3fe..f1939fd 100644
--- a/docs/reference/glib/glib-sections.txt
+++ b/docs/reference/glib/glib-sections.txt
@@ -2525,6 +2525,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>
 <SUBSECTION>
diff --git a/glib/Makefile.am b/glib/Makefile.am
index 2783b51..ba7003a 100644
--- a/glib/Makefile.am
+++ b/glib/Makefile.am
@@ -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    \
diff --git a/glib/garraylist.c b/glib/garraylist.c
new file mode 100644
index 0000000..51086ff
--- /dev/null
+++ b/glib/garraylist.c
@@ -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);
+}
diff --git a/glib/garraylist.h b/glib/garraylist.h
new file mode 100644
index 0000000..b885908
--- /dev/null
+++ b/glib/garraylist.h
@@ -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__ */
diff --git a/glib/glib.h b/glib/glib.h
index 1212d13..7b959f0 100644
--- a/glib/glib.h
+++ b/glib/glib.h
@@ -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>
diff --git a/glib/tests/.gitignore b/glib/tests/.gitignore
index c421004..1a4babd 100644
--- a/glib/tests/.gitignore
+++ b/glib/tests/.gitignore
@@ -3,6 +3,7 @@
 642026
 642026-ec
 array-test
+arraylist
 asyncqueue
 atomic
 autoptr
diff --git a/glib/tests/Makefile.am b/glib/tests/Makefile.am
index 23f2940..3da632a 100644
--- a/glib/tests/Makefile.am
+++ b/glib/tests/Makefile.am
@@ -31,6 +31,7 @@ test_extra_programs = \
 
 test_programs = \
        array-test                      \
+       arraylist                       \
        asyncqueue                      \
        base64                          \
        bitlock                         \
diff --git a/glib/tests/arraylist.c b/glib/tests/arraylist.c
new file mode 100644
index 0000000..8d92acb
--- /dev/null
+++ b/glib/tests/arraylist.c
@@ -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 ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]