A collection of basic/generally desirable code I use across multiple C++ projects.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

816 lines
26 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. #pragma once
  2. #ifndef ULE_TABLE_H
  3. #define ULE_TABLE_H
  4. #include <new> // new
  5. #include <functional> // std::function for traversal
  6. #include <type_traits> // std::enable_if
  7. #include "config.h"
  8. #include "alloc.h"
  9. #include "string.h"
  10. #include "types.h"
  11. // what follows is a collection of hash functions taken from: https://www.partow.net/programming/hashfunctions/#:~:text=The%20hash%20functions%20in%20this,containers%20such%20as%20hash%2Dtables.
  12. //
  13. // Available Hash Functions
  14. // The General Hash Functions Library has the following mix of additive and rotative general purpose string hashing algorithms. The following algorithms vary in usefulness and functionality and are mainly intended as an example for learning how hash functions operate and what they basically look like in code form.
  15. //
  16. // 00 - RS Hash Function
  17. // A simple hash function from Robert Sedgwicks Algorithms in C book. I've added some simple optimizations to the algorithm in order to speed up its hashing process.
  18. #if 1
  19. static unsigned int RSHash(const char* str, unsigned int length)
  20. {
  21. unsigned int b = 378551;
  22. unsigned int a = 63689;
  23. unsigned int hash = 0;
  24. unsigned int i = 0;
  25. for (i = 0; i < length; ++str, ++i)
  26. {
  27. hash = hash * a + (*str);
  28. a = a * b;
  29. }
  30. return hash;
  31. }
  32. #endif
  33. // 01 - JS Hash Function
  34. // A bitwise hash function written by Justin Sobel
  35. #if 1
  36. static unsigned int JSHash(const char* str, unsigned int length)
  37. {
  38. unsigned int hash = 1315423911;
  39. unsigned int i = 0;
  40. for (i = 0; i < length; ++str, ++i)
  41. {
  42. hash ^= ((hash << 5) + (*str) + (hash >> 2));
  43. }
  44. return hash;
  45. }
  46. #endif
  47. // 02 - PJW Hash Function
  48. // This hash algorithm is based on work by Peter J. Weinberger of Renaissance Technologies. The book Compilers (Principles, Techniques and Tools) by Aho, Sethi and Ulman, recommends the use of hash functions that employ the hashing methodology found in this particular algorithm.
  49. #if 1
  50. static unsigned int PJWHash(const char* str, unsigned int length)
  51. {
  52. const unsigned int BitsInUnsignedInt = (unsigned int)(sizeof(unsigned int) * 8);
  53. const unsigned int ThreeQuarters = (unsigned int)((BitsInUnsignedInt * 3) / 4);
  54. const unsigned int OneEighth = (unsigned int)(BitsInUnsignedInt / 8);
  55. const unsigned int HighBits =
  56. (unsigned int)(0xFFFFFFFF) << (BitsInUnsignedInt - OneEighth);
  57. unsigned int hash = 0;
  58. unsigned int test = 0;
  59. unsigned int i = 0;
  60. for (i = 0; i < length; ++str, ++i)
  61. {
  62. hash = (hash << OneEighth) + (*str);
  63. if ((test = hash & HighBits) != 0)
  64. {
  65. hash = (( hash ^ (test >> ThreeQuarters)) & (~HighBits));
  66. }
  67. }
  68. return hash;
  69. }
  70. #endif
  71. // 03 - ELF Hash Function
  72. // Similar to the PJW Hash function, but tweaked for 32-bit processors. It is a widley used hash function on UNIX based systems.
  73. #if 1
  74. static unsigned int ELFHash(const char* str, unsigned int length)
  75. {
  76. unsigned int hash = 0;
  77. unsigned int x = 0;
  78. unsigned int i = 0;
  79. for (i = 0; i < length; ++str, ++i)
  80. {
  81. hash = (hash << 4) + (*str);
  82. if ((x = hash & 0xF0000000L) != 0)
  83. {
  84. hash ^= (x >> 24);
  85. }
  86. hash &= ~x;
  87. }
  88. return hash;
  89. }
  90. #endif
  91. // 04 - BKDR Hash Function
  92. // This hash function comes from Brian Kernighan and Dennis Ritchie's book "The C Programming Language". It is a simple hash function using a strange set of possible seeds which all constitute a pattern of 31....31...31 etc, it seems to be very similar to the DJB hash function.
  93. //
  94. // @NOTE (nick) - this is basically the one I was using before I found this resource with all these other hash functions. (I took it from K&R)
  95. // except, the seed in the version I used was '31', not '131'.
  96. #if 1
  97. static unsigned int BKDRHash(const char* str, unsigned int length)
  98. {
  99. unsigned int seed = 131; /* 31 131 1313 13131 131313 etc.. */
  100. unsigned int hash = 0;
  101. unsigned int i = 0;
  102. for (i = 0; i < length; ++str, ++i)
  103. {
  104. hash = (hash * seed) + (*str);
  105. }
  106. return hash;
  107. }
  108. #endif
  109. // 05 - SDBM Hash Function
  110. // This is the algorithm of choice which is used in the open source SDBM project. The hash function seems to have a good over-all distribution for many different data sets. It seems to work well in situations where there is a high variance in the MSBs of the elements in a data set.
  111. #if 1
  112. static unsigned int SDBMHash(const char* str, unsigned int length)
  113. {
  114. unsigned int hash = 0;
  115. unsigned int i = 0;
  116. for (i = 0; i < length; ++str, ++i)
  117. {
  118. hash = (*str) + (hash << 6) + (hash << 16) - hash;
  119. }
  120. return hash;
  121. }
  122. #endif
  123. // 06 - DJB Hash Function
  124. // An algorithm produced by Professor Daniel J. Bernstein and shown first to the world on the usenet newsgroup comp.lang.c. It is one of the most efficient hash functions ever published.
  125. #if 1
  126. static unsigned int DJBHash(const char* str, unsigned int length)
  127. {
  128. unsigned int hash = 5381;
  129. unsigned int i = 0;
  130. for (i = 0; i < length; ++str, ++i)
  131. {
  132. hash = ((hash << 5) + hash) + (*str);
  133. }
  134. return hash;
  135. }
  136. #endif
  137. // 07 - DEK Hash Function
  138. // An algorithm proposed by Donald E. Knuth in The Art Of Computer Programming Volume 3, under the topic of sorting and search chapter 6.4.
  139. #if 1
  140. static unsigned int DEKHash(const char* str, unsigned int length)
  141. {
  142. unsigned int hash = length;
  143. unsigned int i = 0;
  144. for (i = 0; i < length; ++str, ++i)
  145. {
  146. hash = ((hash << 5) ^ (hash >> 27)) ^ (*str);
  147. }
  148. return hash;
  149. }
  150. #endif
  151. // 08 - AP Hash Functionhing The Project Gutenberg Etext of Webster's Unabridged Dictionary, the longest encountered chain length was 7, the average chain length was 2, the number of empty buckets was 4579.
  152. // An algorithm produced by me Arash Partow. I took ideas from all of the above hash functions making a hybrid rotative and additive hash function algorithm. There isn't any real mathematical analysis explaining why one should use this hash function instead of the others described above other than the fact that I tired to resemble the design as close as possible to a simple LFSR. An empirical result which demonstrated the distributive abilities of the hash algorithm was obtained using a hash-table with 100003 buckets, hashing The Project Gutenberg Etext of Webster's Unabridged Dictionary, the longest encountered chain length was 7, the average chain length was 2, the number of empty buckets was 4579.
  153. // @NOTE(nick) - given the test case above of the whole english dictionary, I think this one makes sense for a general purpose, unknown-length key hash table.
  154. #if 1
  155. static unsigned int APHash(const char* str, unsigned int length)
  156. {
  157. unsigned int hash = 0xAAAAAAAA;
  158. unsigned int i = 0;
  159. for (i = 0; i < length; ++str, ++i)
  160. {
  161. hash ^= ((i & 1) == 0) ? ( (hash << 7) ^ (*str) * (hash >> 3)) :
  162. (~((hash << 11) + ((*str) ^ (hash >> 5))));
  163. }
  164. return hash;
  165. }
  166. #endif
  167. // this is a trick to perform modulus of some u32 |v|, by another u32 |c|, while avoiding a division instruction.
  168. // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
  169. static inline u32 fastModuloReductionDanielLemire(u32 v, u32 c) {
  170. return (u32)(((u64)v * (u64)c) >> 32);
  171. }
  172. static inline u32 hash(const char* key, u32 keyLength, u32 capacity) {
  173. ULE_TYPES_H_FTAG;
  174. u32 value = APHash(key, keyLength);
  175. return fastModuloReductionDanielLemire(value, capacity);
  176. }
  177. template <typename V>
  178. struct TableEntry {
  179. TableEntry<V>* next;
  180. const char* key;
  181. u32 keyLength;
  182. V value;
  183. };
  184. template <typename T>
  185. static void serialize(String* str, TableEntry<T>* entry) {
  186. serialize(str, entry->key);
  187. serialize(str, entry->keyLength);
  188. serialize(str, entry->value);
  189. }
  190. template <typename V>
  191. struct Table {
  192. u32 lanes;
  193. u32 length;
  194. TableEntry<V>** entries;
  195. Table<V>(u32 _lanes = 16) {
  196. ULE_TYPES_H_FTAG;
  197. this->lanes = _lanes;
  198. this->length = 0;
  199. this->entries = (TableEntry<V>**) pCalloc(sizeof(TableEntry<V>*), this->lanes);
  200. }
  201. void* operator new(size_t size) {
  202. ULE_TYPES_H_FTAG;
  203. return (Table<V>*) pMalloc(sizeof(Table<V>));
  204. }
  205. float LoadFactor() {
  206. ULE_TYPES_H_FTAG;
  207. return ((float)this->length)/((float)this->lanes);
  208. }
  209. V insert(const char* key, u32 keyLength, V value) {
  210. ULE_TYPES_H_FTAG;
  211. TableEntry<V>* entry = this->lookup(key, keyLength);
  212. if (!entry) { // no entry with that key exists
  213. entry = (TableEntry<V>*) pMalloc(sizeof (TableEntry<V>));
  214. entry->key = String::cpy(key, keyLength);
  215. entry->keyLength = keyLength;
  216. entry->value = value;
  217. u32 hashValue = hash(key, keyLength, lanes);
  218. entry->next = entries[hashValue];
  219. entries[hashValue] = entry;
  220. this->length++;
  221. return (V) 0;
  222. } else { // entry already exists, replace its value
  223. // pFree(entry->value); // @NOTE how to cleanup if overwriting an owned pointer?
  224. V oldValue = entry->value;
  225. entry->value = value;
  226. return oldValue;
  227. }
  228. }
  229. TableEntry<V>* lookup(const char* key, u32 keyLength) {
  230. ULE_TYPES_H_FTAG;
  231. TableEntry<V>* entry = this->entries[hash(key, keyLength, lanes)];
  232. for (; entry != null; entry = entry->next) {
  233. if (String::memeq((unsigned char*)key, keyLength, (unsigned char*)entry->key, entry->keyLength)) {
  234. return entry;
  235. }
  236. }
  237. return null;
  238. }
  239. V lookupWithDefault(const char* key, u32 keyLength, V defaultValue) {
  240. ULE_TYPES_H_FTAG;
  241. auto entry = this->lookup(key, keyLength);
  242. if (entry == null) return defaultValue;
  243. return entry->value;
  244. }
  245. // do not set |freeValues| to true unless the template parameter 'T' is a pointer,
  246. // and the table is responsible for freeing the memory.
  247. void clear(bool freeValues = false, bool freeKeys = true) {
  248. ULE_TYPES_H_FTAG;
  249. for (u32 i = 0; i < this->lanes; i++) {
  250. TableEntry<V>** lane = &this->entries[i];
  251. TableEntry<V>* entry = *lane;
  252. while (entry != null) {
  253. auto next = entry->next;
  254. if (freeKeys) pFree(entry->key);
  255. if (freeValues) {
  256. // @HACK - it's only relevant to free the value if it's an owned pointer
  257. // (the table is effectively 'responsible' for that memory)
  258. // but you may have 'V' be a non-pointer value entirely, causing this cast to
  259. // be nonsensical/a bug in other cases.
  260. //
  261. // make sure you know what you're doing when you set |freeValues| to |true|.
  262. //
  263. // there's probably a 'better' way to do this using C++ template voodoo,
  264. // but I have no interest in digging myself a deeper grave there.
  265. #ifdef __clang__
  266. #pragma clang diagnostic push
  267. #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
  268. #endif
  269. pFree((void*) entry->value);
  270. #ifdef __clang__
  271. #pragma clang diagnostic pop
  272. #endif
  273. }
  274. pFree(entry);
  275. entry = next;
  276. }
  277. *lane = null;
  278. }
  279. this->length = 0;
  280. }
  281. void traverse(const std::function <void (TableEntry<V>*)>& entryCallback) {
  282. ULE_TYPES_H_FTAG;
  283. const auto lanes = this->lanes;
  284. for (u32 i = 0; i < lanes; i++) {
  285. TableEntry<V>* entry = this->entries[i];
  286. while (entry != null) {
  287. entryCallback(entry);
  288. entry = entry->next;
  289. }
  290. }
  291. }
  292. };
  293. #ifdef ULE_CONFIG_OPTION_SERIALIZATION
  294. template <typename T>
  295. static void serialize(String* str, Table<T> table) {
  296. ULE_TYPES_H_FTAG;
  297. serialize(str, table.lanes);
  298. serialize(str, table.length);
  299. for (u32 i = 0; i < table.lanes; i++) {
  300. TableEntry<T>* entry = table.entries[i];
  301. while (entry) {
  302. serialize(str, entry);
  303. entry = entry->next;
  304. }
  305. }
  306. }
  307. template <typename T>
  308. static void serialize(String* str, Table<T>* table) {
  309. ULE_TYPES_H_FTAG;
  310. SERIALIZE_HANDLE_NULL(str, table);
  311. serialize(str, table->lanes);
  312. serialize(str, table->length);
  313. for (u32 i = 0; i < table->lanes; i++) {
  314. TableEntry<T>* entry = table->entries[i];
  315. while (entry) {
  316. serialize(str, entry);
  317. entry = entry->next;
  318. }
  319. }
  320. }
  321. template <typename T>
  322. static void deserialize(char** buffer, Table<T>* table) {
  323. ULE_TYPES_H_FTAG;
  324. deserialize(buffer, &table->lanes);
  325. u32 length;
  326. deserialize(buffer, &length);
  327. for (u32 i = 0; i < length; i++) {
  328. TableEntry<T> entry;
  329. deserialize(buffer, &entry.key);
  330. deserialize(buffer, &entry.keyLength);
  331. deserialize(buffer, &entry.value);
  332. table->insert(entry.key, entry.keyLength, entry.value);
  333. pFree(entry.key);
  334. }
  335. table->length = length;
  336. }
  337. template <typename T>
  338. static void deserialize(char** buffer, Table<T>** table) {
  339. ULE_TYPES_H_FTAG;
  340. DESERIALIZE_HANDLE_NULL(buffer, table);
  341. u32 lanes;
  342. deserialize(buffer, &lanes);
  343. Table<T>* _table = new Table<T>(lanes);
  344. u32 length;
  345. deserialize(buffer, &length);
  346. for (u32 i = 0; i < length; i++) {
  347. TableEntry<T> entry;
  348. deserialize(buffer, &entry.key);
  349. deserialize(buffer, &entry.keyLength);
  350. deserialize(buffer, &entry.value);
  351. _table->insert(entry.key, entry.keyLength, entry.value);
  352. pFree(entry.key);
  353. }
  354. _table->length = length;
  355. *table = _table;
  356. }
  357. #endif // ULE_CONFIG_OPTION_SERIALIZATION
  358. //================================================================================
  359. // Fixed-key size table.
  360. //
  361. // Sometimes, you want a hash table and you know for a fact how big the keys will
  362. // be at maximum.
  363. //
  364. // This commonly happens when the keys actually aren't strings, but are integers,
  365. // or other packed values.
  366. //
  367. // You can use this instead in that case, and avoid keeping certain memory and
  368. // some work the more generic table has to deal with.
  369. //
  370. // You'll enjoy some speedup from MMX/SSE/AVX if they are supported and you use
  371. // a key size of 16, 32, or 64.
  372. //
  373. //================================================================================
  374. #include <mmintrin.h>
  375. template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 64>::type* = nullptr>
  376. static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
  377. ULE_TYPES_H_FTAG;
  378. // AVX512:
  379. //__mmask32 result = _mm512_cmpeq_epi16_mask (*((__m512i*)m1), *((__m512i*)m2));
  380. //sse4.2:
  381. //int result = 0;
  382. //for (u32 i = 0; i < 4; i++) {
  383. // __m128i s1 = *((__m128i*)(m1+(i*16)));
  384. // __m128i s2 = *((__m128i*)(m2+(i*16)));
  385. // result = _mm_cmpistro(s1, s2, _SIDD_UBYTE_OPS);
  386. //}
  387. // MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
  388. //for (u32 i = 0; i < 4; i++) {
  389. // u32 ii = i*16;
  390. // __m64 s1 = *((__m64*)(m1+ii));
  391. // __m64 s2 = *((__m64*)(m2+ii));
  392. // __m64 result = _mm_cmpeq_pi32(s1, s2);
  393. // if (((u64)result) != ~0ULL) {
  394. // return false;
  395. // }
  396. //}
  397. //return true;
  398. return String::memeq(m1, m2, KEY_SIZE);
  399. }
  400. template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 32>::type* = nullptr>
  401. static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
  402. ULE_TYPES_H_FTAG;
  403. //sse4.2:
  404. //int result = 0;
  405. //for (u32 i = 0; i < 4; i++) {
  406. // __m128i s1 = *((__m128i*)(m1+(i*16)));
  407. // __m128i s2 = *((__m128i*)(m2+(i*16)));
  408. // result = _mm_cmpistro(s1, s2, _SIDD_UBYTE_OPS);
  409. //}
  410. // MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
  411. //for (u32 i = 0; i < 2; i++) {
  412. // u32 ii = i*16;
  413. // __m64 s1 = *((__m64*)(m1+ii));
  414. // __m64 s2 = *((__m64*)(m2+ii));
  415. // __m64 result = _mm_cmpeq_pi32(s1, s2);
  416. // if (((u64)result) != ~0ULL) {
  417. // return false;
  418. // }
  419. //}
  420. //return true;
  421. return String::memeq(m1, m2, KEY_SIZE);
  422. }
  423. template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 16>::type* = nullptr>
  424. static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
  425. ULE_TYPES_H_FTAG;
  426. // MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
  427. //__m64 result = _mm_cmpeq_pi32(*((__m64*)m1), *((__m64*)m2));
  428. //return ((u64)result) == ~0ULL;
  429. return String::memeq(m1, m2, KEY_SIZE);
  430. }
  431. template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE != 64 && KEY_SIZE != 32 && KEY_SIZE != 16>::type* = nullptr>
  432. static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
  433. ULE_TYPES_H_FTAG;
  434. return String::memeq(m1, m2, KEY_SIZE);
  435. }
  436. template <size_t KEY_SIZE, typename V>
  437. struct FixedKeySizeTableEntry {
  438. FixedKeySizeTableEntry<KEY_SIZE, V>* next;
  439. const char key[KEY_SIZE];
  440. V value;
  441. };
  442. template <typename V>
  443. struct DenseHashSetEntry {
  444. };
  445. template <typename V>
  446. struct DenseHashSet {
  447. enum ControlByte {
  448. EMPTY = -128, // 0b10000000 // indicates a free slot, and a place to stop probing
  449. DELETED = -2, // 0b11111110 // indicates there is no value here, but you should keep probing
  450. SENTINEL = -1, // 0b11111111 // indicates end of stream
  451. //FULL = // 0b0xxxxxxx // indicates an occupied slot
  452. };
  453. u8* metadata; // parallel array to 'values', each byte is a 'ControlByte'.
  454. u32 length;
  455. u32 capacity;
  456. V* values;
  457. Allocator* allocator;
  458. // a 'group' represents a capacity for 16 elements in the table. the actual capacity will be |_numGroups * 16|
  459. // we use some SSE instructions that operate on chunks of 16 at a time, so we need this constraint.
  460. DenseHashSet<V>(u32 _numGroups = 4, Allocator* _allocator = &Allocator::GetDefault()) {
  461. ULE_TYPES_H_FTAG;
  462. this->length = 0;
  463. this->capacity = _numGroups;
  464. const size_t valueSize = sizeof(T) * this->capacity * 16;
  465. const size_t metadataSize = sizeof(u8) * this->capacity * 16;
  466. this->values = (T*) _allocator->mallocate(valueSize, _allocator->state);
  467. this->metadata = (u8*) _allocator->mallocate(metadataSize, _allocator->state);
  468. String::memset(this->values, '\0', valueSize);
  469. String::memset(this->metadata, (u8) DenseHashSet::EMPTY, metadataSize);
  470. }
  471. float LoadFactor() {
  472. ULE_TYPES_H_FTAG;
  473. return ((float)this->length)/((float)this->lanes);
  474. }
  475. u32 H1(u32 h) {
  476. ULE_TYPES_H_FTAG;
  477. return h >> 7;
  478. }
  479. u8 H2(u32 h) {
  480. ULE_TYPES_H_FTAG;
  481. return (u8) (h & 0x7F);
  482. }
  483. u32 Match(u8 h2) const {
  484. auto match = _mm_set1_epi8(h2);
  485. return _mm_movemask_epi8(_mm_cmpeq_epi8(match, metadata));
  486. }
  487. V Insert(const char* key, size_t keyLength, V value) {
  488. ULE_TYPES_H_FTAG;
  489. // @TODO check if we need to resize
  490. }
  491. bool Lookup(const char* key, size_t keyLength, V* outValue) {
  492. ULE_TYPES_H_FTAG;
  493. u32 h = APHash(key, keyLength);
  494. size_t group = this->H1(h) % this->capacity;
  495. while (1) {
  496. u8* g = this->metadata + group * 16;
  497. u32 mask = this->Match(this->H2(h));
  498. for (u32 i = 0; i < 32; i++) {
  499. u32 idx = mask & (1 << i);
  500. }
  501. if (mask == 0) return false;
  502. group = (group + 1) % this->capacity;
  503. }
  504. }
  505. };
  506. template <size_t KEY_SIZE, typename V>
  507. struct FixedKeySizeTable {
  508. FixedKeySizeTableEntry<KEY_SIZE, V>** entries;
  509. u32 lanes;
  510. u32 length;
  511. Allocator* allocator;
  512. FixedKeySizeTable<KEY_SIZE, V>(u32 _lanes = 16, Allocator* allocator = &Allocator::GetDefault()) {
  513. ULE_TYPES_H_FTAG;
  514. this->lanes = _lanes;
  515. this->length = 0;
  516. this->allocator = allocator;
  517. this->entries = (FixedKeySizeTableEntry<KEY_SIZE, V>**) allocator->callocate(sizeof(FixedKeySizeTableEntry<KEY_SIZE, V>*), this->lanes, null);
  518. }
  519. void* operator new(size_t size) {
  520. ULE_TYPES_H_FTAG;
  521. return (FixedKeySizeTable<KEY_SIZE, V>*) this->allocator->mallocate(sizeof(FixedKeySizeTable<KEY_SIZE, V>), null);
  522. }
  523. float LoadFactor() {
  524. ULE_TYPES_H_FTAG;
  525. return ((float)this->length)/((float)this->lanes);
  526. }
  527. V insert(const char* key, V value) {
  528. ULE_TYPES_H_FTAG;
  529. FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->lookup(key);
  530. if (!entry) { // no entry with that key exists
  531. entry = (FixedKeySizeTableEntry<KEY_SIZE, V>*) this->allocator->callocate(sizeof(FixedKeySizeTableEntry<KEY_SIZE, V>), 1, null);
  532. String::write((char*)entry->key, key, KEY_SIZE);
  533. entry->value = value;
  534. u32 hashValue = hash(key, KEY_SIZE, lanes);
  535. entry->next = entries[hashValue];
  536. entries[hashValue] = entry;
  537. this->length++;
  538. return (V) 0;
  539. } else { // entry already exists, replace its value
  540. // pFree(entry->value); // @NOTE how to cleanup if overwriting an owned pointer?
  541. V oldValue = entry->value;
  542. entry->value = value;
  543. return oldValue;
  544. }
  545. }
  546. FixedKeySizeTableEntry<KEY_SIZE, V>* lookup(const char* key) {
  547. ULE_TYPES_H_FTAG;
  548. FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->entries[hash(key, KEY_SIZE, lanes)];
  549. for (; entry != null; entry = entry->next) {
  550. if (fixedKeySizeMemEq<KEY_SIZE>((unsigned char*)key, (unsigned char*)entry->key)) {
  551. return entry;
  552. }
  553. }
  554. return null;
  555. }
  556. V lookupWithDefault(const char* key, V defaultValue) {
  557. ULE_TYPES_H_FTAG;
  558. auto entry = this->lookup(key);
  559. if (entry == null) return defaultValue;
  560. return entry->value;
  561. }
  562. // do not set |freeValues| to true unless the template parameter 'T' is a pointer,
  563. // and the table is responsible for freeing the memory.
  564. void clear(bool freeValues = false) {
  565. ULE_TYPES_H_FTAG;
  566. for (u32 i = 0; i < this->lanes; i++) {
  567. FixedKeySizeTableEntry<KEY_SIZE, V>** lane = &this->entries[i];
  568. FixedKeySizeTableEntry<KEY_SIZE, V>* entry = *lane;
  569. while (entry != null) {
  570. auto next = entry->next;
  571. if (freeValues) {
  572. // @HACK - it's only relevant to free the value if it's an owned pointer
  573. // (the table is effectively 'responsible' for that memory)
  574. // but you may have 'V' be a non-pointer value entirely, causing this cast to
  575. // be nonsensical/a bug in other cases.
  576. //
  577. // make sure you know what you're doing when you set |freeValues| to |true|.
  578. //
  579. // there's probably a 'better' way to do this using C++ template voodoo,
  580. // but I have no interest in digging myself a deeper grave there.
  581. #ifdef __clang__
  582. #pragma clang diagnostic push
  583. #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
  584. #endif
  585. if (this->allocator->free) this->allocator->free((void*) entry->value, null);
  586. #ifdef __clang__
  587. #pragma clang diagnostic pop
  588. #endif
  589. }
  590. if (this->allocator->free) this->allocator->free(entry, null);
  591. entry = next;
  592. }
  593. *lane = null;
  594. }
  595. this->length = 0;
  596. }
  597. void traverse(const std::function <void (FixedKeySizeTableEntry<KEY_SIZE, V>*)>& entryCallback) {
  598. ULE_TYPES_H_FTAG;
  599. const auto lanes = this->lanes;
  600. for (u32 i = 0; i < lanes; i++) {
  601. FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->entries[i];
  602. while (entry != null) {
  603. entryCallback(entry);
  604. entry = entry->next;
  605. }
  606. }
  607. }
  608. };
  609. //================================================================================
  610. // a better explaination of cache tables than I could possibly do in a comment:
  611. // https://fgiesen.wordpress.com/2019/02/11/cache-tables/
  612. struct CacheTableEntry {
  613. char* key;
  614. u32 keyLength;
  615. void* value;
  616. };
  617. struct CacheTable {
  618. u32 n, p;
  619. CacheTableEntry* entries; // n and p are the dimensions of the array. n is first.
  620. CacheTable(u32 _n = 8, u32 _p = 8) {
  621. ULE_TYPES_H_FTAG;
  622. this->n = _n;
  623. this->p = _p;
  624. this->entries = (CacheTableEntry*) pCalloc(this->n*this->p, sizeof(CacheTableEntry));
  625. }
  626. void* insert(const char* key, u32 keyLength, void* value) {
  627. ULE_TYPES_H_FTAG;
  628. CacheTableEntry* row = this->entries + hash(key, keyLength, this->n) * this->n;
  629. // We're going to insert in 'row'. We need some policy to decide which column to evict.
  630. // The (stupid) choice for now, is to just start at column 0, and increment every time we insert,
  631. // regardless of which row we chose.
  632. static u32 seed = 0;
  633. // increment, then modulo the number of columns.
  634. seed = ((u64) (seed+1) * (u64) this->p) >> 32;
  635. CacheTableEntry* entry = row + seed;
  636. if (entry->key != null) {
  637. // the entry was already populated. we have to free the memory for the key
  638. // (because we always copy keys on insertion)
  639. // as well as return the address of the old value we overwrite, so the caller can free it
  640. // if necessary.
  641. pFree(entry->key);
  642. entry->key = String::cpy(key, keyLength);
  643. entry->keyLength = keyLength;
  644. void* oldValue = entry->value;
  645. entry->value = value;
  646. return oldValue;
  647. } else {
  648. entry->key = String::cpy(key, keyLength);
  649. entry->keyLength = keyLength;
  650. entry->value = value;
  651. return null;
  652. }
  653. }
  654. CacheTableEntry* lookup(const char* key, u32 keyLength) {
  655. ULE_TYPES_H_FTAG;
  656. CacheTableEntry* row = this->entries + hash(key, keyLength, this->n) * this->n;
  657. for (u32 i = 0; i < this->p; i++) {
  658. CacheTableEntry* entry = row + i;
  659. if (String::memeq((unsigned char*)key, keyLength, (unsigned char*)entry->key, entry->keyLength)) {
  660. return entry;
  661. }
  662. }
  663. return null;
  664. }
  665. void clear(bool freeValues = false) {
  666. ULE_TYPES_H_FTAG;
  667. for (u32 i = 0; i < this->n; i++) {
  668. CacheTableEntry* row = this->entries + i * this->n;
  669. for (u32 j = 0; j < this->p; j++) {
  670. CacheTableEntry* entry = row + j;
  671. if (entry->key != null) {
  672. pFree(entry->key);
  673. }
  674. if (freeValues && entry->value != null) {
  675. pFree(entry->value);
  676. }
  677. }
  678. }
  679. }
  680. };
  681. #endif