[Mesa-dev] [PATCH 26/26] glsl: Add Bloom filter to get_static_name
Ian Romanick
idr at freedesktop.org
Mon Jul 14 15:48:58 PDT 2014
From: Ian Romanick <ian.d.romanick at intel.com>
The use of the Bloom filter rejects the vast majority of strings that
are not in the table before expensive comparisons are performed. This
Bloom filter uses two hashes. One is explicit (calculate_hash in the
code), and the other is implicit. The implicit hash is just the length
of the name, and the filter is the switch-statement in
get_static_name_do_not_call that rejects name with lengths that are not
in the table.
No change Valgrind massif results for a trimmed apitrace of dota2.
NOTE: This patch could probably get squashed into the previous patch. I
kept it separate because I thought it would make things easier to
review.
Signed-off-by: Ian Romanick <ian.d.romanick at intel.com>
---
src/glsl/common_variable_names.py | 113 +++++++++++++++++++++++++++++++++++++-
1 file changed, 112 insertions(+), 1 deletion(-)
diff --git a/src/glsl/common_variable_names.py b/src/glsl/common_variable_names.py
index 7435a12..ee5571c 100644
--- a/src/glsl/common_variable_names.py
+++ b/src/glsl/common_variable_names.py
@@ -119,6 +119,46 @@ name_really_is_not_in_static_names(const char *name)
}
#endif /* DEBUG */
+static uint32_t
+calculate_hash(const char *str)
+{
+ uint32_t hash = 5381;
+
+ while (*str != '\\0') {
+ hash = (hash * 33) + *str;
+ str++;
+ }
+
+ return hash;
+}
+
+/**
+ * Check the Bloom filter for the name
+ */
+static bool
+name_is_in_Bloom_filter(const char *name)
+{
+ static const uint32_t filter[${len(filter)}] = {
+ % for i in range(0,len(filter), 4):
+ ${"{:#010x}".format(filter[i+0])}, ${"{:#010x}".format(filter[i+1])}, ${"{:#010x}".format(filter[i+2])}, ${"{:#010x}".format(filter[i+3])},
+ % endfor
+ };
+
+ const uint32_t h = calculate_hash(name);
+
+ if (h < ${min(all_hash)})
+ return false;
+
+ if (h > ${max(all_hash)})
+ return false;
+
+ const unsigned bit = (h - ${min(all_hash)}) % ${len(filter) * 32};
+ const unsigned i = bit / 32;
+ const unsigned j = bit % 32;
+
+ return (filter[i] & (1U << j)) != 0;
+}
+
/**
* Search the static name table for the specified name
*
@@ -129,6 +169,11 @@ name_really_is_not_in_static_names(const char *name)
static const char *
get_static_name_do_not_call(const char *name)
{
+ /* Do the trivial rejection test before anything.
+ */
+ if (!name_is_in_Bloom_filter(name))
+ return NULL;
+
const unsigned len = strlen(name);
const ${index_type} *table = NULL;
unsigned table_size = 0;
@@ -188,6 +233,14 @@ get_static_name(const char *name)
}
"""
+def calculate_hash(str):
+ hash = 5381
+
+ for c in str:
+ hash = ((hash * 33) + ord(c)) & 0x0ffffffff
+
+ return hash
+
common_names.sort()
# Partiation the list of names into sublists of names with the same length
@@ -213,8 +266,66 @@ elif base < (1 << 16):
else:
index_type = "uint32_t"
+all_hash = []
+for name in common_names:
+ all_hash.append(calculate_hash(name))
+
+# Experimentally determined that 8,192 bits is sufficient for this dataset.
+# This was determined by:
+#
+# 1. Modify the generated code to log a message on entry to get_static_name.
+#
+# 2. Modify the generated code to log a message when name_is_in_Bloom_filter
+# returns true.
+#
+# 3. Modifiy the generated code to log a message each time a name is rejected
+# due to its length. This is the 'default' case in the switch-statement.
+# This is an implicit hash that is part of the Bloom filter.
+#
+# 4. Modify the generated code to log a message each time a name has actually
+# been tested (using strcmp) and was not in the table. This means logging at
+# the end of get_static_name_do_not_call and inside the switch-statement where
+# "lonely" names are handled.
+#
+# 5. Run a ton of shaders through (e.g., shader-db) and collect the output.
+# Count the number of times each message occurred.
+#
+# Two hash functions were tried. The current one and Murmur2. Exhaustive
+# testing over shader-db produced the following results. There were a total
+# of 6,749,837 calls to get_static_name in each run. The # in the column
+# header refers messages mentioned in the list above.
+#
+# Bits Current hash Murmur2
+# #2 / #3 / #4 #2 / #3 / #4
+# 128 5249223 / 262597 / 4826172 763090 / 285531 / 317105
+# 256 4955982 / 162087 / 4633441 508512 / 152491 / 195567
+# 512 334736 / 111152 / 63130 381691 / 98277 / 122960
+# 1024 236521 / 43809 / 32258 326657 / 69346 / 96857
+# 2048 174275 / 1533 / 12288 258885 / 49537 / 48894
+# 4096 171153 / 341 / 10358 185632 / 682 / 24496
+# 8192 161649 / 264 / 931 163035 / 142 / 2439
+# 16384 160782 / 0 / 328 162764 / 15 / 2295
+#
+# Past 512 bits, the current hash was clearly superior to Murmur2. 8,192 bits
+# seems to be a sweet spot of a very small table size (1KiB) and a less than
+# 1% false-positive rate (931/161649).
+
+filter_bits = 8192
+m = min(all_hash)
+filter = []
+for i in range(0, filter_bits / 32):
+ filter.append(0)
+
+for h in all_hash:
+ idx = ((h - m) % filter_bits) / 32
+ bit = ((h - m) % filter_bits) % 32
+
+ filter[idx] = filter[idx] | (1 << bit)
+
from mako.template import Template
print Template(template).render(location_dict = location_dict,
sized_lists = sized_lists,
common_names = common_names,
- index_type = index_type)
+ index_type = index_type,
+ filter = filter,
+ all_hash = all_hash)
--
1.8.1.4
More information about the mesa-dev
mailing list