[cairo-commit] 3 commits - perf/cairo-perf.h perf/cairo-perf-micro.c perf/micro src/cairo-hash.c

Andrea Canciani ranma42 at kemper.freedesktop.org
Wed Aug 3 03:48:45 PDT 2011


 perf/cairo-perf-micro.c     |    1 
 perf/cairo-perf.h           |    1 
 perf/micro/Makefile.sources |    1 
 perf/micro/hash-table.c     |  107 ++++++++++++++++++++++++
 src/cairo-hash.c            |  195 ++++++++++++++++++++++----------------------
 5 files changed, 210 insertions(+), 95 deletions(-)

New commits:
commit 3fbfa1beed291c58daa56b0a962c30b81c4248cb
Author: Andrea Canciani <ranma42 at gmail.com>
Date:   Tue Aug 2 10:50:51 2011 +0200

    hash: Code cleanup
    
    Simplify arrangements by keeping only table sizes, remove some useless
    code and fix make check.

diff --git a/src/cairo-hash.c b/src/cairo-hash.c
index e36dc67..f4fb7cd 100644
--- a/src/cairo-hash.c
+++ b/src/cairo-hash.c
@@ -80,46 +80,38 @@
  * Packard.
  */
 
-typedef struct _cairo_hash_table_arrangement {
-    unsigned long high_water_mark;
-    unsigned long size;
-    unsigned long rehash;
-} cairo_hash_table_arrangement_t;
-
-static const cairo_hash_table_arrangement_t hash_table_arrangements [] = {
-    { 16,		43,		41		},
-    { 32,		73,		71		},
-    { 64,		151,		149		},
-    { 128,		283,		281		},
-    { 256,		571,		569		},
-    { 512,		1153,		1151		},
-    { 1024,		2269,		2267		},
-    { 2048,		4519,		4517		},
-    { 4096,		9013,		9011		},
-    { 8192,		18043,		18041		},
-    { 16384,		36109,		36107		},
-    { 32768,		72091,		72089		},
-    { 65536,		144409,		144407		},
-    { 131072,		288361,		288359		},
-    { 262144,		576883,		576881		},
-    { 524288,		1153459,	1153457		},
-    { 1048576,		2307163,	2307161		},
-    { 2097152,		4613893,	4613891		},
-    { 4194304,		9227641,	9227639		},
-    { 8388608,		18455029,	18455027	},
-    { 16777216,		36911011,	36911009	},
-    { 33554432,		73819861,	73819859	},
-    { 67108864,		147639589,	147639587	},
-    { 134217728,	295279081,	295279079	},
-    { 268435456,	590559793,	590559791	}
+static const unsigned long hash_table_sizes[] = {
+    43,
+    73,
+    151,
+    283,
+    571,
+    1153,
+    2269,
+    4519,
+    9013,
+    18043,
+    36109,
+    72091,
+    144409,
+    288361,
+    576883,
+    1153459,
+    2307163,
+    4613893,
+    9227641,
+    18455029,
+    36911011,
+    73819861,
+    147639589,
+    295279081,
+    590559793
 };
 
-#define NUM_HASH_TABLE_ARRANGEMENTS ARRAY_LENGTH (hash_table_arrangements)
-
 struct _cairo_hash_table {
     cairo_hash_keys_equal_func_t keys_equal;
 
-    const cairo_hash_table_arrangement_t *arrangement;
+    const unsigned long *table_size;
     cairo_hash_entry_t **entries;
 
     unsigned long live_entries;
@@ -132,7 +124,7 @@ struct _cairo_hash_table {
  * @key_a: the first key to be compared
  * @key_b: the second key to be compared
  *
- * Provides a cairo_hash_keys_equal_func_t which always returns
+ * Provides a #cairo_hash_keys_equal_func_t which always returns
  * %TRUE. This is useful to create hash tables using keys whose hash
  * completely describes the key, because in this special case
  * comparing the hashes is sufficient to guarantee that the keys are
@@ -181,10 +173,10 @@ _cairo_hash_table_create (cairo_hash_keys_equal_func_t keys_equal)
     else
 	hash_table->keys_equal = keys_equal;
 
-    hash_table->arrangement = &hash_table_arrangements[0];
+    hash_table->table_size = &hash_table_sizes[0];
 
-    hash_table->entries = calloc (hash_table->arrangement->size,
-				  sizeof(cairo_hash_entry_t *));
+    hash_table->entries = calloc (*hash_table->table_size,
+				  sizeof (cairo_hash_entry_t *));
     if (unlikely (hash_table->entries == NULL)) {
 	_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
 	free (hash_table);
@@ -192,7 +184,7 @@ _cairo_hash_table_create (cairo_hash_keys_equal_func_t keys_equal)
     }
 
     hash_table->live_entries = 0;
-    hash_table->free_entries = hash_table->arrangement->size;
+    hash_table->free_entries = *hash_table->table_size;
     hash_table->iterating = 0;
 
     return hash_table;
@@ -224,8 +216,6 @@ _cairo_hash_table_destroy (cairo_hash_table_t *hash_table)
     assert (hash_table->iterating == 0);
 
     free (hash_table->entries);
-    hash_table->entries = NULL;
-
     free (hash_table);
 }
 
@@ -236,7 +226,7 @@ _cairo_hash_table_lookup_unique_key (cairo_hash_table_t *hash_table,
     unsigned long table_size, i, idx, step;
     cairo_hash_entry_t **entry;
 
-    table_size = hash_table->arrangement->size;
+    table_size = *hash_table->table_size;
     idx = key->hash % table_size;
 
     entry = &hash_table->entries[idx];
@@ -244,7 +234,7 @@ _cairo_hash_table_lookup_unique_key (cairo_hash_table_t *hash_table,
 	return entry;
 
     i = 1;
-    step = 1 + key->hash % hash_table->arrangement->rehash;
+    step = 1 + key->hash % (table_size - 2);
     do {
 	idx += step;
 	if (idx >= table_size)
@@ -279,7 +269,7 @@ _cairo_hash_table_manage (cairo_hash_table_t *hash_table)
 
     /* Keep between 12.5% and 50% entries in the hash table alive and
      * at least 25% free. */
-    unsigned long live_high = hash_table->arrangement->size >> 1;
+    unsigned long live_high = *hash_table->table_size >> 1;
     unsigned long live_low = live_high >> 2;
     unsigned long free_low = live_high >> 1;
 
@@ -287,21 +277,21 @@ _cairo_hash_table_manage (cairo_hash_table_t *hash_table)
 
     if (hash_table->live_entries > live_high)
     {
-	tmp.arrangement = hash_table->arrangement + 1;
+	tmp.table_size = hash_table->table_size + 1;
 	/* This code is being abused if we can't make a table big enough. */
-	assert (tmp.arrangement - hash_table_arrangements <
-		NUM_HASH_TABLE_ARRANGEMENTS);
+	assert (tmp.table_size - hash_table_sizes <
+		ARRAY_LENGTH (hash_table_sizes));
     }
     else if (hash_table->live_entries < live_low)
     {
 	/* Can't shrink if we're at the smallest size */
-	if (hash_table->arrangement == &hash_table_arrangements[0])
-	    tmp.arrangement = hash_table->arrangement;
+	if (hash_table->table_size == &hash_table_sizes[0])
+	    tmp.table_size = hash_table->table_size;
 	else
-	    tmp.arrangement = hash_table->arrangement - 1;
+	    tmp.table_size = hash_table->table_size - 1;
     }
 
-    if (tmp.arrangement == hash_table->arrangement &&
+    if (tmp.table_size == hash_table->table_size &&
 	hash_table->free_entries > free_low)
     {
 	/* The number of live entries is within the desired bounds
@@ -310,12 +300,12 @@ _cairo_hash_table_manage (cairo_hash_table_t *hash_table)
 	return CAIRO_STATUS_SUCCESS;
     }
 
-    new_size = tmp.arrangement->size;
+    new_size = *tmp.table_size;
     tmp.entries = calloc (new_size, sizeof (cairo_hash_entry_t*));
     if (unlikely (tmp.entries == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
-    for (i = 0; i < hash_table->arrangement->size; ++i) {
+    for (i = 0; i < *hash_table->table_size; ++i) {
 	if (ENTRY_IS_LIVE (hash_table->entries[i])) {
 	    *_cairo_hash_table_lookup_unique_key (&tmp, hash_table->entries[i])
 		= hash_table->entries[i];
@@ -324,7 +314,7 @@ _cairo_hash_table_manage (cairo_hash_table_t *hash_table)
 
     free (hash_table->entries);
     hash_table->entries = tmp.entries;
-    hash_table->arrangement = tmp.arrangement;
+    hash_table->table_size = tmp.table_size;
     hash_table->free_entries = new_size - hash_table->live_entries;
 
     return CAIRO_STATUS_SUCCESS;
@@ -348,7 +338,7 @@ _cairo_hash_table_lookup (cairo_hash_table_t *hash_table,
     cairo_hash_entry_t *entry;
     unsigned long table_size, i, idx, step;
 
-    table_size = hash_table->arrangement->size;
+    table_size = *hash_table->table_size;
     idx = key->hash % table_size;
 
     entry = hash_table->entries[idx];
@@ -359,7 +349,7 @@ _cairo_hash_table_lookup (cairo_hash_table_t *hash_table,
 	return NULL;
 
     i = 1;
-    step = 1 + key->hash % hash_table->arrangement->rehash;
+    step = 1 + key->hash % (table_size - 2);
     do {
 	idx += step;
 	if (idx >= table_size)
@@ -406,7 +396,7 @@ _cairo_hash_table_random_entry (cairo_hash_table_t	   *hash_table,
 
     assert (predicate != NULL);
 
-    table_size = hash_table->arrangement->size;
+    table_size = *hash_table->table_size;
     hash = rand ();
     idx = hash % table_size;
 
@@ -415,7 +405,7 @@ _cairo_hash_table_random_entry (cairo_hash_table_t	   *hash_table,
 	return entry;
 
     i = 1;
-    step = 1 + hash % hash_table->arrangement->rehash;
+    step = 1 + hash % (table_size - 2);
     do {
 	idx += step;
 	if (idx >= table_size)
@@ -481,7 +471,7 @@ _cairo_hash_table_lookup_exact_key (cairo_hash_table_t *hash_table,
     unsigned long table_size, i, idx, step;
     cairo_hash_entry_t **entry;
 
-    table_size = hash_table->arrangement->size;
+    table_size = *hash_table->table_size;
     idx = key->hash % table_size;
 
     entry = &hash_table->entries[idx];
@@ -489,7 +479,7 @@ _cairo_hash_table_lookup_exact_key (cairo_hash_table_t *hash_table,
 	return entry;
 
     i = 1;
-    step = 1 + key->hash % hash_table->arrangement->rehash;
+    step = 1 + key->hash % (table_size - 2);
     do {
 	idx += step;
 	if (idx >= table_size)
@@ -557,7 +547,7 @@ _cairo_hash_table_foreach (cairo_hash_table_t	      *hash_table,
 
     /* Mark the table for iteration */
     ++hash_table->iterating;
-    for (i = 0; i < hash_table->arrangement->size; i++) {
+    for (i = 0; i < *hash_table->table_size; i++) {
 	entry = hash_table->entries[i];
 	if (ENTRY_IS_LIVE(entry))
 	    hash_callback (entry, closure);
commit aaa10fbf125a80e5379172b8564384a945728cba
Author: Andrea Canciani <ranma42 at gmail.com>
Date:   Tue Aug 2 10:50:00 2011 +0200

    hash: Improve handling of dead entries
    
    When there are no free entries to terminate a search, checking that a
    key is not in the table requires probing every entry in the table,
    i.e. it degenerates in an O(n) operation.
    
    Rehashing when the number of free entries is less than 25% makes the
    expected lookup time O(1).
    
    The hash-table micro benchmark become 4-6x faster.
    
    Fixes https://bugs.freedesktop.org/show_bug.cgi?id=17399

diff --git a/src/cairo-hash.c b/src/cairo-hash.c
index 98f48a8..e36dc67 100644
--- a/src/cairo-hash.c
+++ b/src/cairo-hash.c
@@ -59,21 +59,20 @@
 #define ENTRY_IS_DEAD(entry) ((entry) == DEAD_ENTRY)
 #define ENTRY_IS_LIVE(entry) ((entry) >  DEAD_ENTRY)
 
-/* We expect keys will not be destroyed frequently, so our table does not
- * contain any explicit shrinking code nor any chain-coalescing code for
- * entries randomly deleted by memory pressure (except during rehashing, of
- * course). These assumptions are potentially bad, but they make the
- * implementation straightforward.
- *
- * Revisit later if evidence appears that we're using excessive memory from
- * a mostly-dead table.
- *
- * This table is open-addressed with double hashing. Each table size is a
- * prime chosen to be a little more than double the high water mark for a
- * given arrangement, so the tables should remain < 50% full. The table
- * size makes for the "first" hash modulus; a second prime (2 less than the
- * first prime) serves as the "second" hash modulus, which is smaller and
- * thus guarantees a complete permutation of table indices.
+/*
+ * This table is open-addressed with double hashing. Each table size
+ * is a prime and it makes for the "first" hash modulus; a second
+ * prime (2 less than the first prime) serves as the "second" hash
+ * modulus, which is smaller and thus guarantees a complete
+ * permutation of table indices.
+ *
+ * Hash tables are rehashed in order to keep between 12.5% and 50%
+ * entries in the hash table alive and at least 25% free. When table
+ * size is changed, the new table has about 25% live elements.
+ *
+ * The free entries guarantee an expected constant-time lookup.
+ * Doubling/halving the table in the described fashion guarantees
+ * amortized O(1) insertion/removal.
  *
  * This structure, and accompanying table, is borrowed/modified from the
  * file xserver/render/glyph.c in the freedesktop.org x server, with
@@ -124,6 +123,7 @@ struct _cairo_hash_table {
     cairo_hash_entry_t **entries;
 
     unsigned long live_entries;
+    unsigned long free_entries;
     unsigned long iterating;   /* Iterating, no insert, no resize */
 };
 
@@ -192,6 +192,7 @@ _cairo_hash_table_create (cairo_hash_keys_equal_func_t keys_equal)
     }
 
     hash_table->live_entries = 0;
+    hash_table->free_entries = hash_table->arrangement->size;
     hash_table->iterating = 0;
 
     return hash_table;
@@ -259,44 +260,54 @@ _cairo_hash_table_lookup_unique_key (cairo_hash_table_t *hash_table,
 }
 
 /**
- * _cairo_hash_table_resize:
+ * _cairo_hash_table_manage:
  * @hash_table: a hash table
  *
  * Resize the hash table if the number of entries has gotten much
  * bigger or smaller than the ideal number of entries for the current
- * size.
+ * size and guarantee some free entries to be used as lookup
+ * termination points.
  *
  * Return value: %CAIRO_STATUS_SUCCESS if successful or
  * %CAIRO_STATUS_NO_MEMORY if out of memory.
  **/
 static cairo_status_t
-_cairo_hash_table_resize (cairo_hash_table_t *hash_table)
+_cairo_hash_table_manage (cairo_hash_table_t *hash_table)
 {
     cairo_hash_table_t tmp;
     unsigned long new_size, i;
 
-    /* This keeps the hash table between 25% and 50% full. */
-    unsigned long high = hash_table->arrangement->high_water_mark;
-    unsigned long low = high >> 2;
-
-    if (hash_table->live_entries >= low && hash_table->live_entries <= high)
-	return CAIRO_STATUS_SUCCESS;
+    /* Keep between 12.5% and 50% entries in the hash table alive and
+     * at least 25% free. */
+    unsigned long live_high = hash_table->arrangement->size >> 1;
+    unsigned long live_low = live_high >> 2;
+    unsigned long free_low = live_high >> 1;
 
     tmp = *hash_table;
 
-    if (hash_table->live_entries > high)
+    if (hash_table->live_entries > live_high)
     {
 	tmp.arrangement = hash_table->arrangement + 1;
 	/* This code is being abused if we can't make a table big enough. */
 	assert (tmp.arrangement - hash_table_arrangements <
 		NUM_HASH_TABLE_ARRANGEMENTS);
     }
-    else /* hash_table->live_entries < low */
+    else if (hash_table->live_entries < live_low)
     {
 	/* Can't shrink if we're at the smallest size */
 	if (hash_table->arrangement == &hash_table_arrangements[0])
-	    return CAIRO_STATUS_SUCCESS;
-	tmp.arrangement = hash_table->arrangement - 1;
+	    tmp.arrangement = hash_table->arrangement;
+	else
+	    tmp.arrangement = hash_table->arrangement - 1;
+    }
+
+    if (tmp.arrangement == hash_table->arrangement &&
+	hash_table->free_entries > free_low)
+    {
+	/* The number of live entries is within the desired bounds
+	 * (we're not going to resize the table) and we have enough
+	 * free entries. Do nothing. */
+	return CAIRO_STATUS_SUCCESS;
     }
 
     new_size = tmp.arrangement->size;
@@ -314,6 +325,7 @@ _cairo_hash_table_resize (cairo_hash_table_t *hash_table)
     free (hash_table->entries);
     hash_table->entries = tmp.entries;
     hash_table->arrangement = tmp.arrangement;
+    hash_table->free_entries = new_size - hash_table->live_entries;
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -361,6 +373,7 @@ _cairo_hash_table_lookup (cairo_hash_table_t *hash_table,
 	    return NULL;
     } while (++i < table_size);
 
+    ASSERT_NOT_REACHED;
     return NULL;
 }
 
@@ -440,21 +453,23 @@ cairo_status_t
 _cairo_hash_table_insert (cairo_hash_table_t *hash_table,
 			  cairo_hash_entry_t *key_and_value)
 {
+    cairo_hash_entry_t **entry;
     cairo_status_t status;
 
     /* Insert is illegal while an iterator is running. */
     assert (hash_table->iterating == 0);
 
-    hash_table->live_entries++;
-    status = _cairo_hash_table_resize (hash_table);
-    if (unlikely (status)) {
-	/* abort the insert... */
-	hash_table->live_entries--;
+    status = _cairo_hash_table_manage (hash_table);
+    if (unlikely (status))
 	return status;
-    }
 
-    *_cairo_hash_table_lookup_unique_key (hash_table,
-					  key_and_value) = key_and_value;
+    entry = _cairo_hash_table_lookup_unique_key (hash_table, key_and_value);
+
+    if (ENTRY_IS_FREE (*entry))
+	hash_table->free_entries--;
+
+    *entry = key_and_value;
+    hash_table->live_entries++;
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -513,7 +528,7 @@ _cairo_hash_table_remove (cairo_hash_table_t *hash_table,
 	 * memory to shrink the hash table. It does leave the table in a
 	 * consistent state, and we've already succeeded in removing the
 	 * entry, so we don't examine the failure status of this call. */
-	_cairo_hash_table_resize (hash_table);
+	_cairo_hash_table_manage (hash_table);
     }
 }
 
@@ -554,6 +569,6 @@ _cairo_hash_table_foreach (cairo_hash_table_t	      *hash_table,
     if (--hash_table->iterating == 0) {
 	/* Should we fail to shrink the hash table, it is left unaltered,
 	 * and we don't need to propagate the error status. */
-	_cairo_hash_table_resize (hash_table);
+	_cairo_hash_table_manage (hash_table);
     }
 }
commit 374b26ff03b9f36a7be974e65e42938a3c11b04c
Author: Andrea Canciani <ranma42 at gmail.com>
Date:   Wed Aug 3 09:49:08 2011 +0200

    perf: Add hash table benchmark
    
    A benchmark to test the speed of hash tables when inserting and
    removing a huge number of elements.
    
    Although originally hash tables were assumed not to get many
    deletions, in practice they are now being used as caches in multiple
    places. This means that they often have a fixed number of live
    elements and an element is evicted whenever a new element is inserted
    (this happens explicitly for cairo_cache_t objects, but also, for
    example, in scaled_font_map + holdovers). This access pattern is very
    inefficient with the current implementation.

diff --git a/perf/cairo-perf-micro.c b/perf/cairo-perf-micro.c
index ee5269a..ca447de 100644
--- a/perf/cairo-perf-micro.c
+++ b/perf/cairo-perf-micro.c
@@ -548,6 +548,7 @@ const cairo_perf_case_t perf_cases[] = {
     { hatching,   64, 512},
     { tessellate, 100, 100},
     { subimage_copy, 16, 512},
+    { hash_table, 16, 16},
     { pattern_create_radial, 16, 16},
     { zrusin, 415, 415},
     { world_map, 800, 800},
diff --git a/perf/cairo-perf.h b/perf/cairo-perf.h
index 59a0d22..32edc74 100644
--- a/perf/cairo-perf.h
+++ b/perf/cairo-perf.h
@@ -193,6 +193,7 @@ CAIRO_PERF_DECL (hatching);
 CAIRO_PERF_DECL (tessellate);
 CAIRO_PERF_DECL (text);
 CAIRO_PERF_DECL (glyphs);
+CAIRO_PERF_DECL (hash_table);
 CAIRO_PERF_DECL (pattern_create_radial);
 CAIRO_PERF_DECL (zrusin);
 CAIRO_PERF_DECL (world_map);
diff --git a/perf/micro/Makefile.sources b/perf/micro/Makefile.sources
index aae9e21..90f4bb6 100644
--- a/perf/micro/Makefile.sources
+++ b/perf/micro/Makefile.sources
@@ -5,6 +5,7 @@ libcairo_perf_micro_sources = \
 	disjoint.c		\
 	fill.c			\
 	hatching.c		\
+	hash-table.c		\
 	long-lines.c		\
 	mosaic.c		\
 	paint.c			\
diff --git a/perf/micro/hash-table.c b/perf/micro/hash-table.c
new file mode 100644
index 0000000..79978f5
--- /dev/null
+++ b/perf/micro/hash-table.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2011 Andrea Canciani
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Red Hat, Inc. not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Red Hat, Inc. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Andrea Canciani <ranma42 at gmail.com>
+ */
+
+#include "cairo-perf.h"
+
+#define ITER 1000
+#define HOLDOVERS 256
+#define LIVE_ENTRIES 257
+#define ACTIVE_FONTS (LIVE_ENTRIES - HOLDOVERS - 1)
+
+/*
+ * The original implementation of hash tables was very inefficient, as
+ * pointed out in https://bugs.freedesktop.org/show_bug.cgi?id=17399
+ *
+ * This benchmark tries to fill up the scaled_font_map hash table to
+ * show the O(n) behavior.
+ */
+
+static cairo_perf_ticks_t
+do_hash_table (cairo_t *cr, int width, int height, int loops)
+{
+    cairo_scaled_font_t *active_fonts[ACTIVE_FONTS];
+    cairo_matrix_t m;
+    int i;
+
+    cairo_matrix_init_identity (&m);
+
+    /* Touch HOLDOVERS scaled fonts to fill up the holdover list. */
+    for (i = 0; i < HOLDOVERS; i++) {
+	m.yy = m.xx * (i + 1);
+	cairo_set_font_matrix (cr, &m);
+	cairo_get_scaled_font (cr);
+    }
+
+    /*
+     * Reference some scaled fonts so that they will be kept in the
+     * scaled fonts map. We want LIVE_ENTRIES elements in the font
+     * map, but cairo keeps HOLDOVERS recently used fonts in it and we
+     * will be activating a new font in the cr context, so we just
+     * keep references to ACTIVE_FONTS fonts.
+     *
+     * Note: setting LIVE_ENTRIES == HOLDOVERS+1 means that we keep no
+     * font in active_fonts and the slowness is caused by the holdover
+     * fonts only.
+     */
+    for (i = 0; i < ACTIVE_FONTS; i++) {
+	cairo_scaled_font_t *scaled_font;
+
+	m.yy = m.xx * (i + 1);
+	cairo_set_font_matrix (cr, &m);
+
+	scaled_font = cairo_get_scaled_font (cr);
+	active_fonts[i] = cairo_scaled_font_reference (scaled_font);
+    }
+
+    cairo_perf_timer_start ();
+
+    while (loops--) {
+	m.xx += 1.0;
+
+	/* Generate ITER new scaled fonts per loop */
+	for (i = 0; i < ITER; i++) {
+	    m.yy = m.xx * (i + 1);
+	    cairo_set_font_matrix (cr, &m);
+	    cairo_get_scaled_font (cr);
+	}
+    }
+
+    cairo_perf_timer_stop ();
+
+    for (i = 0; i < ACTIVE_FONTS; i++)
+	cairo_scaled_font_destroy (active_fonts[i]);
+
+    return cairo_perf_timer_elapsed ();
+}
+
+void
+hash_table (cairo_perf_t *perf, cairo_t *cr, int width, int height)
+{
+    if (! cairo_perf_can_run (perf, "hash-table", NULL))
+	return;
+
+    cairo_perf_cover_sources_and_operators (perf, "hash-table",
+					    do_hash_table, NULL);
+}


More information about the cairo-commit mailing list