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:
+7
-148
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user