├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include ├── words-extract-match.h ├── words-extract-small.h ├── words-extract.h └── words.h ├── main.cpp └── switch_fnv1a.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(stringswitch) 3 | 4 | if(NOT CMAKE_BUILD_TYPE) 5 | set(CMAKE_BUILD_TYPE Release) 6 | endif() 7 | set(CMAKE_CXX_FLAGS "-Wall -Wextra") 8 | set(CMAKE_CXX_FLAGS_DEBUG "-g") 9 | set(CMAKE_CXX_FLAGS_RELEASE "-Ofast") 10 | 11 | add_executable(demo main.cpp) 12 | set_property(TARGET demo PROPERTY CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set_property(TARGET demo PROPERTY CXX_STANDARD 17) 14 | set_property(TARGET demo PROPERTY CMAKE_CXX_EXTENSIONS OFF) 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Xavier Roche 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Switch/Case strings using constexpr long hash 2 | 3 | ## The Problem 4 | 5 | * To avoid questionable code like this: 6 | 7 | ```c++ 8 | } else if (key == "poney") { 9 | } else if (key == "elephant") { 10 | } else if (key == "dog") { 11 | } else if (key == "kitten") { 12 | ``` 13 | 14 | ie.: *cumbersome, and not quite good in term of performances* 15 | 16 | * We basically want to do that in `C++`: 17 | 18 | ```c++ 19 | switch(argv[i]) { 20 | case "poney": 21 | break; 22 | case "elephant": 23 | break; 24 | case "dog": 25 | break; 26 | case "kitten": 27 | break; 28 | } 29 | ``` 30 | 31 | ## The Solution 32 | 33 | We use a `constexpr` metaprogrammed long hash, with fancy custom string prefixes, allowing to do *nearly* what we want: 34 | 35 | ```c++ 36 | switch(fnv1a128::hash(argv[i])) { 37 | case "poney"_fnv1a128: 38 | break; 39 | case "elephant"_fnv1a128: 40 | break; 41 | case "dog"_fnv1a128: 42 | break; 43 | case "kitten"_fnv1a128: 44 | break; 45 | } 46 | ``` 47 | 48 | 👉 See the [`switch_fnv1a.h`](switch_fnv1a.h) source code and the [`main.cpp`](main.cpp) sample. 49 | 50 | ## Discussion 51 | 52 | ### Is This Good Enough ? 53 | 54 | #### Simplicity 55 | 56 | Nearly what we want, only need a call to `fnv1a128::hash` in the `switch`, and `_fnv1a128` string prefix in `case`. 57 | 58 | #### Collisions 59 | 60 | Using a rather known *128-bit* hashing (Fnv1-a) *should* limit the risks of accidental collisions (odds are `1/2^64` if the hash is truly distributed). FNV hashes are typically [designed to be fast while maintaining a low collision rate](https://tools.ietf.org/html/draft-eastlake-fnv-16). 61 | 62 | Collision rate of `1/2^64` is considered low enough not to worry about them. Risks of being killed by a meteor each year (> `1/2^27`) is several orders of magnitude more worrying, actually ([The Economist](https://www.cnet.com/news/odds-of-dying-from-an-asteroid-strike-1-in-74817414/) and [W. Casey, *Cybersecurity and Applied Mathematics*](https://www.sciencedirect.com/book/9780128044520/cybersecurity-and-applied-mathematics)). 63 | 64 | #### Attacks 65 | 66 | Question remains on *non-accidental* collisions (ie. *attacks*). One possible solution would be to use a stronger hash (ie. cryptographic hash) such as SHA256 (~~XOR-folded~~ [truncated](https://csrc.nist.gov/csrc/media/events/first-cryptographic-hash-workshop/documents/kelsey_truncation.pdf) to 128-bits), such as [this one](https://github.com/aguinet/sha256_literal) or [this one](https://github.com/elbeno/constexpr/), but this would make the initial string computation much slower, not mentioning the build time. 67 | 68 | As [suggested](https://tools.ietf.org/html/draft-eastlake-fnv-16#section-2.2), mitigating possible attacks could involve a initial salt, such as `fnv1a128::hash(__FILE__)` or `fnv1a128::hash(__DATE__)` (with non-reproducible build), but this would not allow to mitigate attacks when compiled code itself is known. 69 | 70 | #### Performances 71 | 72 | * All hash string literal are computed at **build-time** 73 | * Hash is computed once at runtime (for the input to be compared) with a reasonable cost (ie. one 128-bit multiply and one 8-bit xor per character) 74 | * The `switch/case` construct is rather well-optimized (using binary search-like dispatch, ie. `O(log(N))`) 75 | * 128-bit `__uint128_t` are rather well-handled for this usage (thanks to AVX extensions) 76 | 77 | Three `std::string::operator==` calls are typically as slow as a complete `switch/case` set containing 1,000 different cases. 78 | 79 | *All these results need to be taken with a grain of salt, being based on highly volatile micro-benchmarks, you have been warned.* 80 | 81 | #### Benchmarks (micro-benchmarks only) 82 | 83 | Quick benchmarks with output emitted (redirected to `/dev/null`) 84 | 85 | * Matching 100,000 times 100 different strings with **10 cases**, printing them to stdout: **1.5x** faster 86 | * Matching 100,000 times 100 different strings with **100 cases**, printing them to stdout: **3.9x** faster 87 | * Matching 100,000 times 100 different strings with **1000 cases**, printing them to stdout: **120x** faster 88 | 89 | Quick benchmarks with no output emitted (pure computation) 90 | 91 | * Matching 100,000 times 100 different strings with **10 cases**: **2.3x** faster 92 | * Matching 100,000 times 100 different strings with **100 cases**: **9x** faster 93 | * Matching 100,000 times 100 different strings with **1000 cases**: **400x** faster 94 | 95 | Further tests are needed to assess the impact on performances on real-world code (such as configuration parsing code typically). 96 | 97 | #### Unit Tests 98 | 99 | The wonders of meta-programming allows you to actually integrate unit tests in the code itself: 100 | 101 | ```c++ 102 | // Static unit tests: 103 | static_assert("hello"_fnv1a32 == 0x4f9f2cab); 104 | static_assert("hello"_fnv1a64 == 0xa430d84680aabd0b); 105 | static_assert("hello"_fnv1a128 == Pack128(0xe3e1efd54283d94f, 0x7081314b599d31b3)); 106 | ``` 107 | 108 | ### Disasembled Example 109 | 110 | As an example, let's see how the compiler dispatches four `case`: 111 | 112 | ```c++ 113 | const char* dispatch(const __uint128_t match) 114 | { 115 | switch (match) { 116 | case "poney"_fnv1a128: 117 | return "I want one, too!"; 118 | case "elephant"_fnv1a128: 119 | return "Not in my apartment please!"; 120 | case "dog"_fnv1a128: 121 | return "Good puppy!"; 122 | case "kitten"_fnv1a128: 123 | return "Aawwwwwwww!"; 124 | default: 125 | return "Don't know this animal!"; 126 | } 127 | } 128 | ``` 129 | 130 | Even for four cases (and a default), the compiler optimized already the tests by with binary search: the "dog" and poney cases blocks are split from the "kitten" and "elephant" ones. 131 | 132 | The first test is done through two regular 64-bit comparisons (and immediate values), the remaining ones are using AVX extensions (comparisons with PC-relative indexed address). 133 | 134 | ```asm 135 | movabs $0x6efcb17ab10f2a3d,%rax # ("kitten"_fnv1a128 - 1)&FFFFFFFF 136 | cmp %rdi,%rax 137 | movabs $0xfcd05704e13c64bf,%rax # ("kitten"_fnv1a128 - 1)>> 64 138 | movq %rdi,%xmm0 139 | movq %rsi,%xmm1 140 | punpcklqdq %xmm1,%xmm0 141 | sbb %rsi,%rax 142 | jl 0x400bba 143 | 144 | movdqa 0x17bce(%rip),%xmm1 # "dog"_fnv1a128 (__uint128_t) 145 | pcmpeqb %xmm0,%xmm1 146 | pmovmskb %xmm1,%eax 147 | cmp $0xffff,%eax 148 | je 0x400bf0 149 | 150 | pcmpeqb 0x17bc7(%rip),%xmm0 # "poney"_fnv1a128 (__uint128_t) 151 | pmovmskb %xmm0,%eax 152 | cmp $0xffff,%eax 153 | jne 0x400bea 154 | 155 | mov $0x41c6a0,%eax # "I want one, too!" (const char*) 156 | retq 157 | 158 | test_kitten: 159 | movdqa 0x17b7e(%rip),%xmm1 # "kitten"_fnv1a128 (__uint128_t) 160 | pcmpeqb %xmm0,%xmm1 161 | pmovmskb %xmm1,%eax 162 | cmp $0xffff,%eax 163 | je 0x400bf6 164 | 165 | pcmpeqb 0x17b77(%rip),%xmm0 # "elephant"_fnv1a128 (__uint128_t) 166 | pmovmskb %xmm0,%eax 167 | cmp $0xffff,%eax 168 | 169 | jne 0x400bea 170 | mov $0x41c6b1,%eax # "Not in my apartment please!" (const char*) 171 | retq 172 | 173 | unknown: 174 | mov $0x41c6e6,%eax # "Don't know this animal!" (const char*) 175 | retq 176 | 177 | puppy: 178 | mov $0x41c6ce,%eax # "Good puppy!" (const char*) 179 | retq 180 | 181 | kitten: 182 | mov $0x41c6da,%eax # "Aawwwwwwww! (const char*) 183 | retq 184 | ``` 185 | 186 | The initial hash computation itself is rather well-optimized, as the 128-bit prime number used (0x1000000000000000000013b) is well-fit for split 64-bit `mul`. 187 | 188 | ```asm 189 | test %rsi,%rsi 190 | je 0x400de9 191 | 192 | movabs $0x6c62272e07bb0142,%rcx 193 | movabs $0x62b821756295c58d,%rax 194 | lea -0x1(%rsi),%rdx 195 | mov %esi,%r9d 196 | and $0x3,%r9d 197 | cmp $0x3,%rdx 198 | jae 0x400dfe 199 | xor %edx,%edx 200 | xor %r10d,%r10d 201 | test %r9,%r9 202 | jne 0x400ea9 203 | 204 | label_ret: 205 | retq 206 | 207 | empty_string: 208 | movabs $0x6c62272e07bb0142,%rdx 209 | movabs $0x62b821756295c58d,%rax 210 | retq 211 | 212 | label2: 213 | push %rbx 214 | sub %r9,%rsi 215 | xor %r10d,%r10d 216 | mov $0x13b,%r11d 217 | nopl 0x0(%rax,%rax,1) 218 | 219 | label3: 220 | movzbl (%rdi,%r10,1),%r8d 221 | xor %rax,%r8 222 | mov %r8,%rax 223 | mul %r11 224 | shl $0x18,%r8 225 | add %rdx,%r8 226 | imul $0x13b,%rcx,%rbx 227 | add %r8,%rbx 228 | movzbl 0x1(%rdi,%r10,1),%ecx 229 | xor %rax,%rcx 230 | mov %rcx,%rax 231 | mul %r11 232 | shl $0x18,%rcx 233 | add %rdx,%rcx 234 | imul $0x13b,%rbx,%rbx 235 | add %rcx,%rbx 236 | movzbl 0x2(%rdi,%r10,1),%ecx 237 | xor %rax,%rcx 238 | mov %rcx,%rax 239 | mul %r11 240 | shl $0x18,%rcx 241 | add %rdx,%rcx 242 | imul $0x13b,%rbx,%rdx 243 | add %rcx,%rdx 244 | movzbl 0x3(%rdi,%r10,1),%ecx 245 | xor %rax,%rcx 246 | imul $0x13b,%rdx,%rbx 247 | mov %rcx,%rax 248 | mul %r11 249 | shl $0x18,%rcx 250 | add %rdx,%rcx 251 | add %rbx,%rcx 252 | add $0x4,%r10 253 | cmp %r10,%rsi 254 | jne 0x400e10 255 | mov %rcx,%rdx 256 | pop %rbx 257 | test %r9,%r9 258 | je 0x400de8 259 | 260 | label4: 261 | add %r10,%rdi 262 | neg %r9 263 | mov $0x13b,%r8d 264 | data16 nopw %cs:0x0(%rax,%rax,1) 265 | label5: 266 | movzbl (%rdi),%esi 267 | xor %rax,%rsi 268 | mov %rsi,%rax 269 | mul %r8 270 | shl $0x18,%rsi 271 | add %rdx,%rsi 272 | imul $0x13b,%rcx,%rcx 273 | add %rsi,%rcx 274 | add $0x1,%rdi 275 | add $0x1,%r9 276 | jne 0x400ec0 277 | mov %rcx,%rdx 278 | retq 279 | ``` 280 | 281 | ## Thanks 282 | 283 | Thanks to [Algolia](https://www.algolia.com/) for giving me the opportunity to play with those kind of stuff during my work! 284 | -------------------------------------------------------------------------------- /include/words-extract-match.h: -------------------------------------------------------------------------------- 1 | /* tail -100 include/words-extract.h 2 | */ 3 | 4 | WORD("tobaccos"); 5 | WORD("plannings"); 6 | WORD("press"); 7 | WORD("muggers"); 8 | WORD("fortress"); 9 | WORD("reappoints"); 10 | WORD("scruples"); 11 | WORD("gnomes"); 12 | WORD("apocalyptic"); 13 | WORD("moderates"); 14 | WORD("halving"); 15 | WORD("weathering"); 16 | WORD("blowouts"); 17 | WORD("distinction"); 18 | WORD("dumping"); 19 | WORD("poultry"); 20 | WORD("therapists"); 21 | WORD("maximum"); 22 | WORD("defers"); 23 | WORD("lankier"); 24 | WORD("approached"); 25 | WORD("winked"); 26 | WORD("construing"); 27 | WORD("paradoxical"); 28 | WORD("orthogonality"); 29 | WORD("localizing"); 30 | WORD("fleecing"); 31 | WORD("delimit"); 32 | WORD("footlights"); 33 | WORD("blessedly"); 34 | WORD("interrogated"); 35 | WORD("fantasy"); 36 | WORD("foxtrots"); 37 | WORD("hit"); 38 | WORD("malaise"); 39 | WORD("invisibility"); 40 | WORD("correcter"); 41 | WORD("prolongs"); 42 | WORD("supporters"); 43 | WORD("hubbub"); 44 | WORD("audible"); 45 | WORD("scrutiny"); 46 | WORD("smells"); 47 | WORD("detaching"); 48 | WORD("castings"); 49 | WORD("nominatives"); 50 | WORD("solemnly"); 51 | WORD("babysitters"); 52 | WORD("morbid"); 53 | WORD("invariable"); 54 | WORD("kinswoman"); 55 | WORD("moles"); 56 | WORD("cowardice"); 57 | WORD("prawning"); 58 | WORD("falloff"); 59 | WORD("bulimia"); 60 | WORD("aptitude"); 61 | WORD("diploma"); 62 | WORD("scholars"); 63 | WORD("broadens"); 64 | WORD("persecutes"); 65 | WORD("retrospection"); 66 | WORD("palms"); 67 | WORD("daughter"); 68 | WORD("misgiving"); 69 | WORD("variations"); 70 | WORD("underclassmen"); 71 | WORD("coronae"); 72 | WORD("rivets"); 73 | WORD("bipartisan"); 74 | WORD("blow"); 75 | WORD("repairing"); 76 | WORD("dourer"); 77 | WORD("scants"); 78 | WORD("moms"); 79 | WORD("sloshed"); 80 | WORD("equalizer"); 81 | WORD("caddied"); 82 | WORD("flattery"); 83 | WORD("meritorious"); 84 | WORD("propagates"); 85 | WORD("unsympathetic"); 86 | WORD("casuals"); 87 | WORD("gobbing"); 88 | WORD("aground"); 89 | WORD("excavator"); 90 | WORD("possessiveness"); 91 | WORD("cracked"); 92 | WORD("canneries"); 93 | WORD("gambles"); 94 | WORD("nightclubbed"); 95 | WORD("catchwords"); 96 | WORD("quashing"); 97 | WORD("summons"); 98 | WORD("fizzing"); 99 | WORD("noisiness"); 100 | WORD("shapeliness"); 101 | WORD("ligatures"); 102 | WORD("aphrodisiac"); 103 | WORD("quicker"); 104 | -------------------------------------------------------------------------------- /include/words-extract-small.h: -------------------------------------------------------------------------------- 1 | WORD("tobaccos"); 2 | WORD("plannings"); 3 | WORD("press"); 4 | WORD("muggers"); 5 | WORD("fortress"); 6 | WORD("reappoints"); 7 | WORD("scruples"); 8 | WORD("gnomes"); 9 | WORD("apocalyptic"); 10 | WORD("moderates"); 11 | WORD("halving"); 12 | WORD("weathering"); 13 | WORD("blowouts"); 14 | WORD("distinction"); 15 | WORD("dumping"); 16 | WORD("poultry"); 17 | WORD("therapists"); 18 | WORD("maximum"); 19 | WORD("defers"); 20 | WORD("lankier"); 21 | WORD("approached"); 22 | WORD("winked"); 23 | WORD("construing"); 24 | WORD("paradoxical"); 25 | WORD("orthogonality"); 26 | WORD("localizing"); 27 | WORD("fleecing"); 28 | WORD("delimit"); 29 | WORD("footlights"); 30 | WORD("blessedly"); 31 | WORD("interrogated"); 32 | WORD("fantasy"); 33 | WORD("foxtrots"); 34 | WORD("hit"); 35 | WORD("malaise"); 36 | WORD("invisibility"); 37 | WORD("correcter"); 38 | WORD("prolongs"); 39 | WORD("supporters"); 40 | WORD("hubbub"); 41 | WORD("audible"); 42 | WORD("scrutiny"); 43 | WORD("smells"); 44 | WORD("detaching"); 45 | WORD("castings"); 46 | WORD("nominatives"); 47 | WORD("solemnly"); 48 | WORD("babysitters"); 49 | WORD("morbid"); 50 | WORD("invariable"); 51 | WORD("kinswoman"); 52 | WORD("moles"); 53 | WORD("cowardice"); 54 | WORD("prawning"); 55 | WORD("falloff"); 56 | WORD("bulimia"); 57 | WORD("aptitude"); 58 | WORD("diploma"); 59 | WORD("scholars"); 60 | WORD("broadens"); 61 | WORD("persecutes"); 62 | WORD("retrospection"); 63 | WORD("palms"); 64 | WORD("daughter"); 65 | WORD("misgiving"); 66 | WORD("variations"); 67 | WORD("underclassmen"); 68 | WORD("coronae"); 69 | WORD("rivets"); 70 | WORD("bipartisan"); 71 | WORD("blow"); 72 | WORD("repairing"); 73 | WORD("dourer"); 74 | WORD("scants"); 75 | WORD("moms"); 76 | WORD("sloshed"); 77 | WORD("equalizer"); 78 | WORD("caddied"); 79 | WORD("flattery"); 80 | WORD("meritorious"); 81 | WORD("propagates"); 82 | WORD("unsympathetic"); 83 | WORD("casuals"); 84 | WORD("gobbing"); 85 | WORD("aground"); 86 | WORD("excavator"); 87 | WORD("possessiveness"); 88 | WORD("cracked"); 89 | WORD("canneries"); 90 | WORD("gambles"); 91 | WORD("nightclubbed"); 92 | WORD("catchwords"); 93 | WORD("quashing"); 94 | WORD("summons"); 95 | WORD("fizzing"); 96 | WORD("noisiness"); 97 | WORD("shapeliness"); 98 | WORD("ligatures"); 99 | WORD("aphrodisiac"); 100 | WORD("quicker"); 101 | -------------------------------------------------------------------------------- /include/words-extract.h: -------------------------------------------------------------------------------- 1 | /* grep -v "'" /usr/share/dict/words|grep -E '^[a-z]'|sort -R|head -1000|sed -e 's/^/WORD("/' -e 's/$/");/' > include/words-extract.h 2 | */ 3 | 4 | WORD("tapped"); 5 | WORD("existing"); 6 | WORD("brightest"); 7 | WORD("spirituals"); 8 | WORD("frenziedly"); 9 | WORD("uptown"); 10 | WORD("cankers"); 11 | WORD("our"); 12 | WORD("supermodel"); 13 | WORD("haste"); 14 | WORD("oakum"); 15 | WORD("inventing"); 16 | WORD("flotilla"); 17 | WORD("dopes"); 18 | WORD("rowboats"); 19 | WORD("cider"); 20 | WORD("bulletining"); 21 | WORD("bungs"); 22 | WORD("privatizes"); 23 | WORD("canvasback"); 24 | WORD("widespread"); 25 | WORD("piled"); 26 | WORD("featherweights"); 27 | WORD("hides"); 28 | WORD("paternal"); 29 | WORD("palliates"); 30 | WORD("rockers"); 31 | WORD("thunders"); 32 | WORD("fogeys"); 33 | WORD("probationer"); 34 | WORD("nifty"); 35 | WORD("sunny"); 36 | WORD("prattle"); 37 | WORD("idealistic"); 38 | WORD("tether"); 39 | WORD("stragglers"); 40 | WORD("nocturne"); 41 | WORD("flusters"); 42 | WORD("monotheism"); 43 | WORD("jealously"); 44 | WORD("afghan"); 45 | WORD("dropping"); 46 | WORD("customers"); 47 | WORD("provincial"); 48 | WORD("donut"); 49 | WORD("unintentional"); 50 | WORD("decals"); 51 | WORD("regenerates"); 52 | WORD("vengeful"); 53 | WORD("aping"); 54 | WORD("smoker"); 55 | WORD("racketeer"); 56 | WORD("transfuses"); 57 | WORD("drawls"); 58 | WORD("spins"); 59 | WORD("tight"); 60 | WORD("levee"); 61 | WORD("sidestep"); 62 | WORD("coaxes"); 63 | WORD("nationalization"); 64 | WORD("movables"); 65 | WORD("vibrant"); 66 | WORD("caramel"); 67 | WORD("sympathies"); 68 | WORD("middles"); 69 | WORD("annoy"); 70 | WORD("quit"); 71 | WORD("pizzas"); 72 | WORD("bookworms"); 73 | WORD("bushwhackers"); 74 | WORD("weighs"); 75 | WORD("hourly"); 76 | WORD("typhoid"); 77 | WORD("landslidden"); 78 | WORD("spending"); 79 | WORD("mizzenmast"); 80 | WORD("inundation"); 81 | WORD("frocks"); 82 | WORD("lanes"); 83 | WORD("annexes"); 84 | WORD("shading"); 85 | WORD("cynicism"); 86 | WORD("henchman"); 87 | WORD("edifice"); 88 | WORD("backpack"); 89 | WORD("unexpectedly"); 90 | WORD("cradled"); 91 | WORD("alloying"); 92 | WORD("rattans"); 93 | WORD("reasonably"); 94 | WORD("emancipate"); 95 | WORD("allergens"); 96 | WORD("revivified"); 97 | WORD("chiselled"); 98 | WORD("jitters"); 99 | WORD("latitudinal"); 100 | WORD("frighting"); 101 | WORD("coruscating"); 102 | WORD("cambers"); 103 | WORD("gummiest"); 104 | WORD("pelvic"); 105 | WORD("dimension"); 106 | WORD("contracting"); 107 | WORD("pinhole"); 108 | WORD("nefarious"); 109 | WORD("straitens"); 110 | WORD("hamper"); 111 | WORD("distrust"); 112 | WORD("dozing"); 113 | WORD("reformat"); 114 | WORD("photograph"); 115 | WORD("decreed"); 116 | WORD("steamed"); 117 | WORD("eighth"); 118 | WORD("diplomatic"); 119 | WORD("mushrooms"); 120 | WORD("flees"); 121 | WORD("duplicated"); 122 | WORD("felons"); 123 | WORD("blacksmiths"); 124 | WORD("gems"); 125 | WORD("county"); 126 | WORD("binge"); 127 | WORD("expunges"); 128 | WORD("lactates"); 129 | WORD("tatty"); 130 | WORD("overconfident"); 131 | WORD("indecisive"); 132 | WORD("inheritors"); 133 | WORD("curtseyed"); 134 | WORD("irks"); 135 | WORD("backbit"); 136 | WORD("cubes"); 137 | WORD("interpreters"); 138 | WORD("example"); 139 | WORD("gravels"); 140 | WORD("pagodas"); 141 | WORD("unruffled"); 142 | WORD("jaunty"); 143 | WORD("sleepy"); 144 | WORD("foppish"); 145 | WORD("doff"); 146 | WORD("give"); 147 | WORD("launder"); 148 | WORD("lemons"); 149 | WORD("kiddos"); 150 | WORD("unseemliness"); 151 | WORD("majorities"); 152 | WORD("genera"); 153 | WORD("mains"); 154 | WORD("telecommuting"); 155 | WORD("naivest"); 156 | WORD("achievable"); 157 | WORD("irrigates"); 158 | WORD("spellbinder"); 159 | WORD("chroniclers"); 160 | WORD("entanglement"); 161 | WORD("cyanide"); 162 | WORD("amity"); 163 | WORD("vanities"); 164 | WORD("brisker"); 165 | WORD("tonsils"); 166 | WORD("bluebirds"); 167 | WORD("quaver"); 168 | WORD("devastated"); 169 | WORD("lynching"); 170 | WORD("operational"); 171 | WORD("augurs"); 172 | WORD("industry"); 173 | WORD("veering"); 174 | WORD("stranger"); 175 | WORD("gizmo"); 176 | WORD("floodlighting"); 177 | WORD("scooted"); 178 | WORD("womanhood"); 179 | WORD("windfalls"); 180 | WORD("outsmart"); 181 | WORD("propels"); 182 | WORD("insemination"); 183 | WORD("constantly"); 184 | WORD("judged"); 185 | WORD("bovine"); 186 | WORD("touting"); 187 | WORD("dissolute"); 188 | WORD("esthete"); 189 | WORD("machining"); 190 | WORD("outrageously"); 191 | WORD("obstetrical"); 192 | WORD("religiously"); 193 | WORD("recapitulated"); 194 | WORD("sensible"); 195 | WORD("parsonage"); 196 | WORD("metamorphosing"); 197 | WORD("momma"); 198 | WORD("seaplanes"); 199 | WORD("introduces"); 200 | WORD("sachets"); 201 | WORD("measurable"); 202 | WORD("forsworn"); 203 | WORD("savor"); 204 | WORD("purulent"); 205 | WORD("appetite"); 206 | WORD("undergoing"); 207 | WORD("interrogatory"); 208 | WORD("untouched"); 209 | WORD("unsigned"); 210 | WORD("employers"); 211 | WORD("hedonistic"); 212 | WORD("wimples"); 213 | WORD("betiding"); 214 | WORD("melodiousness"); 215 | WORD("helots"); 216 | WORD("specifically"); 217 | WORD("lawfully"); 218 | WORD("sorters"); 219 | WORD("selected"); 220 | WORD("bespoken"); 221 | WORD("nonentity"); 222 | WORD("embezzlers"); 223 | WORD("alcohol"); 224 | WORD("affects"); 225 | WORD("penknives"); 226 | WORD("musketeers"); 227 | WORD("cockades"); 228 | WORD("ascendents"); 229 | WORD("disinterment"); 230 | WORD("overhearing"); 231 | WORD("propping"); 232 | WORD("ibices"); 233 | WORD("tornados"); 234 | WORD("digitalis"); 235 | WORD("aquas"); 236 | WORD("risers"); 237 | WORD("lighters"); 238 | WORD("mastodons"); 239 | WORD("webcams"); 240 | WORD("mavericks"); 241 | WORD("mahogany"); 242 | WORD("backstopping"); 243 | WORD("wriggled"); 244 | WORD("dolly"); 245 | WORD("talkative"); 246 | WORD("lunatics"); 247 | WORD("hyphens"); 248 | WORD("biologists"); 249 | WORD("burrito"); 250 | WORD("hypersensitivity"); 251 | WORD("vex"); 252 | WORD("ciphering"); 253 | WORD("ferrets"); 254 | WORD("comestible"); 255 | WORD("divisive"); 256 | WORD("cranny"); 257 | WORD("outdated"); 258 | WORD("gawky"); 259 | WORD("interested"); 260 | WORD("puffiness"); 261 | WORD("despatch"); 262 | WORD("trumping"); 263 | WORD("divides"); 264 | WORD("soughing"); 265 | WORD("freelances"); 266 | WORD("rimed"); 267 | WORD("sobriquets"); 268 | WORD("unbeliever"); 269 | WORD("overcrowded"); 270 | WORD("garter"); 271 | WORD("laxer"); 272 | WORD("disembodied"); 273 | WORD("babushka"); 274 | WORD("advertised"); 275 | WORD("uninformed"); 276 | WORD("minibikes"); 277 | WORD("intensity"); 278 | WORD("fashionistas"); 279 | WORD("aphasia"); 280 | WORD("smart"); 281 | WORD("borrowing"); 282 | WORD("director"); 283 | WORD("covenant"); 284 | WORD("minorities"); 285 | WORD("castigating"); 286 | WORD("chip"); 287 | WORD("rarer"); 288 | WORD("tantalizingly"); 289 | WORD("flask"); 290 | WORD("approving"); 291 | WORD("pinups"); 292 | WORD("whiteners"); 293 | WORD("wittily"); 294 | WORD("reaching"); 295 | WORD("tams"); 296 | WORD("digesting"); 297 | WORD("altruistically"); 298 | WORD("recite"); 299 | WORD("ostracizes"); 300 | WORD("improvidence"); 301 | WORD("centipedes"); 302 | WORD("escorting"); 303 | WORD("advertises"); 304 | WORD("rift"); 305 | WORD("janitor"); 306 | WORD("stent"); 307 | WORD("presenter"); 308 | WORD("diurnal"); 309 | WORD("nectar"); 310 | WORD("jockstraps"); 311 | WORD("hissed"); 312 | WORD("overrunning"); 313 | WORD("supersizes"); 314 | WORD("peanut"); 315 | WORD("divider"); 316 | WORD("sulkiest"); 317 | WORD("effervescing"); 318 | WORD("mucks"); 319 | WORD("prejudges"); 320 | WORD("stopgaps"); 321 | WORD("panaceas"); 322 | WORD("read"); 323 | WORD("monstrances"); 324 | WORD("lisle"); 325 | WORD("devastate"); 326 | WORD("loudmouth"); 327 | WORD("martyred"); 328 | WORD("traitor"); 329 | WORD("pasting"); 330 | WORD("fiats"); 331 | WORD("underlie"); 332 | WORD("egotism"); 333 | WORD("collaboration"); 334 | WORD("contemplates"); 335 | WORD("fleece"); 336 | WORD("lethal"); 337 | WORD("coalitions"); 338 | WORD("catnip"); 339 | WORD("garbs"); 340 | WORD("pushover"); 341 | WORD("discrete"); 342 | WORD("heroics"); 343 | WORD("ovoids"); 344 | WORD("set"); 345 | WORD("bosun"); 346 | WORD("mannishness"); 347 | WORD("cutlery"); 348 | WORD("sampled"); 349 | WORD("manpower"); 350 | WORD("sluice"); 351 | WORD("sweepers"); 352 | WORD("traced"); 353 | WORD("pill"); 354 | WORD("whores"); 355 | WORD("ceramic"); 356 | WORD("irrecoverable"); 357 | WORD("repositories"); 358 | WORD("hired"); 359 | WORD("triennial"); 360 | WORD("fattened"); 361 | WORD("anagram"); 362 | WORD("welshed"); 363 | WORD("lacquers"); 364 | WORD("requesting"); 365 | WORD("crackerjacks"); 366 | WORD("reeking"); 367 | WORD("hangars"); 368 | WORD("orgasm"); 369 | WORD("echelon"); 370 | WORD("pearliest"); 371 | WORD("stomachaches"); 372 | WORD("helixes"); 373 | WORD("tapeworm"); 374 | WORD("pirates"); 375 | WORD("mismanagement"); 376 | WORD("citrons"); 377 | WORD("shunts"); 378 | WORD("thoughtfully"); 379 | WORD("moisturized"); 380 | WORD("defaces"); 381 | WORD("closeting"); 382 | WORD("shameful"); 383 | WORD("goblins"); 384 | WORD("choices"); 385 | WORD("bittersweets"); 386 | WORD("construed"); 387 | WORD("shimmers"); 388 | WORD("roster"); 389 | WORD("such"); 390 | WORD("now"); 391 | WORD("glad"); 392 | WORD("flimsier"); 393 | WORD("neither"); 394 | WORD("quota"); 395 | WORD("octane"); 396 | WORD("magnetizing"); 397 | WORD("deviating"); 398 | WORD("slamming"); 399 | WORD("milepost"); 400 | WORD("waterfront"); 401 | WORD("tolerant"); 402 | WORD("capped"); 403 | WORD("upbraiding"); 404 | WORD("renounced"); 405 | WORD("vetoed"); 406 | WORD("creaky"); 407 | WORD("blankest"); 408 | WORD("regimen"); 409 | WORD("demagogs"); 410 | WORD("meddler"); 411 | WORD("uniforms"); 412 | WORD("trundles"); 413 | WORD("slues"); 414 | WORD("goldenest"); 415 | WORD("scarceness"); 416 | WORD("screwed"); 417 | WORD("beggar"); 418 | WORD("monopolies"); 419 | WORD("occurred"); 420 | WORD("directors"); 421 | WORD("restatements"); 422 | WORD("queasy"); 423 | WORD("sparkling"); 424 | WORD("grandstand"); 425 | WORD("quarrelled"); 426 | WORD("evils"); 427 | WORD("cobbled"); 428 | WORD("catapults"); 429 | WORD("sheets"); 430 | WORD("expenditures"); 431 | WORD("allergenic"); 432 | WORD("diamond"); 433 | WORD("inequalities"); 434 | WORD("topped"); 435 | WORD("militarization"); 436 | WORD("twos"); 437 | WORD("remortgages"); 438 | WORD("indigestion"); 439 | WORD("encores"); 440 | WORD("bulletproofed"); 441 | WORD("adeptly"); 442 | WORD("catechised"); 443 | WORD("staggers"); 444 | WORD("initiators"); 445 | WORD("hedonism"); 446 | WORD("shines"); 447 | WORD("scourged"); 448 | WORD("infuriates"); 449 | WORD("abridged"); 450 | WORD("vassals"); 451 | WORD("manicuring"); 452 | WORD("sang"); 453 | WORD("dendrite"); 454 | WORD("permeability"); 455 | WORD("participle"); 456 | WORD("boles"); 457 | WORD("multiplies"); 458 | WORD("dinnered"); 459 | WORD("vigilantly"); 460 | WORD("scatterbrains"); 461 | WORD("typecast"); 462 | WORD("backstretch"); 463 | WORD("expostulates"); 464 | WORD("thoroughly"); 465 | WORD("clappers"); 466 | WORD("convocation"); 467 | WORD("lariat"); 468 | WORD("angled"); 469 | WORD("transmigration"); 470 | WORD("generalissimos"); 471 | WORD("licked"); 472 | WORD("inert"); 473 | WORD("accruals"); 474 | WORD("brig"); 475 | WORD("beakers"); 476 | WORD("landslides"); 477 | WORD("forsythias"); 478 | WORD("recognized"); 479 | WORD("hutzpa"); 480 | WORD("cavort"); 481 | WORD("betterment"); 482 | WORD("maxillae"); 483 | WORD("thunderhead"); 484 | WORD("streetlights"); 485 | WORD("panda"); 486 | WORD("horrifying"); 487 | WORD("predict"); 488 | WORD("cultivates"); 489 | WORD("telegrapher"); 490 | WORD("catchy"); 491 | WORD("alibiing"); 492 | WORD("displacements"); 493 | WORD("comfiest"); 494 | WORD("seducer"); 495 | WORD("convicted"); 496 | WORD("topmost"); 497 | WORD("razed"); 498 | WORD("germinated"); 499 | WORD("disparages"); 500 | WORD("dabbles"); 501 | WORD("spelled"); 502 | WORD("assumes"); 503 | WORD("scratches"); 504 | WORD("prevaricates"); 505 | WORD("mornings"); 506 | WORD("mousses"); 507 | WORD("formatting"); 508 | WORD("theologies"); 509 | WORD("ibises"); 510 | WORD("sonnies"); 511 | WORD("inspectors"); 512 | WORD("teacups"); 513 | WORD("ward"); 514 | WORD("martyring"); 515 | WORD("stepmom"); 516 | WORD("slily"); 517 | WORD("garages"); 518 | WORD("secret"); 519 | WORD("virginity"); 520 | WORD("fuck"); 521 | WORD("bricked"); 522 | WORD("hued"); 523 | WORD("calculation"); 524 | WORD("firebombed"); 525 | WORD("grossest"); 526 | WORD("apace"); 527 | WORD("suavest"); 528 | WORD("towelings"); 529 | WORD("cranking"); 530 | WORD("hordes"); 531 | WORD("oversimplifying"); 532 | WORD("attach"); 533 | WORD("defoliants"); 534 | WORD("varmint"); 535 | WORD("malnutrition"); 536 | WORD("hematology"); 537 | WORD("oscillators"); 538 | WORD("hunts"); 539 | WORD("bugs"); 540 | WORD("filch"); 541 | WORD("appellants"); 542 | WORD("brink"); 543 | WORD("structural"); 544 | WORD("dale"); 545 | WORD("tilled"); 546 | WORD("bucketed"); 547 | WORD("feds"); 548 | WORD("imbecilities"); 549 | WORD("jerk"); 550 | WORD("contumacious"); 551 | WORD("refulgent"); 552 | WORD("transfixing"); 553 | WORD("mappings"); 554 | WORD("occupancy"); 555 | WORD("gluts"); 556 | WORD("nays"); 557 | WORD("hitching"); 558 | WORD("sleeveless"); 559 | WORD("growth"); 560 | WORD("libellous"); 561 | WORD("blackballing"); 562 | WORD("windjammers"); 563 | WORD("snakebites"); 564 | WORD("verbs"); 565 | WORD("properties"); 566 | WORD("hemorrhaging"); 567 | WORD("arbitrariness"); 568 | WORD("crossover"); 569 | WORD("nigh"); 570 | WORD("delves"); 571 | WORD("diner"); 572 | WORD("inflicting"); 573 | WORD("yipping"); 574 | WORD("walruses"); 575 | WORD("zither"); 576 | WORD("sauntered"); 577 | WORD("artworks"); 578 | WORD("troys"); 579 | WORD("girt"); 580 | WORD("boasters"); 581 | WORD("infirmity"); 582 | WORD("nineteen"); 583 | WORD("malfunctions"); 584 | WORD("laundry"); 585 | WORD("belabors"); 586 | WORD("poetesses"); 587 | WORD("flagged"); 588 | WORD("settled"); 589 | WORD("burped"); 590 | WORD("hyperventilates"); 591 | WORD("seasoning"); 592 | WORD("esquire"); 593 | WORD("fledged"); 594 | WORD("participation"); 595 | WORD("brilliant"); 596 | WORD("weddings"); 597 | WORD("nectarines"); 598 | WORD("shaded"); 599 | WORD("themselves"); 600 | WORD("operate"); 601 | WORD("fuelling"); 602 | WORD("delicious"); 603 | WORD("laterals"); 604 | WORD("buckboards"); 605 | WORD("kidded"); 606 | WORD("kneads"); 607 | WORD("rightmost"); 608 | WORD("jurisdiction"); 609 | WORD("acorns"); 610 | WORD("quenches"); 611 | WORD("insults"); 612 | WORD("unstructured"); 613 | WORD("breakfasting"); 614 | WORD("guesstimates"); 615 | WORD("filleting"); 616 | WORD("lasagnas"); 617 | WORD("perjure"); 618 | WORD("shinnied"); 619 | WORD("strangulation"); 620 | WORD("snoot"); 621 | WORD("attunes"); 622 | WORD("colonialism"); 623 | WORD("frugal"); 624 | WORD("punts"); 625 | WORD("stampedes"); 626 | WORD("downgrade"); 627 | WORD("subsisting"); 628 | WORD("scuppers"); 629 | WORD("cigarillos"); 630 | WORD("pantsuit"); 631 | WORD("binging"); 632 | WORD("grouts"); 633 | WORD("recenter"); 634 | WORD("finals"); 635 | WORD("fairgrounds"); 636 | WORD("snorers"); 637 | WORD("valences"); 638 | WORD("shinbone"); 639 | WORD("gearwheels"); 640 | WORD("interdicted"); 641 | WORD("legislatures"); 642 | WORD("girdled"); 643 | WORD("dungeons"); 644 | WORD("disciplining"); 645 | WORD("vaporization"); 646 | WORD("toothless"); 647 | WORD("shimmying"); 648 | WORD("brazens"); 649 | WORD("extirpate"); 650 | WORD("nullity"); 651 | WORD("dressier"); 652 | WORD("rectangular"); 653 | WORD("knotty"); 654 | WORD("category"); 655 | WORD("coevals"); 656 | WORD("meek"); 657 | WORD("regulates"); 658 | WORD("retardants"); 659 | WORD("quadrants"); 660 | WORD("juleps"); 661 | WORD("scurrilously"); 662 | WORD("thespians"); 663 | WORD("obediently"); 664 | WORD("repayments"); 665 | WORD("delegates"); 666 | WORD("rerunning"); 667 | WORD("retaliates"); 668 | WORD("flaw"); 669 | WORD("pities"); 670 | WORD("bibliographers"); 671 | WORD("lawns"); 672 | WORD("murkiness"); 673 | WORD("worrywart"); 674 | WORD("ornaments"); 675 | WORD("sponsors"); 676 | WORD("spigot"); 677 | WORD("presences"); 678 | WORD("vulgarizing"); 679 | WORD("whiskered"); 680 | WORD("kilned"); 681 | WORD("breakups"); 682 | WORD("befitted"); 683 | WORD("pockmarked"); 684 | WORD("moonlighting"); 685 | WORD("electrical"); 686 | WORD("speedometer"); 687 | WORD("island"); 688 | WORD("eminence"); 689 | WORD("yeast"); 690 | WORD("particle"); 691 | WORD("theological"); 692 | WORD("ceding"); 693 | WORD("colonnade"); 694 | WORD("slurs"); 695 | WORD("sunfish"); 696 | WORD("eh"); 697 | WORD("railroads"); 698 | WORD("strayed"); 699 | WORD("sax"); 700 | WORD("idiot"); 701 | WORD("tireless"); 702 | WORD("bushier"); 703 | WORD("refugee"); 704 | WORD("winks"); 705 | WORD("dimming"); 706 | WORD("bunks"); 707 | WORD("agenda"); 708 | WORD("paisleys"); 709 | WORD("pontifical"); 710 | WORD("cloaks"); 711 | WORD("stomped"); 712 | WORD("slated"); 713 | WORD("priestesses"); 714 | WORD("semester"); 715 | WORD("sedately"); 716 | WORD("barbarian"); 717 | WORD("riddle"); 718 | WORD("whitener"); 719 | WORD("pushier"); 720 | WORD("engorge"); 721 | WORD("congregational"); 722 | WORD("scoutmasters"); 723 | WORD("mirth"); 724 | WORD("piccalilli"); 725 | WORD("sodden"); 726 | WORD("brokenhearted"); 727 | WORD("palmier"); 728 | WORD("fingerprinting"); 729 | WORD("quislings"); 730 | WORD("caparisoning"); 731 | WORD("recognizably"); 732 | WORD("goslings"); 733 | WORD("cuddled"); 734 | WORD("agitates"); 735 | WORD("jimmied"); 736 | WORD("slumbering"); 737 | WORD("bounders"); 738 | WORD("barnstorms"); 739 | WORD("piloting"); 740 | WORD("enclave"); 741 | WORD("commercials"); 742 | WORD("sharia"); 743 | WORD("autistic"); 744 | WORD("truer"); 745 | WORD("tidily"); 746 | WORD("consciously"); 747 | WORD("barred"); 748 | WORD("temblor"); 749 | WORD("camphor"); 750 | WORD("abacuses"); 751 | WORD("runny"); 752 | WORD("papyrus"); 753 | WORD("associates"); 754 | WORD("perspectives"); 755 | WORD("sheltering"); 756 | WORD("countersigned"); 757 | WORD("managerial"); 758 | WORD("quarry"); 759 | WORD("thoracic"); 760 | WORD("massacred"); 761 | WORD("impenetrable"); 762 | WORD("encodes"); 763 | WORD("halyards"); 764 | WORD("lancing"); 765 | WORD("gal"); 766 | WORD("meatloaves"); 767 | WORD("maverick"); 768 | WORD("proselytizes"); 769 | WORD("preliminary"); 770 | WORD("repulsing"); 771 | WORD("packaged"); 772 | WORD("patriarchal"); 773 | WORD("pretty"); 774 | WORD("ineffable"); 775 | WORD("embalm"); 776 | WORD("precipice"); 777 | WORD("shadow"); 778 | WORD("love"); 779 | WORD("lionizing"); 780 | WORD("bedroll"); 781 | WORD("crookedness"); 782 | WORD("pinked"); 783 | WORD("enlistments"); 784 | WORD("trapezoidal"); 785 | WORD("placebo"); 786 | WORD("directly"); 787 | WORD("salsas"); 788 | WORD("proffers"); 789 | WORD("brandishing"); 790 | WORD("podiatrists"); 791 | WORD("daintiest"); 792 | WORD("recompense"); 793 | WORD("porcupines"); 794 | WORD("also"); 795 | WORD("trellising"); 796 | WORD("plotting"); 797 | WORD("inaccuracy"); 798 | WORD("bitterer"); 799 | WORD("soap"); 800 | WORD("hypothalamus"); 801 | WORD("lexicography"); 802 | WORD("ninnies"); 803 | WORD("afternoon"); 804 | WORD("eats"); 805 | WORD("watercolors"); 806 | WORD("membership"); 807 | WORD("protectorates"); 808 | WORD("impassivity"); 809 | WORD("wondrous"); 810 | WORD("eyeballed"); 811 | WORD("gigabyte"); 812 | WORD("gentlest"); 813 | WORD("confirming"); 814 | WORD("antagonism"); 815 | WORD("broadcast"); 816 | WORD("sage"); 817 | WORD("joker"); 818 | WORD("oppressors"); 819 | WORD("interluding"); 820 | WORD("fuze"); 821 | WORD("launched"); 822 | WORD("releasing"); 823 | WORD("reestablishing"); 824 | WORD("bigwigs"); 825 | WORD("rheumatism"); 826 | WORD("laborious"); 827 | WORD("tactically"); 828 | WORD("striping"); 829 | WORD("eateries"); 830 | WORD("fame"); 831 | WORD("chauvinistic"); 832 | WORD("sitter"); 833 | WORD("camisole"); 834 | WORD("conserving"); 835 | WORD("champagne"); 836 | WORD("abbeys"); 837 | WORD("epitomizing"); 838 | WORD("goulash"); 839 | WORD("goatee"); 840 | WORD("pained"); 841 | WORD("hiker"); 842 | WORD("pygmy"); 843 | WORD("ware"); 844 | WORD("polyhedron"); 845 | WORD("carouse"); 846 | WORD("gazetting"); 847 | WORD("coconuts"); 848 | WORD("dill"); 849 | WORD("insensitively"); 850 | WORD("vaulters"); 851 | WORD("osiers"); 852 | WORD("unmanning"); 853 | WORD("worksheets"); 854 | WORD("lavender"); 855 | WORD("manhood"); 856 | WORD("paraprofessionals"); 857 | WORD("bowdlerized"); 858 | WORD("dumfounding"); 859 | WORD("waistline"); 860 | WORD("rises"); 861 | WORD("stickiness"); 862 | WORD("envelope"); 863 | WORD("unspoken"); 864 | WORD("wowed"); 865 | WORD("analyticalally"); 866 | WORD("ulna"); 867 | WORD("gist"); 868 | WORD("jihads"); 869 | WORD("stoker"); 870 | WORD("nay"); 871 | WORD("fogy"); 872 | WORD("poled"); 873 | WORD("evicted"); 874 | WORD("goriest"); 875 | WORD("buried"); 876 | WORD("glades"); 877 | WORD("comprehensive"); 878 | WORD("reenters"); 879 | WORD("caricaturing"); 880 | WORD("dieting"); 881 | WORD("diagnostic"); 882 | WORD("replenishing"); 883 | WORD("daydreamer"); 884 | WORD("mutter"); 885 | WORD("massage"); 886 | WORD("alleluia"); 887 | WORD("misrepresents"); 888 | WORD("redefined"); 889 | WORD("deprogramed"); 890 | WORD("raconteurs"); 891 | WORD("cunningest"); 892 | WORD("placate"); 893 | WORD("peach"); 894 | WORD("cosmetic"); 895 | WORD("grower"); 896 | WORD("rheumatic"); 897 | WORD("aficionados"); 898 | WORD("hookworm"); 899 | WORD("ovulates"); 900 | WORD("freelancers"); 901 | WORD("midsummer"); 902 | WORD("admired"); 903 | WORD("gargoyles"); 904 | WORD("tobaccos"); 905 | WORD("plannings"); 906 | WORD("press"); 907 | WORD("muggers"); 908 | WORD("fortress"); 909 | WORD("reappoints"); 910 | WORD("scruples"); 911 | WORD("gnomes"); 912 | WORD("apocalyptic"); 913 | WORD("moderates"); 914 | WORD("halving"); 915 | WORD("weathering"); 916 | WORD("blowouts"); 917 | WORD("distinction"); 918 | WORD("dumping"); 919 | WORD("poultry"); 920 | WORD("therapists"); 921 | WORD("maximum"); 922 | WORD("defers"); 923 | WORD("lankier"); 924 | WORD("approached"); 925 | WORD("winked"); 926 | WORD("construing"); 927 | WORD("paradoxical"); 928 | WORD("orthogonality"); 929 | WORD("localizing"); 930 | WORD("fleecing"); 931 | WORD("delimit"); 932 | WORD("footlights"); 933 | WORD("blessedly"); 934 | WORD("interrogated"); 935 | WORD("fantasy"); 936 | WORD("foxtrots"); 937 | WORD("hit"); 938 | WORD("malaise"); 939 | WORD("invisibility"); 940 | WORD("correcter"); 941 | WORD("prolongs"); 942 | WORD("supporters"); 943 | WORD("hubbub"); 944 | WORD("audible"); 945 | WORD("scrutiny"); 946 | WORD("smells"); 947 | WORD("detaching"); 948 | WORD("castings"); 949 | WORD("nominatives"); 950 | WORD("solemnly"); 951 | WORD("babysitters"); 952 | WORD("morbid"); 953 | WORD("invariable"); 954 | WORD("kinswoman"); 955 | WORD("moles"); 956 | WORD("cowardice"); 957 | WORD("prawning"); 958 | WORD("falloff"); 959 | WORD("bulimia"); 960 | WORD("aptitude"); 961 | WORD("diploma"); 962 | WORD("scholars"); 963 | WORD("broadens"); 964 | WORD("persecutes"); 965 | WORD("retrospection"); 966 | WORD("palms"); 967 | WORD("daughter"); 968 | WORD("misgiving"); 969 | WORD("variations"); 970 | WORD("underclassmen"); 971 | WORD("coronae"); 972 | WORD("rivets"); 973 | WORD("bipartisan"); 974 | WORD("blow"); 975 | WORD("repairing"); 976 | WORD("dourer"); 977 | WORD("scants"); 978 | WORD("moms"); 979 | WORD("sloshed"); 980 | WORD("equalizer"); 981 | WORD("caddied"); 982 | WORD("flattery"); 983 | WORD("meritorious"); 984 | WORD("propagates"); 985 | WORD("unsympathetic"); 986 | WORD("casuals"); 987 | WORD("gobbing"); 988 | WORD("aground"); 989 | WORD("excavator"); 990 | WORD("possessiveness"); 991 | WORD("cracked"); 992 | WORD("canneries"); 993 | WORD("gambles"); 994 | WORD("nightclubbed"); 995 | WORD("catchwords"); 996 | WORD("quashing"); 997 | WORD("summons"); 998 | WORD("fizzing"); 999 | WORD("noisiness"); 1000 | WORD("shapeliness"); 1001 | WORD("ligatures"); 1002 | WORD("aphrodisiac"); 1003 | WORD("quicker"); 1004 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Switch/Case strings using constexpr Fnv1-a. 3 | * @maintainer xavier dot roche at algolia.com 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "switch_fnv1a.h" 17 | 18 | #define L_(L) #L 19 | #define L(L) L_(L) 20 | 21 | // Execute benchmarks ? 22 | #define ENABLE_BENCHS 23 | 24 | // Small (100 cases) benchs ? 25 | // #define SMALL_BENCHS 26 | 27 | // Do not print output for benchs ? 28 | // #define NOPRINT_BENCHS 29 | 30 | // Sample case 31 | const char* dispatch(const fnv1a128::Type match) 32 | { 33 | switch (match) { 34 | case "poney"_fnv1a128: 35 | return "I want one, too!"; 36 | case "elephant"_fnv1a128: 37 | return "Not in my apartment please!"; 38 | case "dog"_fnv1a128: 39 | return "Good puppy!"; 40 | case "kitten"_fnv1a128: 41 | return "Aawwwwwwww!"; 42 | default: 43 | return "Don't know this animal!"; 44 | } 45 | } 46 | 47 | // Long switch of 1000 'case 48 | constexpr const char* dispatch_1000(const fnv1a128::Type match) 49 | { 50 | switch (match) { 51 | #define WORD(W) \ 52 | case W##_fnv1a128: \ 53 | return L(__LINE__) 54 | #ifndef SMALL_BENCHS 55 | #include "include/words-extract.h" 56 | #else 57 | #include "include/words-extract-small.h" 58 | #endif 59 | #undef WORD 60 | 61 | default: 62 | return "unknown!"; 63 | } 64 | } 65 | 66 | // Hash-switch version 67 | auto compute_hash(const char *str, size_t size) 68 | { 69 | return fnv1a128::hash(str, size); 70 | } 71 | 72 | // Hash-switch version 73 | const char* match_case(const std::string& str) 74 | { 75 | return dispatch_1000(compute_hash(str.c_str(), str.size())); 76 | } 77 | 78 | // Pure-if version 79 | const char* match_if(const std::string& str) 80 | { 81 | if (str.size() == 0) 82 | return ""; 83 | #define WORD(W) else if (str == W) return L(__LINE__) 84 | #ifndef SMALL_BENCHS 85 | #include "include/words-extract.h" 86 | #else 87 | #include "include/words-extract-small.h" 88 | #endif 89 | #undef WORD 90 | return "unknown!"; 91 | } 92 | 93 | static void bench(void) 94 | { 95 | { 96 | // Small benchmarks 97 | constexpr size_t max = 100000; 98 | #ifdef NOPRINT_BENCHS 99 | volatile size_t calc = 1; 100 | #endif 101 | 102 | std::vector strings; 103 | #define WORD(W) strings.push_back(W) 104 | #include "include/words-extract-match.h" 105 | #undef WORD 106 | 107 | unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); 108 | std::shuffle(strings.begin(), strings.end(), std::default_random_engine(seed)); 109 | 110 | uint64_t elapsed_if; 111 | uint64_t elapsed_case; 112 | 113 | // Naive if 114 | { 115 | auto start = std::chrono::steady_clock::now(); 116 | for (size_t i = 0; i < max; i++) { 117 | for (const auto& string : strings) { 118 | const char* const match = match_if(string.c_str()); 119 | #ifndef NOPRINT_BENCHS 120 | std::cout << match << "\n"; 121 | #else 122 | calc *= match[0]; 123 | #endif 124 | } 125 | } 126 | auto end = std::chrono::steady_clock::now(); 127 | auto elapsed = std::chrono::duration_cast(end - start).count(); 128 | elapsed_if = elapsed; 129 | std::cerr << elapsed << "ms\n"; 130 | } 131 | 132 | // Hashed switch 133 | { 134 | auto start = std::chrono::steady_clock::now(); 135 | for (size_t i = 0; i < max; i++) { 136 | for (const auto& string : strings) { 137 | const char* const match = match_case(string.c_str()); 138 | #ifndef NOPRINT_BENCHS 139 | std::cout << match << "\n"; 140 | #else 141 | calc *= match[0]; 142 | #endif 143 | } 144 | } 145 | auto end = std::chrono::steady_clock::now(); 146 | auto elapsed = std::chrono::duration_cast(end - start).count(); 147 | elapsed_case = elapsed; 148 | std::cerr << elapsed << "ms\n"; 149 | } 150 | 151 | const auto factor = (elapsed_if * 100) / elapsed_case; 152 | std::cerr << "Factor: " << (factor / 100) << "." << (factor % 100) << "\n"; 153 | } 154 | } 155 | 156 | int main(int argc, char** argv) 157 | { 158 | // Execute benchmarks ? 159 | #ifdef ENABLE_BENCHS 160 | if (argc == 1) 161 | bench(); 162 | #endif 163 | 164 | // Demo 165 | for (int i = 1; i < argc; i++) { 166 | const auto hash = fnv1a128::hash(argv[i]); 167 | 168 | std::cout << dispatch(hash) << "\n"; 169 | std::cout << "Hash value:" << std::hex << ((uint64_t)(hash >> 64)) << ((uint64_t)(hash)) << "\n"; 170 | } 171 | 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /switch_fnv1a.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Switch/Case strings using constexpr Fnv1-a. 3 | * @comment References: 4 | * @comment References: 5 | * @maintainer xavier dot roche at algolia.com 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // Traits for FNV1a 16 | template 17 | struct fnv1a_traits 18 | { 19 | static constexpr bool Supported = false; 20 | }; 21 | 22 | // Traits for 32-bit FNV1a 23 | template<> 24 | struct fnv1a_traits<32> 25 | { 26 | static constexpr bool Supported = true; 27 | using Type = uint32_t; 28 | 29 | static constexpr Type Prime = 0x1000193; 30 | static constexpr Type Offset = 0x811c9dc5; 31 | }; 32 | 33 | // Traits for 64-bit FNV1a 34 | template<> 35 | struct fnv1a_traits<64> 36 | { 37 | static constexpr bool Supported = true; 38 | using Type = uint64_t; 39 | 40 | static constexpr Type Prime = 0x100000001b3; 41 | static constexpr Type Offset = 0xcbf29ce484222325; 42 | }; 43 | 44 | static constexpr __uint128_t Pack128(uint64_t high, uint64_t low) 45 | { 46 | return ((__uint128_t)high << 64) + (__uint128_t)low; 47 | } 48 | 49 | // Traits for 128-bit FNV1a 50 | template<> 51 | struct fnv1a_traits<128> 52 | { 53 | static constexpr bool Supported = true; 54 | using Type = __uint128_t; 55 | 56 | static constexpr Type Prime = Pack128(0x1000000, 0x000000000000013b); 57 | static constexpr Type Offset = Pack128(0x6c62272e07bb0142, 0x62b821756295c58d); 58 | }; 59 | 60 | // Generic FNV1a implementation 61 | template 62 | struct fnv1a 63 | { 64 | static_assert(fnv1a_traits::Supported); 65 | using Type = typename fnv1a_traits::Type; 66 | 67 | /** 68 | * Compute the Fowler–Noll–Vo hash 69 | * @comment stop An optional stop character 70 | * @param s The string 71 | * @param l The string size 72 | * @return The fnv-1a hash 73 | */ 74 | template 75 | static constexpr Type hash_container(T s, 76 | const std::size_t l, 77 | L stopLen = nullptr, 78 | Type hash = fnv1a_traits::Offset) 79 | { 80 | // See 81 | std::size_t j = 0; 82 | for (; j < l; j++) { 83 | const uint8_t byte = s[j]; 84 | if constexpr (stop != 0) { 85 | if (byte == stop) { 86 | if constexpr (!std::is_same::value) { 87 | *stopLen = j + 1; 88 | } 89 | break; 90 | } 91 | } 92 | hash ^= byte; 93 | hash *= fnv1a_traits::Prime; 94 | } 95 | 96 | return hash; 97 | } 98 | 99 | /** 100 | * Compute the Fowler–Noll–Vo hash 101 | * @comment stop An optional stop character 102 | * @param s The string 103 | * @param l The string size 104 | * @return The fnv-1a hash 105 | */ 106 | template 107 | static constexpr Type hash(const C* s, 108 | const std::size_t l, 109 | L stopLen = nullptr, 110 | Type hash = fnv1a_traits::Offset) 111 | { 112 | // Accept [ unsigned | signed ] char 113 | static_assert(std::is_integral::value); 114 | static_assert(sizeof(C) == 1); 115 | return hash_container(s, l, stopLen, hash); 116 | } 117 | 118 | /** 119 | * Compute the Fowler–Noll–Vo hash 120 | * @param s The string 121 | * @return The fnv-1a hash 122 | */ 123 | template 124 | static constexpr Type hash(const char (&s)[l]) 125 | { 126 | return hash(&s[0], l - 1); 127 | } 128 | 129 | // Do not infer length for char arrays 130 | template 131 | static constexpr Type hash(char (&s)[l]) 132 | { 133 | return hash(&s[0]); 134 | } 135 | 136 | /** 137 | * Compute the Fowler–Noll–Vo hash 138 | * @param s The string 139 | * @return The fnv-1a hash 140 | */ 141 | static constexpr Type hash(const char* s) { return hash(s, __builtin_strlen(s)); } 142 | 143 | /** 144 | * Compute the Fowler–Noll–Vo hash 145 | * @param str The string 146 | * @return The fnv-1a hash 147 | */ 148 | static constexpr Type hash(const std::string& str) { return hash(str.c_str(), str.size()); } 149 | 150 | /** 151 | * Compute the Fowler–Noll–Vo hash 152 | * @param str The string view 153 | * @return The fnv-1a hash 154 | */ 155 | template 156 | static constexpr Type hash(const std::basic_string_view& str) 157 | { 158 | // Accept [ unsigned | signed ] char 159 | static_assert(std::is_integral::value); 160 | static_assert(sizeof(C) == 1); 161 | return hash(str.data(), str.size()); 162 | } 163 | }; 164 | 165 | using fnv1a32 = fnv1a<32>; 166 | using fnv1a64 = fnv1a<64>; 167 | using fnv1a128 = fnv1a<128>; 168 | 169 | constexpr fnv1a32::Type operator"" _fnv1a32(const char* s, const std::size_t l) 170 | { 171 | return fnv1a32::hash(s, l); 172 | } 173 | constexpr fnv1a64::Type operator"" _fnv1a64(const char* s, const std::size_t l) 174 | { 175 | return fnv1a64::hash(s, l); 176 | } 177 | constexpr fnv1a128::Type operator"" _fnv1a128(const char* s, const std::size_t l) 178 | { 179 | return fnv1a128::hash(s, l); 180 | } 181 | 182 | // Static unit tests: 183 | static_assert("hello"_fnv1a32 == 0x4f9f2cab); 184 | static_assert("hello"_fnv1a64 == 0xa430d84680aabd0b); 185 | static_assert("hello"_fnv1a128 == Pack128(0xe3e1efd54283d94f, 0x7081314b599d31b3)); 186 | 187 | using strhash = fnv1a128; 188 | constexpr strhash::Type operator"" _strhash(const char* s, const std::size_t l) 189 | { 190 | return strhash::hash(s, l); 191 | } 192 | 193 | // Lowercase operator[] wrapper for any operator[]-aware types 194 | namespace strhash_lower { 195 | template 196 | class _lowercase_container 197 | { 198 | public: 199 | constexpr _lowercase_container(const T& container) 200 | : _container(container) 201 | {} 202 | 203 | constexpr auto operator[](std::size_t index) const 204 | { 205 | const auto c = _container[index]; 206 | static_assert(std::is_integral::value); 207 | static_assert(sizeof(c) == 1); 208 | return c >= 'A' && c <= 'Z' ? (c + 'a' - 'A') : c; 209 | } 210 | 211 | private: 212 | const T& _container; 213 | }; 214 | 215 | /** 216 | * Hash a generic container, using a lowercase modifier, providing 'size' 8-byte characters through operator[] 217 | */ 218 | template 219 | constexpr strhash::Type hash(const T& container, std::size_t size) 220 | { 221 | return strhash::hash_container(strhash_lower::_lowercase_container(container), size); 222 | } 223 | 224 | /** 225 | * Hash a std::string, using a lowercase modifier 226 | */ 227 | static constexpr strhash::Type hash(const std::string& str) 228 | { 229 | return hash(str.c_str(), str.size()); 230 | } 231 | 232 | /** 233 | * Hash a std::string, using a lowercase modifier 234 | */ 235 | template 236 | static constexpr strhash::Type hash(const std::basic_string_view& str) 237 | { 238 | // Accept [ unsigned | signed ] char 239 | static_assert(std::is_integral::value); 240 | static_assert(sizeof(C) == 1); 241 | return hash(str.data(), str.size()); 242 | } 243 | } // namespace strhash_lower 244 | 245 | // Case-insensitive version 246 | constexpr strhash::Type operator"" _strhash_lower(const char* s, const std::size_t l) 247 | { 248 | return strhash_lower::hash(s, l); 249 | } 250 | --------------------------------------------------------------------------------