diff --git a/src/gutils.c b/src/gutils.c
index f6c868b..6ac333c 100644
--- a/src/gutils.c
+++ b/src/gutils.c
@@ -475,3 +475,351 @@ gnm_object_get_bool (gpointer o, const char *name)
 	g_object_get (o, name, &b, NULL);
 	return b;
 }
+
+/* ------------------------------------------------------------------------- */
+
+/*
+ * The following code was derived from glib's ghash.c
+ * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
+ * Modified by the GLib Team and others 1997-2000.
+ */
+
+typedef struct {
+	gpointer key;
+} GnmThinNode;
+
+struct GnmThinSet_ {
+	gint             size;
+	gint             mod;
+	guint            mask;
+	gint             nnodes;
+	gint             noccupied;  /* nnodes + tombstones */
+	GnmThinNode     *nodes;
+	GHashFunc        hash_func;
+	GEqualFunc       key_equal_func;
+	gpointer         key_tombstone;
+	gpointer         key_unused;
+};
+
+enum { THIN_SET_MIN_SHIFT = 3 };  /* 1 << 3 == 8 buckets */
+
+/* Each table size has an associated prime modulo (the first prime
+ * lower than the table size) used to find the initial bucket. Probing
+ * then works modulo 2^n. The prime modulo is necessary to get a
+ * good distribution with poor hash functions. */
+static const gint prime_mod [] =
+{
+  1,          /* For 1 << 0 */
+  2,
+  3,
+  7,
+  13,
+  31,
+  61,
+  127,
+  251,
+  509,
+  1021,
+  2039,
+  4093,
+  8191,
+  16381,
+  32749,
+  65521,      /* For 1 << 16 */
+  131071,
+  262139,
+  524287,
+  1048573,
+  2097143,
+  4194301,
+  8388593,
+  16777213,
+  33554393,
+  67108859,
+  134217689,
+  268435399,
+  536870909,
+  1073741789,
+  2147483647  /* For 1 << 31 */
+};
+
+static void
+gnm_thin_set_set_shift (GnmThinSet *set, gint shift)
+{
+	g_return_if_fail (shift >= THIN_SET_MIN_SHIFT);
+	g_return_if_fail (shift < (gint)G_N_ELEMENTS (prime_mod));
+
+	set->size = 1 << shift;
+	set->mod  = prime_mod [shift];
+	set->mask = set->size - 1;
+}
+
+static gint
+gnm_thin_set_find_closest_shift (gint n)
+{
+	gint i;
+
+	for (i = 0; n; i++)
+		n >>= 1;
+
+	return i;
+}
+
+static void
+gnm_thin_set_set_shift_from_size (GnmThinSet *set, gint size)
+{
+	gint shift;
+
+	shift = gnm_thin_set_find_closest_shift (size);
+	shift = MAX (shift, THIN_SET_MIN_SHIFT);
+
+	gnm_thin_set_set_shift (set, shift);
+}
+
+
+GnmThinSet *
+gnm_thin_set_new (GHashFunc hash_func,
+		  GEqualFunc key_equal_func,
+		  gconstpointer imp_key1,
+		  gconstpointer imp_key2)
+{
+	GnmThinSet *set;
+	gint i;
+
+	g_return_val_if_fail (imp_key1 != imp_key2, NULL);
+
+	set = g_slice_new (GnmThinSet);
+	gnm_thin_set_set_shift (set, THIN_SET_MIN_SHIFT);
+	set->nnodes         = 0;
+	set->noccupied      = 0;
+	set->hash_func      = hash_func ? hash_func : g_direct_hash;
+	set->key_equal_func = key_equal_func ? key_equal_func : g_direct_equal;
+	set->nodes          = g_new (GnmThinNode, set->size);
+	set->key_unused     = (gpointer)imp_key1;
+	set->key_tombstone  = (gpointer)imp_key2;
+
+	for (i = 0; i < set->size; i++) {
+		GnmThinNode *node = &set->nodes[i];
+		node->key = set->key_unused;
+	}
+
+	return set;
+}
+
+static void
+gnm_thin_set_resize (GnmThinSet *set)
+{
+	GnmThinNode *new_nodes;
+	gint old_size;
+	gint i;
+
+	old_size = set->size;
+	gnm_thin_set_set_shift_from_size (set, set->nnodes * 2);
+
+	new_nodes = g_new (GnmThinNode, set->size);
+	for (i = 0; i < set->size; i++) {
+		GnmThinNode *node = &new_nodes[i];
+		node->key = set->key_unused;
+	}
+
+	for (i = 0; i < old_size; i++) {
+		GnmThinNode *node = &set->nodes [i];
+		GnmThinNode *new_node;
+		guint hash_val;
+		guint step = 0;
+
+		if (node->key == set->key_unused ||
+		    node->key == set->key_tombstone)
+			continue;
+
+		hash_val = set->hash_func (node->key) % set->mod;
+		new_node = &new_nodes [hash_val];
+
+		while (new_node->key != set->key_unused) {
+			step++;
+			hash_val += step;
+			hash_val &= set->mask;
+			new_node = &new_nodes [hash_val];
+		}
+
+		*new_node = *node;
+	}
+
+	g_free (set->nodes);
+	set->nodes = new_nodes;
+	set->noccupied = set->nnodes;
+}
+
+static void
+gnm_thin_set_maybe_resize (GnmThinSet *set)
+{
+	gint noccupied = set->noccupied;
+	gint size = set->size;
+
+	if ((size > set->nnodes * 4 && size > (1 << THIN_SET_MIN_SHIFT)) ||
+	    (size <= noccupied + (noccupied / 16)))
+		gnm_thin_set_resize (set);
+}
+
+static guint
+gnm_thin_set_lookup_node (GnmThinSet    *set,
+                          gconstpointer  key,
+			  guint         *hash_return)
+{
+	GnmThinNode *node;
+	guint node_index;
+	guint hash_value;
+	guint step = 0;
+	guint first_tombstone = (guint)-1;
+
+	hash_value = *hash_return = set->hash_func (key);
+
+	node_index = hash_value % set->mod;
+	node = &set->nodes [node_index];
+
+	while (node->key != set->key_unused) {
+		if (node->key == set->key_tombstone) {
+			if (first_tombstone == (guint)-1)
+				first_tombstone = node_index;
+		} else if (set->key_equal_func (node->key, key))
+			break;
+
+		step++;
+		node_index += step;
+		node_index &= set->mask;
+		node = &set->nodes [node_index];
+	}
+
+	if (first_tombstone != (guint)-1)
+		return first_tombstone;
+
+	return node_index;
+}
+
+static void
+gnm_thin_set_remove_node (GnmThinSet   *set,
+                          GnmThinNode  *node)
+{
+	node->key = set->key_tombstone;
+	set->nnodes--;
+}
+
+static void
+gnm_thin_set_remove_all (GnmThinSet *set)
+{
+	int i;
+
+	for (i = 0; i < set->size; i++) {
+		GnmThinNode *node = &set->nodes[i];
+		node->key = set->key_unused;
+	}
+
+	set->nnodes = 0;
+	set->noccupied = 0;
+}
+
+void
+gnm_thin_set_destroy (GnmThinSet *set)
+{
+	g_return_if_fail (set != NULL);
+
+	gnm_thin_set_remove_all (set);
+	g_free (set->nodes);
+	g_slice_free (GnmThinSet, set);
+}
+
+void
+gnm_thin_set_insert (GnmThinSet *set, gpointer key)
+{
+	GnmThinNode *node;
+	guint node_index;
+	guint key_hash;
+	gboolean is_empty;
+
+	g_return_if_fail (set != NULL);
+	g_return_if_fail (key != set->key_unused);
+	g_return_if_fail (key != set->key_tombstone);
+
+	node_index = gnm_thin_set_lookup_node (set, key, &key_hash);
+	node = &set->nodes [node_index];
+
+	is_empty = (node->key == set->key_unused);
+	if (!is_empty && node->key != set->key_tombstone) {
+		/* Already there. */
+	} else {
+		node->key = key;
+		set->nnodes++;
+		if (is_empty) {
+			/* We replaced an empty node, and not a tombstone */
+			set->noccupied++;
+			gnm_thin_set_maybe_resize (set);
+		}
+	}
+}
+
+gboolean
+gnm_thin_set_remove (GnmThinSet *set, gconstpointer key)
+{
+	GnmThinNode *node;
+	guint node_index;
+	guint hash_value;
+
+	g_return_val_if_fail (set != NULL, FALSE);
+	g_return_val_if_fail (key != set->key_unused, FALSE);
+	g_return_val_if_fail (key != set->key_tombstone, FALSE);
+
+	node_index = gnm_thin_set_lookup_node (set, key, &hash_value);
+	node = &set->nodes [node_index];
+
+	if (node->key == set->key_unused)
+		return FALSE;
+
+	gnm_thin_set_remove_node (set, node);
+	gnm_thin_set_maybe_resize (set);
+
+	return TRUE;
+}
+
+void
+gnm_thin_set_foreach (GnmThinSet *set,
+		      GHFunc func,
+		      gpointer user_data)
+{
+	gint i;
+
+	g_return_if_fail (set != NULL);
+
+	for (i = 0; i < set->size; i++) {
+		GnmThinNode *node = &set->nodes [i];
+
+		if (node->key != set->key_unused &&
+		    node->key != set->key_tombstone)
+			func (node->key, node->key, user_data);
+	}
+}
+
+guint
+gnm_thin_set_size (GnmThinSet *set)
+{
+	g_return_val_if_fail (set != NULL, 0);
+	return set->nnodes;
+}
+
+gpointer
+gnm_thin_set_lookup (GnmThinSet *set, gconstpointer key)
+{
+	GnmThinNode *node;
+	guint node_index;
+	guint hash_value;
+
+	g_return_val_if_fail (set != NULL, NULL);
+	g_return_val_if_fail (key != set->key_unused, NULL);
+	g_return_val_if_fail (key != set->key_tombstone, NULL);
+
+	node_index = gnm_thin_set_lookup_node (set, key, &hash_value);
+	node = &set->nodes [node_index];
+
+	return node->key == set->key_unused ? NULL : node->key;
+}
+
+/* ------------------------------------------------------------------------- */
diff --git a/src/gutils.h b/src/gutils.h
index e7d9d02..8e132e5 100644
--- a/src/gutils.h
+++ b/src/gutils.h
@@ -40,6 +40,24 @@ void       gnm_insert_meta_date (GODoc *doc, char const *name);
 
 gboolean   gnm_object_get_bool (gpointer o, const char *name);
 
+/* ------------------------------------------------------------------------- */
+
+typedef struct GnmThinSet_ GnmThinSet;
+GnmThinSet *gnm_thin_set_new (GHashFunc hash_func,
+			      GEqualFunc key_equal_func,
+			      gconstpointer imp_key1,
+			      gconstpointer imp_key2);
+void	    gnm_thin_set_destroy (GnmThinSet *set);
+void	    gnm_thin_set_insert (GnmThinSet *set, gpointer key);
+gboolean    gnm_thin_set_remove (GnmThinSet *set, gconstpointer key);
+void	    gnm_thin_set_foreach (GnmThinSet *set,
+				  GHFunc func,
+				  gpointer user_data);
+guint	    gnm_thin_set_size (GnmThinSet *set);
+gpointer    gnm_thin_set_lookup (GnmThinSet *set, gconstpointer key);
+
+/* ------------------------------------------------------------------------- */
+
 G_END_DECLS
 
 #endif /* _GNM_GUTILS_H_ */
diff --git a/src/sheet.c b/src/sheet.c
index cd46fdc..2da5c31 100644
--- a/src/sheet.c
+++ b/src/sheet.c
@@ -721,9 +721,23 @@ gnm_sheet_constructor (GType type,
 	return obj;
 }
 
+static guint
+cell_set_hash (GnmCell const *key)
+{
+	return gnm_cellpos_hash (&key->pos);
+}
+
+static gint
+cell_set_equal (GnmCell const *a, GnmCell const *b)
+{
+	return gnm_cellpos_equal (&a->pos, &b->pos);
+}
+
+
 static void
 gnm_sheet_init (Sheet *sheet)
 {
+	static GnmCell dummy1, dummy2;
 	PangoContext *context;
 
 	sheet->priv = g_new0 (SheetPrivate, 1);
@@ -786,8 +800,9 @@ gnm_sheet_init (Sheet *sheet)
 	sheet->hash_merged = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
 					       (GCompareFunc)&gnm_cellpos_equal);
 
-	sheet->cell_hash = g_hash_table_new ((GHashFunc)&gnm_cellpos_hash,
-					     (GCompareFunc)&gnm_cellpos_equal);
+	sheet->cell_set = gnm_thin_set_new ((GHashFunc)cell_set_hash,
+					    (GCompareFunc)cell_set_equal,
+					    &dummy1, &dummy2);
 
 	/* Init preferences */
 	sheet->convs = gnm_conventions_default;
@@ -1867,13 +1882,13 @@ GnmCell *
 sheet_cell_get (Sheet const *sheet, int col, int row)
 {
 	GnmCell *cell;
-	GnmCellPos pos;
+	GnmCell key;
 
 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
 
-	pos.col = col;
-	pos.row = row;
-	cell = g_hash_table_lookup (sheet->cell_hash, &pos);
+	key.pos.col = col;
+	key.pos.row = row;
+	cell = gnm_thin_set_lookup (sheet->cell_set, &key);
 
 	return cell;
 }
@@ -3649,7 +3664,7 @@ sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
 {
 	g_return_if_fail (IS_SHEET (sheet));
 
-	g_hash_table_foreach (sheet->cell_hash, callback, data);
+	gnm_thin_set_foreach (sheet->cell_set, callback, data);
 }
 
 /**
@@ -3661,7 +3676,7 @@ sheet_cell_foreach (Sheet const *sheet, GHFunc callback, gpointer data)
 unsigned
 sheet_cells_count (Sheet const *sheet)
 {
-	return g_hash_table_size (sheet->cell_hash);
+	return gnm_thin_set_size (sheet->cell_set);
 }
 
 static void
@@ -3784,7 +3799,7 @@ sheet_cell_add_to_hash (Sheet *sheet, GnmCell *cell)
 
 	gnm_cell_unrender (cell);
 
-	g_hash_table_insert (sheet->cell_hash, &cell->pos, cell);
+	gnm_thin_set_insert (sheet->cell_set, cell);
 
 	if (gnm_sheet_merge_is_corner (sheet, &cell->pos))
 		cell->base.flags |= GNM_CELL_IS_MERGED;
@@ -3903,7 +3918,7 @@ sheet_cell_remove_from_hash (Sheet *sheet, GnmCell *cell)
 	cell_unregister_span (cell);
 	if (gnm_cell_expr_is_linked (cell))
 		dependent_unlink (GNM_CELL_TO_DEP (cell));
-	g_hash_table_remove (sheet->cell_hash, &cell->pos);
+	gnm_thin_set_remove (sheet->cell_set, cell);
 	cell->base.flags &= ~(GNM_CELL_IN_SHEET_LIST|GNM_CELL_IS_MERGED);
 }
 
@@ -4100,7 +4115,7 @@ sheet_destroy_contents (Sheet *sheet)
 
 	/* Remove all the cells */
 	sheet_cell_foreach (sheet, (GHFunc) &cb_remove_allcells, NULL);
-	g_hash_table_destroy (sheet->cell_hash);
+	gnm_thin_set_destroy (sheet->cell_set);
 
 	/* Delete in ascending order to avoid decrementing max_used each time */
 	for (i = 0; i <= max_col; ++i)
diff --git a/src/sheet.h b/src/sheet.h
index 374d5c1..5ccf86e 100644
--- a/src/sheet.h
+++ b/src/sheet.h
@@ -4,6 +4,7 @@
 
 #include "gnumeric.h"
 #include "colrow.h"
+#include "gutils.h"
 #include "position.h"
 #include <pango/pango.h>
 #include <goffice/goffice.h>
@@ -42,7 +43,7 @@ struct _Sheet {
 
 	ColRowCollection cols, rows;
 
-	GHashTable  *cell_hash;	/* The cells in hashed format */
+	GnmThinSet  *cell_set;	/* The cells in hashed format */
 
 	GnmNamedExprCollection *names;
 
