#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace mln; using namespace mln::util; template void testSetDiff(TMapIter beg, TMapIter end, TMap2& map2) { while (beg != end) { auto item = *beg++; auto i = map2.find(item.first); EXPECT_TRUE(i); EXPECT_EQ(*i, item.second); } } // TODO: need key-value iteration // template // void testSetDiff(TMap1 map1, TMap2& map2) { // testSetDiff(map1.begin(), map1.end(), map2); // testSetDiff(map2.begin(), map2.end(), map1); //} template void testInit() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); // Construct from separate initializer lists const auto map = TinyUnorderedMap{{1, 2, 3}, {3, 2, 1}}; EXPECT_EQ(3, map.size()); // Construct from list of pairs const auto map2 = TinyUnorderedMap{{{1, 3}, {2, 2}, {3, 1}}}; EXPECT_EQ(map.size(), map2.size()); // testSetDiff(map, map2); EXPECT_TRUE(map[1] && map2[1]); EXPECT_TRUE(map[2] && map2[2]); EXPECT_TRUE(map[3] && map2[3]); // Construct in a different order const auto map3 = TinyUnorderedMap{{2, 3, 1}, {2, 1, 3}}; EXPECT_EQ(map.size(), map3.size()); // testSetDiff(map, map3); EXPECT_TRUE(map3[1]); EXPECT_TRUE(map3[2]); EXPECT_TRUE(map3[3]); } TEST(TinyMap, Init) { testInit<0>(); testInit<2>(); testInit<5>(); testInit<10>(); } template void testCopy() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const auto map = TinyUnorderedMap{{1, 2, 3}, {3, 2, 1}}; decltype(map) map2(map); std::remove_const_t map3; map3 = map2; // testSetDiff(map, map2); // testSetDiff(map, map3); EXPECT_EQ(map.size(), map2.size()); EXPECT_EQ(map.size(), map3.size()); EXPECT_TRUE(map[1] && map2[1] && map3[1]); EXPECT_TRUE(map[2] && map2[2] && map3[2]); EXPECT_TRUE(map[3] && map2[3] && map3[3]); } TEST(TinyMap, Copy) { testCopy<0>(); testCopy<2>(); testCopy<5>(); testCopy<10>(); } template void testGenInit() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const auto input = std::vector{1, 2, 3}; const auto map = TinyUnorderedMap{input.begin(), input.end(), [](const auto& x) { return std::make_pair(x, 4 - x); }}; EXPECT_EQ(3, map.size()); EXPECT_EQ(3, map[1]); EXPECT_EQ(2, map[2]); EXPECT_EQ(1, map[3]); } TEST(TinyMap, GenInit) { testGenInit<0>(); testGenInit<2>(); testGenInit<5>(); testGenInit<10>(); } template void testMove() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const auto map = TinyUnorderedMap{{1, 2, 3}, {3, 2, 1}}; std::remove_const_t map2(map); // move constructor decltype(map2) map3(std::move(map2)); EXPECT_TRUE(map2.empty()); // testSetDiff(map, map3); EXPECT_TRUE(map3[1]); EXPECT_TRUE(map3[2]); EXPECT_TRUE(map3[3]); // move assignment decltype(map2) map4; map4 = std::move(map3); EXPECT_TRUE(map3.empty()); // testSetDiff(map, map4); EXPECT_TRUE(map4[1]); EXPECT_TRUE(map4[2]); EXPECT_TRUE(map4[3]); map4.clear(); EXPECT_TRUE(map4.empty()); } TEST(TinyMap, Move) { testMove<0>(); testMove<2>(); testMove<5>(); testMove<10>(); } template void testConstLookup() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const auto map = TinyUnorderedMap{{2, 4, 6}, {3, 2, 1}}; EXPECT_EQ(3, *map.find(2)); EXPECT_EQ(2, *map.find(4)); EXPECT_EQ(1, *map.find(6)); } TEST(TinyMap, ConstLookup) { testConstLookup<0>(); testConstLookup<2>(); testConstLookup<5>(); testConstLookup<10>(); } template void testMutableLookup() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); auto map = TinyUnorderedMap{{2, 4, 6}, {3, 2, 1}}; EXPECT_EQ(3, *map.find(2)); EXPECT_EQ(2, *map.find(4)); EXPECT_EQ(1, *map.find(6)); // operator[] doesn't insert... for now EXPECT_FALSE(map[7]); EXPECT_FALSE(map[5]); EXPECT_FALSE(map[0]); EXPECT_FALSE(map[3]); EXPECT_EQ(3, map.size()); EXPECT_EQ(3, map[2]); EXPECT_EQ(2, map[4]); EXPECT_EQ(1, map[6]); } TEST(TinyMap, MutableLookup) { testMutableLookup<0>(); testMutableLookup<2>(); testMutableLookup<5>(); testMutableLookup<10>(); } template void testTileIDKey() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const std::vector keys = { {1, 0, {1, 0, 0}}, {1, 0, {1, 1, 0}}, {1, 0, {1, 0, 1}}, {1, 0, {1, 1, 1}}, {2, 0, {2, 0, 0}}, {2, 0, {2, 1, 0}}, {2, 0, {2, 0, 1}}, {2, 0, {2, 1, 1}}, {2, 0, {2, 2, 1}}, {2, 0, {2, 3, 1}}, {2, 0, {2, 2, 2}}, {2, 0, {2, 3, 2}}, {0, 0, {0, 0, 0}}, }; const std::vector values(keys.size(), 0); const auto map = TinyUnorderedMap{ keys.begin(), keys.end(), values.begin(), values.end()}; for (const auto& k : keys) { EXPECT_TRUE(map.find(k)); EXPECT_FALSE(map.find({3, k.wrap, k.canonical})); EXPECT_FALSE(map.find({k.overscaledZ, 1, k.canonical})); } } TEST(TinyMap, TileIDKey) { testTileIDKey<0>(); testTileIDKey<2>(); testTileIDKey<5>(); testTileIDKey<10>(); } template void testCustomComp() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); const auto map = TinyUnorderedMap{{1, 2, 3}, {3, 2, 1}}; const auto map2 = TinyUnorderedMap, std::equal_to<>>{{2, 3, 1}, {2, 1, 3}}; // testSetDiff(map, map2); EXPECT_EQ(map.size(), map2.size()); EXPECT_TRUE(map2[1]); EXPECT_TRUE(map2[2]); EXPECT_TRUE(map2[3]); } TEST(TinyMap, CustomComp) { testCustomComp<0>(); testCustomComp<2>(); testCustomComp<5>(); testCustomComp<10>(); } template using TIter = typename std::vector::const_iterator; template using TMakeMap = std::function, TIter, TIter, TIter)>; using ssize_t = std::ptrdiff_t; void incTileID(OverscaledTileID& tid) { tid.canonical.y++; if (tid.canonical.y == 1U << tid.canonical.z) { tid.canonical.y = 0; tid.canonical.x++; } if (tid.canonical.x == 1U << tid.canonical.z) { tid.canonical.x = 0; tid.canonical.z++; } } // return a function that generate successive tile IDs std::function makeGenTileID() { auto tid = std::make_shared(0, 0, 0, 0, 0); return [=] { auto v = *tid; incTileID(*tid); return v; }; } std::function makeGenIntID() { static std::size_t n = 0; return [=] { return ++n; }; } std::function makeGenStringID() { static int n = 0; return [=] { return "someprefix_" + std::to_string(++n); }; } static volatile int do_not_optimize_away = 0; template void benchmark(const std::string_view label, const std::size_t max, // largest number of elements to consider const std::size_t lookups, // number of searches for each item (~ read/write ratio) const std::size_t reports, // number of items reported from `max` (should divide evenly) const TMakeMap make, const std::function generate, const std::size_t seed = 0xf00dLL) { if ((max / reports) * reports != max) { assert(false); return; } std::seed_seq seed_seq{seed}; std::default_random_engine engine(seed_seq); // build keys std::vector keys; keys.reserve(max); std::generate_n(std::back_inserter(keys), max, std::move(generate)); // build values std::vector values(keys.size()); std::iota(values.begin(), values.end(), 0); using SteadyClock = std::chrono::steady_clock; using Usec = std::chrono::microseconds; std::vector times(reports); constexpr std::size_t timingIterations = 50; // test each size from 1 to max for (std::size_t i = 1; i <= max; ++i) { // Build and lookup keys in different random orders std::vector buildKeys(keys.begin(), keys.begin() + i); std::vector testKeys(keys.begin(), keys.begin() + i); std::shuffle(buildKeys.begin(), buildKeys.end(), engine); std::shuffle(testKeys.begin(), testKeys.end(), engine); // (build a map and look up the keys many times) many times const auto startTime = SteadyClock::now(); for (std::size_t tt = 0; tt < timingIterations; ++tt) { const auto map = make(buildKeys.begin(), buildKeys.end(), values.begin(), values.begin() + i); for (std::size_t j = 0; j < lookups; ++j) { for (const auto& k : testKeys) { if (map.count(k)) { do_not_optimize_away = do_not_optimize_away + 1; } } } } if ((i % (max / reports)) == 0) { const auto totalElapsed = std::chrono::duration_cast(SteadyClock::now() - startTime); const auto elapsed = static_cast(totalElapsed.count()) / timingIterations; times[i / (max / reports) - 1] = elapsed; } } std::stringstream ss; ss << std::setw(10) << std::left << label; ss << " threshold=" << std::setw(3) << std::setfill('0') << std::right << Threshold << std::setfill(' '); ss << " (x" << std::setw(2) << (max / reports) << "): "; for (std::size_t i = 0; i < reports; ++i) { ss << std::setw(6) << std::round(times[i] * 10); } Log::Info(Event::Timing, ss.str()); } constexpr std::size_t lookups = 100; TEST(TinyMap, TEST_REQUIRES_ACCURATE_TIMING(BenchmarkRef)) { #if defined(DEBUG) Log::Info(Event::General, "Build Type: Debug"); #elif defined(NDEBUG) Log::Info(Event::General, "Build Type: Release"); #else Log::Info(Event::General, "Build Type: ?"); #endif // std::unordered_map with size_t keys benchmark>( "int:stl", 20, lookups, 10, [&](auto kb, auto ke, auto vb, auto) { std::unordered_map m; m.reserve(std::distance(kb, ke)); while (kb != ke) m.insert(std::make_pair(*kb++, *vb++)); return m; }, makeGenIntID()); // std::unordered_map with string keys benchmark>( "str:stl", 10, lookups, 10, [&](auto kb, auto ke, auto vb, auto) { std::unordered_map m; m.reserve(std::distance(kb, ke)); while (kb != ke) m.insert(std::make_pair(*kb++, *vb++)); return m; }, makeGenStringID()); benchmark>( "tile:stl", 20, lookups, 10, [&](auto kb, auto ke, auto vb, auto) { std::unordered_map m; m.reserve(std::distance(kb, ke)); while (kb != ke) m.insert(std::make_pair(*kb++, *vb++)); return m; }, makeGenTileID()); } template void testBenchmarkTinyMap() { testing::ScopedTrace trace(__FILE__, __LINE__, Threshold); // TinyMap with int keys benchmark, Threshold>( "int:tiny", 20, lookups, 10, [&](auto kb, auto ke, auto vb, auto ve) { return TinyUnorderedMap{kb, ke, vb, ve}; }, makeGenIntID()); // TinyMap with string keys benchmark, Threshold>( "str:tiny", 10, lookups, 10, [&](auto kb, auto ke, auto vb, auto ve) { return TinyUnorderedMap{kb, ke, vb, ve}; }, makeGenStringID()); benchmark, Threshold>( "tile:tiny", 20, lookups, 10, [&](auto kb, auto ke, auto vb, auto ve) { return TinyUnorderedMap{kb, ke, vb, ve}; }, makeGenTileID()); } TEST(TinyMap, TEST_REQUIRES_ACCURATE_TIMING(BenchmarkTinyMap)) { testBenchmarkTinyMap<4>(); testBenchmarkTinyMap<8>(); testBenchmarkTinyMap<16>(); }