Imagine a language with five keywords, for simplicity.
keywords = ["AS", "FROM", "INSERT", "SELECT", "WHERE"];
tokens = [As, From, Insert, Select, Where];
Now you have a token that you need to interpret: let token_raw = "FROM";
let index = keywords.binary_search(token_raw);
let token = tokens[index];
As a binary search, that’s O(log N) string comparisons. In this worst case, it may try comparing with INSERT, then AS, then FROM before deciding index is 1, but for more keywords you have more comparisons.With perfect hashing, you devise a single hash function that hashes all possible values uniquely. Now we have something like this:
tokens = {914576: As, 73932: From, 5791456: Insert, 21596: Select, 86548: Where};
… with all the numbers calculated by hashing the strings at compile time. Then, instead of O(log N) string comparisons, you only need one hash and one hash table lookup: let token_raw = "FROM";
let token = tokens[hash(token_raw)]; switch(str[i++]){
case 'A':
switch(str[i++]){
case 'L': ...
case 'N':
switch(str[i++]){
case 'D':
found token!
case 'Y:
found token!
default:
syntax error!
}
...
}
case 'S': ...
case 'F': ...
...
}
Lexers already do this, and in each place where a token is found, it can know which token it was based on the DFA state that it's in.What I was trying to say is that the lexer is already doing a state machine, and the keyword can be determined from the state of the state machine, rather than re-determining it from the token substring itself.
Perfect hashing trades more computing for less cache misses.
[edit] Also, you shouldn't forget that sparse switch...case aren't O(1).
The old keyword lookup method uses binary search. Binary search is a search algorithm that must compare at most O(log n) entries before finding a match.
Making 1 keyword comparison instead of log n keyword comparisons yields the 20% speedup.
Worthwhile to note that that speedup is for the raw parsing, without parse-analysis (i.e. looking up the tables referenced and such), planning (figuring out how to execute the query) and execution. So end-user impact will be much smaller than 20%. But for some cases, e.g. when many columns are referenced, it'll be measurable.
Using a perfect hash table is definitely cool, but it's hard to imagine a query with so many keywords that this would have a measurable impact on total query execution time.
Addendum: And that indeed means that column references, unless quoted with "", used to go through the binary search and now go through the perfect hash.
Edit: Typo
Thanks for that insight!