SparseArray.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. #include <Tests/Framework/Framework.h>
  6. #include <AnKi/Util/SparseArray.h>
  7. #include <AnKi/Util/HighRezTimer.h>
  8. #include <unordered_map>
  9. #include <ctime>
  10. #include <algorithm>
  11. #include <malloc.h>
  12. namespace anki {
  13. namespace {
  14. static I64 constructor0Count = 0;
  15. static I64 constructor1Count = 0;
  16. static I64 constructor2Count = 0;
  17. static I64 constructor3Count = 0;
  18. static I64 destructorCount = 0;
  19. static I64 copyCount = 0;
  20. static I64 moveCount = 0;
  21. class SAFoo
  22. {
  23. public:
  24. int m_x;
  25. SAFoo()
  26. : m_x(0)
  27. {
  28. ++constructor0Count;
  29. }
  30. SAFoo(int x)
  31. : m_x(x)
  32. {
  33. ++constructor1Count;
  34. }
  35. SAFoo(const SAFoo& b)
  36. : m_x(b.m_x)
  37. {
  38. ++constructor2Count;
  39. }
  40. SAFoo(SAFoo&& b)
  41. : m_x(b.m_x)
  42. {
  43. b.m_x = 0;
  44. ++constructor3Count;
  45. }
  46. ~SAFoo()
  47. {
  48. ++destructorCount;
  49. }
  50. SAFoo& operator=(const SAFoo& b)
  51. {
  52. m_x = b.m_x;
  53. ++copyCount;
  54. return *this;
  55. }
  56. SAFoo& operator=(SAFoo&& b)
  57. {
  58. m_x = b.m_x;
  59. b.m_x = 0;
  60. ++moveCount;
  61. return *this;
  62. }
  63. static void checkCalls()
  64. {
  65. ANKI_TEST_EXPECT_EQ(constructor0Count, 0); // default
  66. ANKI_TEST_EXPECT_GT(constructor1Count, 0);
  67. ANKI_TEST_EXPECT_EQ(constructor2Count, 0); // copy
  68. ANKI_TEST_EXPECT_GT(constructor3Count, 0); // move
  69. ANKI_TEST_EXPECT_EQ(destructorCount, constructor1Count + constructor3Count);
  70. ANKI_TEST_EXPECT_EQ(copyCount, 0); // copy
  71. ANKI_TEST_EXPECT_GEQ(moveCount, 0); // move
  72. constructor0Count = constructor1Count = constructor2Count = constructor3Count = destructorCount = copyCount = moveCount = 0;
  73. }
  74. };
  75. template<typename TIndex>
  76. class Config
  77. {
  78. public:
  79. using Index = TIndex;
  80. Index m_initialStorage = 0;
  81. U32 m_linearProbingCount = 0;
  82. F32 m_maxLodFactor = 0;
  83. Index getInitialStorageSize() const
  84. {
  85. return m_initialStorage;
  86. }
  87. U32 getLinearProbingCount() const
  88. {
  89. return m_linearProbingCount;
  90. }
  91. F32 getMaxLoadFactor() const
  92. {
  93. return m_maxLodFactor;
  94. }
  95. };
  96. } // namespace
  97. } // namespace anki
  98. ANKI_TEST(Util, SparseArray)
  99. {
  100. // Set same key
  101. {
  102. SparseArray<PtrSize> arr;
  103. arr.emplace(1000, 123);
  104. arr.emplace(1000, 124);
  105. auto it = arr.find(1000);
  106. ANKI_TEST_EXPECT_EQ(*it, 124);
  107. arr.erase(it);
  108. }
  109. // Check destroy and grow
  110. {
  111. SparseArray<SAFoo, SingletonMemoryPoolWrapper<DefaultMemoryPool>, Config<U32>> arr(Config<U32>{64, 2, 0.8f});
  112. arr.emplace(64 * 1, 123);
  113. arr.emplace(64 * 2, 124);
  114. arr.emplace(64 * 3, 125);
  115. ANKI_TEST_EXPECT_EQ(arr.find(64 * 1)->m_x, 123);
  116. ANKI_TEST_EXPECT_EQ(arr.find(64 * 2)->m_x, 124);
  117. ANKI_TEST_EXPECT_EQ(arr.find(64 * 3)->m_x, 125);
  118. SparseArray<SAFoo, SingletonMemoryPoolWrapper<DefaultMemoryPool>, Config<U32>> arr2(std::move(arr));
  119. arr2.destroy();
  120. SAFoo::checkCalls();
  121. }
  122. // Do complex insertions
  123. {
  124. SparseArray<SAFoo, SingletonMemoryPoolWrapper<DefaultMemoryPool>, Config<U32>> arr(Config<U32>{64, 3, 0.8f});
  125. arr.emplace(64u * 0 - 1, 1);
  126. // Linear probing to 0
  127. arr.emplace(64 * 1 - 1, 2);
  128. // Linear probing to 1
  129. arr.emplace(64 * 2 - 1, 3);
  130. // Linear probing to 2
  131. arr.emplace(1, 3);
  132. // Swap
  133. arr.emplace(64 * 1, 3);
  134. ANKI_TEST_EXPECT_EQ(arr.getSize(), 5);
  135. arr.destroy();
  136. SAFoo::checkCalls();
  137. }
  138. // Fuzzy test
  139. {
  140. constexpr U kMax = 10000;
  141. SparseArray<SAFoo> arr;
  142. std::vector<int> numbers;
  143. srand(U32(time(nullptr)));
  144. // Insert random
  145. for(U i = 0; i < kMax; ++i)
  146. {
  147. I32 num;
  148. while(1)
  149. {
  150. num = rand();
  151. if(std::find(numbers.begin(), numbers.end(), num) == numbers.end())
  152. {
  153. // Not found
  154. ANKI_TEST_EXPECT_EQ(arr.find(num), arr.getEnd());
  155. arr.emplace(num, num);
  156. ANKI_TEST_EXPECT_EQ(arr.getSize(), i + 1);
  157. numbers.push_back(num);
  158. break;
  159. }
  160. else
  161. {
  162. // Found
  163. ANKI_TEST_EXPECT_NEQ(arr.find(num), arr.getEnd());
  164. }
  165. }
  166. arr.validate();
  167. }
  168. ANKI_TEST_EXPECT_EQ(arr.getSize(), kMax);
  169. // Remove randomly
  170. U count = kMax;
  171. while(count--)
  172. {
  173. U idx = rand() % (count + 1);
  174. int num = numbers[idx];
  175. numbers.erase(numbers.begin() + idx);
  176. auto it = arr.find(num);
  177. ANKI_TEST_EXPECT_NEQ(it, arr.getEnd());
  178. ANKI_TEST_EXPECT_EQ(it->m_x, num);
  179. arr.erase(it);
  180. arr.validate();
  181. }
  182. }
  183. // Fuzzy test #2: Do random insertions and removals
  184. {
  185. constexpr U kMax = 10000;
  186. SparseArray<SAFoo, SingletonMemoryPoolWrapper<DefaultMemoryPool>, Config<U64>> arr(Config<U64>{64, 8, 0.8f});
  187. using StlMap = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>>;
  188. StlMap map(10, std::hash<int>(), std::equal_to<int>());
  189. for(U i = 0; i < kMax; ++i)
  190. {
  191. const Bool insert = (rand() & 1) || arr.getSize() == 0;
  192. if(insert)
  193. {
  194. const I32 idx = rand();
  195. if(map.find(idx) != map.end())
  196. {
  197. continue;
  198. }
  199. arr.emplace(idx, idx + 1);
  200. map[idx] = idx + 1;
  201. arr.validate();
  202. }
  203. else
  204. {
  205. const U idx = U(rand()) % map.size();
  206. auto it = std::next(std::begin(map), idx);
  207. int key = it->first;
  208. auto it2 = arr.find(key);
  209. ANKI_TEST_EXPECT_NEQ(it2, arr.getEnd());
  210. ANKI_TEST_EXPECT_EQ(it->second, it2->m_x);
  211. map.erase(it);
  212. arr.erase(it2);
  213. ANKI_TEST_EXPECT_EQ(arr.find(key), arr.getEnd());
  214. arr.validate();
  215. }
  216. // Iterate and check
  217. {
  218. StlMap bMap = map;
  219. auto it = arr.getBegin();
  220. while(it != arr.getEnd())
  221. {
  222. I32 key = it->m_x - 1;
  223. auto it2 = bMap.find(key);
  224. ANKI_TEST_EXPECT_NEQ(it2, bMap.end());
  225. bMap.erase(it2);
  226. ++it;
  227. }
  228. }
  229. }
  230. arr.destroy();
  231. // Check what the SparseArray have called
  232. SAFoo::checkCalls();
  233. }
  234. }
  235. static I64 akAllocSize = 0;
  236. static I64 akMaxAllocSize = 0;
  237. static ANKI_DONT_INLINE void* allocAlignedAk([[maybe_unused]] void* userData, void* ptr, PtrSize size, [[maybe_unused]] PtrSize alignment)
  238. {
  239. if(ptr == nullptr)
  240. {
  241. #if ANKI_OS_LINUX
  242. akAllocSize += size;
  243. akMaxAllocSize = max(akMaxAllocSize, akAllocSize);
  244. #endif
  245. return malloc(size);
  246. }
  247. else
  248. {
  249. #if ANKI_OS_LINUX
  250. PtrSize s = malloc_usable_size(ptr);
  251. akAllocSize -= s;
  252. #endif
  253. free(ptr);
  254. return nullptr;
  255. }
  256. }
  257. static I64 stlAllocSize = 0;
  258. static I64 stlMaxAllocSize = 0;
  259. static ANKI_DONT_INLINE void* allocAlignedStl([[maybe_unused]] void* userData, void* ptr, PtrSize size, [[maybe_unused]] PtrSize alignment)
  260. {
  261. if(ptr == nullptr)
  262. {
  263. #if ANKI_OS_LINUX
  264. stlAllocSize += size;
  265. stlMaxAllocSize = max(stlMaxAllocSize, stlAllocSize);
  266. #endif
  267. return malloc(size);
  268. }
  269. else
  270. {
  271. #if ANKI_OS_LINUX
  272. PtrSize s = malloc_usable_size(ptr);
  273. stlAllocSize -= s;
  274. #endif
  275. free(ptr);
  276. return nullptr;
  277. }
  278. }
  279. ANKI_TEST(Util, SparseArrayBench)
  280. {
  281. HeapMemoryPool pool(allocAlignedAk, nullptr);
  282. HeapAllocator<U8> allocStl(allocAlignedStl, nullptr);
  283. using StlMap = std::unordered_map<int, int, std::hash<int>, std::equal_to<int>, HeapAllocator<std::pair<const int, int>>>;
  284. StlMap stdMap(10, std::hash<int>(), std::equal_to<int>(), allocStl);
  285. using AkMap = SparseArray<int, SingletonMemoryPoolWrapper<DefaultMemoryPool>, Config<U32>>;
  286. AkMap akMap(Config<U32>{256, U32(log2(256.0f)), 0.9f});
  287. HighRezTimer timer;
  288. constexpr U kCount = 1024 * 1024 * 6;
  289. // Create a huge set
  290. std::vector<int> vals;
  291. {
  292. std::unordered_map<int, int> tmpMap;
  293. for(U i = 0; i < kCount; ++i)
  294. {
  295. // Put unique keys
  296. int v;
  297. do
  298. {
  299. v = int(getRandom());
  300. } while(tmpMap.find(v) != tmpMap.end() && v != 0);
  301. tmpMap[v] = 1;
  302. vals.push_back(v);
  303. }
  304. }
  305. // Insertion
  306. {
  307. // AnkI
  308. timer.start();
  309. for(U i = 0; i < kCount; ++i)
  310. {
  311. akMap.emplace(vals[i], vals[i]);
  312. }
  313. timer.stop();
  314. Second akTime = timer.getElapsedTime();
  315. // STL
  316. timer.start();
  317. for(U i = 0; i < kCount; ++i)
  318. {
  319. stdMap[vals[i]] = vals[i];
  320. }
  321. timer.stop();
  322. Second stlTime = timer.getElapsedTime();
  323. ANKI_TEST_LOGI("Inserting bench: STL %f AnKi %f | %f%%", stlTime, akTime, stlTime / akTime * 100.0);
  324. }
  325. // Search
  326. {
  327. // Search in random order
  328. randomShuffle(vals.begin(), vals.end());
  329. int count = 0;
  330. // Find values AnKi
  331. timer.start();
  332. for(U i = 0; i < kCount; ++i)
  333. {
  334. auto it = akMap.find(vals[i]);
  335. count += *it;
  336. }
  337. timer.stop();
  338. Second akTime = timer.getElapsedTime();
  339. // Find values STL
  340. timer.start();
  341. for(U i = 0; i < kCount; ++i)
  342. {
  343. count += stdMap[vals[i]];
  344. }
  345. timer.stop();
  346. Second stlTime = timer.getElapsedTime();
  347. // Print the "count" so that the compiler won't optimize it
  348. ANKI_TEST_LOGI("Find bench: STL %f AnKi %f | %f%% (r:%d)", stlTime, akTime, stlTime / akTime * 100.0, count);
  349. }
  350. // Mem usage
  351. const I64 stlMemUsage = stlMaxAllocSize + sizeof(stdMap);
  352. const I64 akMemUsage = akMaxAllocSize + sizeof(akMap);
  353. ANKI_TEST_LOGI("Max mem usage: STL %li AnKi %li | %f%% (At any given time what was the max mem usage)", stlMemUsage, akMemUsage,
  354. F64(stlMemUsage) / F32(akMemUsage) * 100.0f);
  355. // Deletes
  356. {
  357. // Remove in random order
  358. randomShuffle(vals.begin(), vals.end());
  359. // Random delete AnKi
  360. Second akTime = 0.0;
  361. for(U i = 0; i < vals.size(); ++i)
  362. {
  363. auto it = akMap.find(vals[i]);
  364. timer.start();
  365. akMap.erase(it);
  366. timer.stop();
  367. akTime += timer.getElapsedTime();
  368. }
  369. // Random delete STL
  370. Second stlTime = 0.0;
  371. for(U i = 0; i < vals.size(); ++i)
  372. {
  373. auto it = stdMap.find(vals[i]);
  374. timer.start();
  375. stdMap.erase(it);
  376. timer.stop();
  377. stlTime += timer.getElapsedTime();
  378. }
  379. ANKI_TEST_LOGI("Deleting bench: STL %f AnKi %f | %f%%", stlTime, akTime, stlTime / akTime * 100.0);
  380. }
  381. akMap.destroy();
  382. }