From a3d7c71ac2374ccb1e3746996c22332e9f48cddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Ol=C5=A1=C3=A1k?= Date: Tue, 6 Feb 2024 03:45:15 -0500 Subject: [PATCH] mesa: replace _mesa_HashTable::ht with util_sparse_array for faster lookups This reduces the overhead of _mesa_HashLookupLocked by 19% according to sysprof, which could be inaccurate. While this commit inlines _mesa_HashLookupLocked for a better gain, the testing was done without inlining to make it fair. Reviewed-by: Adam Jackson Reviewed-by: Pierre-Eric Pelloux-Prayer Part-of: --- src/mesa/main/hash.c | 155 ++----------------------------------------- src/mesa/main/hash.h | 70 ++++++++++++------- 2 files changed, 54 insertions(+), 171 deletions(-) diff --git a/src/mesa/main/hash.c b/src/mesa/main/hash.c index d7688f31f48..46b1ef7855b 100644 --- a/src/mesa/main/hash.c +++ b/src/mesa/main/hash.c @@ -41,63 +41,6 @@ #include "util/hash_table.h" #include "util/u_memory.h" -/** - * Magic GLuint object name that gets stored outside of the struct hash_table. - * - * The hash table needs a particular pointer to be the marker for a key that - * was deleted from the table, along with NULL for the "never allocated in the - * table" marker. Legacy GL allows any GLuint to be used as a GL object name, - * and we use a 1:1 mapping from GLuints to key pointers, so we need to be - * able to track a GLuint that happens to match the deleted key outside of - * struct hash_table. We tell the hash table to use "1" as the deleted key - * value, so that we test the deleted-key-in-the-table path as best we can. - */ -#define DELETED_KEY_VALUE 1 - -/** @{ - * Mapping from our use of GLuint as both the key and the hash value to the - * hash_table.h API - * - * There exist many integer hash functions, designed to avoid collisions when - * the integers are spread across key space with some patterns. In GL, the - * pattern (in the case of glGen*()ed object IDs) is that the keys are unique - * contiguous integers starting from 1. Because of that, we just use the key - * as the hash value, to minimize the cost of the hash function. If objects - * are never deleted, we will never see a collision in the table, because the - * table resizes itself when it approaches full, and thus key % table_size == - * key. - * - * The case where we could have collisions for genned objects would be - * something like: glGenBuffers(&a, 100); glDeleteBuffers(&a + 50, 50); - * glGenBuffers(&b, 100), because objects 1-50 and 101-200 are allocated at - * the end of that sequence, instead of 1-150. So far it doesn't appear to be - * a problem. - */ -static inline bool -uint_key_compare(const void *a, const void *b) -{ - return a == b; -} - -static inline uint32_t -uint_hash(GLuint id) -{ - return id; -} - -static inline uint32_t -uint_key_hash(const void *key) -{ - return uint_hash((uintptr_t)key); -} - -static inline void * -uint_key(GLuint id) -{ - return (void *)(uintptr_t) id; -} -/** @} */ - /** * Initialize a hash table. */ @@ -105,8 +48,7 @@ void _mesa_InitHashTable(struct _mesa_HashTable *table) { memset(table, 0, sizeof(*table)); - table->ht = _mesa_hash_table_create(NULL, uint_key_hash, uint_key_compare); - _mesa_hash_table_set_deleted_key(table->ht, uint_key(DELETED_KEY_VALUE)); + util_sparse_array_init(&table->array, sizeof(void*), 1024); util_idalloc_init(&table->id_alloc, 8); /* Mark ID = 0 as used, so that we don't return it. */ util_idalloc_reserve(&table->id_alloc, 0); @@ -134,19 +76,13 @@ _mesa_DeinitHashTable(struct _mesa_HashTable *table, { if (free_callback) { util_idalloc_foreach_no_zero_safe(&table->id_alloc, id) { - void *obj; - if (id == DELETED_KEY_VALUE) { - obj = table->deleted_key_data; - } else { - obj = _mesa_hash_table_search_pre_hashed(table->ht, uint_hash(id), - uint_key(id))->data; - } - free_callback(obj, userData); + free_callback(*(void**)util_sparse_array_get(&table->array, id), + userData); } } - _mesa_hash_table_destroy(table->ht, NULL); util_idalloc_fini(&table->id_alloc); + util_sparse_array_finish(&table->array); simple_mtx_destroy(&table->Mutex); } @@ -158,54 +94,6 @@ _mesa_HashEnableNameReuse(struct _mesa_HashTable *table) _mesa_HashUnlockMutex(table); } -/** - * Lookup an entry in the hash table without locking the mutex. - * - * The hash table mutex must be locked manually by calling - * _mesa_HashLockMutex() before calling this function. - * - * \param table the hash table. - * \param key the key. - * - * \return pointer to user's data or NULL if key not in table - */ -void * -_mesa_HashLookupLocked(struct _mesa_HashTable *table, GLuint key) -{ - const struct hash_entry *entry; - - assert(key); - - if (key == DELETED_KEY_VALUE) - return table->deleted_key_data; - - entry = _mesa_hash_table_search_pre_hashed(table->ht, - uint_hash(key), - uint_key(key)); - if (!entry) - return NULL; - - return entry->data; -} - -/** - * Lookup an entry in the hash table. - * - * \param table the hash table. - * \param key the key. - * - * \return pointer to user's data or NULL if key not in table - */ -void * -_mesa_HashLookup(struct _mesa_HashTable *table, GLuint key) -{ - void *res; - _mesa_HashLockMutex(table); - res = _mesa_HashLookupLocked(table, key); - _mesa_HashUnlockMutex(table); - return res; -} - /** * Insert a key/pointer pair into the hash table without locking the mutex. * If an entry with this key already exists we'll replace the existing entry. @@ -220,24 +108,12 @@ _mesa_HashLookup(struct _mesa_HashTable *table, GLuint key) void _mesa_HashInsertLocked(struct _mesa_HashTable *table, GLuint key, void *data) { - uint32_t hash = uint_hash(key); - struct hash_entry *entry; - assert(key); if (key > table->MaxKey) table->MaxKey = key; - if (key == DELETED_KEY_VALUE) { - table->deleted_key_data = data; - } else { - entry = _mesa_hash_table_search_pre_hashed(table->ht, hash, uint_key(key)); - if (entry) { - entry->data = data; - } else { - _mesa_hash_table_insert_pre_hashed(table->ht, hash, uint_key(key), data); - } - } + *(void**)util_sparse_array_get(&table->array, key) = data; util_idalloc_reserve(&table->id_alloc, key); } @@ -270,18 +146,8 @@ _mesa_HashInsert(struct _mesa_HashTable *table, GLuint key, void *data) void _mesa_HashRemoveLocked(struct _mesa_HashTable *table, GLuint key) { - struct hash_entry *entry; - assert(key); - - if (key == DELETED_KEY_VALUE) { - table->deleted_key_data = NULL; - } else { - entry = _mesa_hash_table_search_pre_hashed(table->ht, - uint_hash(key), - uint_key(key)); - _mesa_hash_table_remove(table->ht, entry); - } + *(void**)util_sparse_array_get(&table->array, key) = NULL; util_idalloc_free(&table->id_alloc, key); } @@ -309,14 +175,7 @@ _mesa_HashWalkLocked(struct _mesa_HashTable *table, assert(callback); util_idalloc_foreach_no_zero_safe(&table->id_alloc, id) { - void *obj; - if (id == DELETED_KEY_VALUE) { - obj = table->deleted_key_data; - } else { - obj = _mesa_hash_table_search_pre_hashed(table->ht, uint_hash(id), - uint_key(id))->data; - } - callback(obj, userData); + callback(*(void**)util_sparse_array_get(&table->array, id), userData); } } diff --git a/src/mesa/main/hash.h b/src/mesa/main/hash.h index f3cbdcfc2a0..11fa528feec 100644 --- a/src/mesa/main/hash.h +++ b/src/mesa/main/hash.h @@ -36,19 +36,20 @@ #include "c11/threads.h" #include "util/simple_mtx.h" +#include "util/sparse_array.h" #include "util/u_idalloc.h" /** - * The hash table data structure. + * The not-really-hash-table data structure. It pretends to be a hash table, + * but it uses util_idalloc to keep track of GL object IDs and + * util_sparse_array for storing entries. Lookups only access the array. */ struct _mesa_HashTable { - struct hash_table *ht; - GLuint MaxKey; /**< highest key inserted so far */ - simple_mtx_t Mutex; /**< mutual exclusion lock */ + struct util_sparse_array array; + /* Used when name reuse is enabled */ struct util_idalloc id_alloc; - - /** Value that would be in the table for DELETED_KEY_VALUE. */ - void *deleted_key_data; + simple_mtx_t Mutex; + GLuint MaxKey; /**< highest key inserted so far */ bool alloc_via_idalloc; }; @@ -60,18 +61,12 @@ _mesa_DeinitHashTable(struct _mesa_HashTable *table, void (*free_callback)(void *data, void *userData), void *userData); -void * -_mesa_HashLookup(struct _mesa_HashTable *table, GLuint key); - void _mesa_HashInsert(struct _mesa_HashTable *table, GLuint key, void *data); void _mesa_HashRemove(struct _mesa_HashTable *table, GLuint key); -void * -_mesa_HashLookupLocked(struct _mesa_HashTable *table, GLuint key); - void _mesa_HashInsertLocked(struct _mesa_HashTable *table, GLuint key, void *data); @@ -126,16 +121,6 @@ _mesa_HashUnlockMutex(struct _mesa_HashTable *table) simple_mtx_unlock(&table->Mutex); } -static inline void * -_mesa_HashLookupMaybeLocked(struct _mesa_HashTable *table, GLuint key, - bool locked) -{ - if (locked) - return _mesa_HashLookupLocked(table, key); - else - return _mesa_HashLookup(table, key); -} - static inline void _mesa_HashLockMaybeLocked(struct _mesa_HashTable *table, bool locked) { @@ -150,4 +135,43 @@ _mesa_HashUnlockMaybeLocked(struct _mesa_HashTable *table, bool locked) _mesa_HashUnlockMutex(table); } +/** + * Lookup an entry in the hash table without locking the mutex. + * + * The hash table mutex must be locked manually by calling + * _mesa_HashLockMutex() before calling this function. + * + * \return pointer to user's data or NULL if key not in table + */ +static inline void * +_mesa_HashLookupLocked(struct _mesa_HashTable *table, GLuint key) +{ + assert(key); + return *(void**)util_sparse_array_get(&table->array, key); +} + +/** + * Lookup an entry in the hash table. + * + * \return pointer to user's data or NULL if key not in table + */ +static inline void * +_mesa_HashLookup(struct _mesa_HashTable *table, GLuint key) +{ + _mesa_HashLockMutex(table); + void *res = _mesa_HashLookupLocked(table, key); + _mesa_HashUnlockMutex(table); + return res; +} + +static inline void * +_mesa_HashLookupMaybeLocked(struct _mesa_HashTable *table, GLuint key, + bool locked) +{ + if (locked) + return _mesa_HashLookupLocked(table, key); + else + return _mesa_HashLookup(table, key); +} + #endif