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 <ajax@redhat.com>
Reviewed-by: Pierre-Eric Pelloux-Prayer <pierre-eric.pelloux-prayer@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/27494>
This commit is contained in:
Marek Olšák
2024-02-06 03:45:15 -05:00
committed by Marge Bot
parent bccc983012
commit a3d7c71ac2
2 changed files with 54 additions and 171 deletions
+7 -148
View File
@@ -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);
}
}
+47 -23
View File
@@ -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