1 //@HEADER
2 // ************************************************************************
3 //
4 //                        Kokkos v. 3.0
5 //       Copyright (2020) National Technology & Engineering
6 //               Solutions of Sandia, LLC (NTESS).
7 //
8 // Under the terms of Contract DE-NA0003525 with NTESS,
9 // the U.S. Government retains certain rights in this software.
10 //
11 // Redistribution and use in source and binary forms, with or without
12 // modification, are permitted provided that the following conditions are
13 // met:
14 //
15 // 1. Redistributions of source code must retain the above copyright
16 // notice, this list of conditions and the following disclaimer.
17 //
18 // 2. Redistributions in binary form must reproduce the above copyright
19 // notice, this list of conditions and the following disclaimer in the
20 // documentation and/or other materials provided with the distribution.
21 //
22 // 3. Neither the name of the Corporation nor the names of the
23 // contributors may be used to endorse or promote products derived from
24 // this software without specific prior written permission.
25 //
26 // THIS SOFTWARE IS PROVIDED BY NTESS "AS IS" AND ANY
27 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL NTESS OR THE
30 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
31 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
32 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
33 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
36 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 //
38 // Questions? Contact Christian R. Trott (crtrott@sandia.gov)
39 //
40 // ************************************************************************
41 //@HEADER
42 
43 #ifndef KOKKOS_TEST_UNORDERED_MAP_HPP
44 #define KOKKOS_TEST_UNORDERED_MAP_HPP
45 
46 #include <gtest/gtest.h>
47 #include <iostream>
48 #include <Kokkos_UnorderedMap.hpp>
49 
50 namespace Test {
51 
52 namespace Impl {
53 
54 template <typename MapType, bool Near = false>
55 struct TestInsert {
56   using map_type        = MapType;
57   using execution_space = typename map_type::execution_space;
58   using value_type      = uint32_t;
59 
60   map_type map;
61   uint32_t inserts;
62   uint32_t collisions;
63 
TestInsertTest::Impl::TestInsert64   TestInsert(map_type arg_map, uint32_t arg_inserts, uint32_t arg_collisions)
65       : map(arg_map), inserts(arg_inserts), collisions(arg_collisions) {}
66 
testitTest::Impl::TestInsert67   void testit(bool rehash_on_fail = true) {
68     execution_space().fence();
69 
70     uint32_t failed_count = 0;
71     do {
72       failed_count = 0;
73       Kokkos::parallel_reduce(inserts, *this, failed_count);
74 
75       if (rehash_on_fail && failed_count > 0u) {
76         const uint32_t new_capacity = map.capacity() +
77                                       ((map.capacity() * 3ull) / 20u) +
78                                       failed_count / collisions;
79         map.rehash(new_capacity);
80       }
81     } while (rehash_on_fail && failed_count > 0u);
82 
83     execution_space().fence();
84   }
85 
86   KOKKOS_INLINE_FUNCTION
initTest::Impl::TestInsert87   void init(value_type &failed_count) const { failed_count = 0; }
88 
89   KOKKOS_INLINE_FUNCTION
joinTest::Impl::TestInsert90   void join(volatile value_type &failed_count,
91             const volatile value_type &count) const {
92     failed_count += count;
93   }
94 
95   KOKKOS_INLINE_FUNCTION
operator ()Test::Impl::TestInsert96   void operator()(uint32_t i, value_type &failed_count) const {
97     const uint32_t key = Near ? i / collisions : i % (inserts / collisions);
98     if (map.insert(key, i).failed()) ++failed_count;
99   }
100 };
101 
102 template <typename MapType, bool Near>
103 struct TestErase {
104   using self_type = TestErase<MapType, Near>;
105 
106   using map_type        = MapType;
107   using execution_space = typename MapType::execution_space;
108 
109   map_type m_map;
110   uint32_t m_num_erase;
111   uint32_t m_num_duplicates;
112 
TestEraseTest::Impl::TestErase113   TestErase(map_type map, uint32_t num_erases, uint32_t num_duplicates)
114       : m_map(map), m_num_erase(num_erases), m_num_duplicates(num_duplicates) {}
115 
testitTest::Impl::TestErase116   void testit() {
117     execution_space().fence();
118     Kokkos::parallel_for(m_num_erase, *this);
119     execution_space().fence();
120   }
121 
122   KOKKOS_INLINE_FUNCTION
operator ()Test::Impl::TestErase123   void operator()(typename execution_space::size_type i) const {
124     if (Near) {
125       m_map.erase(i / m_num_duplicates);
126     } else {
127       m_map.erase(i % (m_num_erase / m_num_duplicates));
128     }
129   }
130 };
131 
132 template <typename MapType>
133 struct TestFind {
134   using map_type        = MapType;
135   using execution_space = typename MapType::execution_space::execution_space;
136   using value_type      = uint32_t;
137 
138   map_type m_map;
139   uint32_t m_num_insert;
140   uint32_t m_num_duplicates;
141   uint32_t m_max_key;
142 
TestFindTest::Impl::TestFind143   TestFind(map_type map, uint32_t num_inserts, uint32_t num_duplicates)
144       : m_map(map),
145         m_num_insert(num_inserts),
146         m_num_duplicates(num_duplicates),
147         m_max_key(((num_inserts + num_duplicates) - 1) / num_duplicates) {}
148 
testitTest::Impl::TestFind149   void testit(value_type &errors) {
150     execution_space().fence();
151     Kokkos::parallel_reduce(m_map.capacity(), *this, errors);
152     execution_space().fence();
153   }
154 
155   KOKKOS_INLINE_FUNCTION
initTest::Impl::TestFind156   static void init(value_type &dst) { dst = 0; }
157 
158   KOKKOS_INLINE_FUNCTION
joinTest::Impl::TestFind159   static void join(volatile value_type &dst, const volatile value_type &src) {
160     dst += src;
161   }
162 
163   KOKKOS_INLINE_FUNCTION
operator ()Test::Impl::TestFind164   void operator()(typename execution_space::size_type i,
165                   value_type &errors) const {
166     const bool expect_to_find_i =
167         (i < typename execution_space::size_type(m_max_key));
168 
169     const bool exists = m_map.exists(i);
170 
171     if (expect_to_find_i && !exists) ++errors;
172     if (!expect_to_find_i && exists) ++errors;
173   }
174 };
175 
176 }  // namespace Impl
177 
178 // MSVC reports a syntax error for this test.
179 // WORKAROUND MSVC
180 #ifndef _WIN32
181 template <typename Device>
test_insert(uint32_t num_nodes,uint32_t num_inserts,uint32_t num_duplicates,bool near)182 void test_insert(uint32_t num_nodes, uint32_t num_inserts,
183                  uint32_t num_duplicates, bool near) {
184   using map_type = Kokkos::UnorderedMap<uint32_t, uint32_t, Device>;
185   using const_map_type =
186       Kokkos::UnorderedMap<const uint32_t, const uint32_t, Device>;
187 
188   const uint32_t expected_inserts =
189       (num_inserts + num_duplicates - 1u) / num_duplicates;
190 
191   map_type map;
192   map.rehash(num_nodes, false);
193 
194   if (near) {
195     Impl::TestInsert<map_type, true> test_insert(map, num_inserts,
196                                                  num_duplicates);
197     test_insert.testit();
198   } else {
199     Impl::TestInsert<map_type, false> test_insert(map, num_inserts,
200                                                   num_duplicates);
201     test_insert.testit();
202   }
203 
204   const bool print_list = false;
205   if (print_list) {
206     Kokkos::Impl::UnorderedMapPrint<map_type> f(map);
207     f.apply();
208   }
209 
210   const uint32_t map_size = map.size();
211 
212   ASSERT_FALSE(map.failed_insert());
213   {
214     EXPECT_EQ(expected_inserts, map_size);
215 
216     {
217       uint32_t find_errors = 0;
218       Impl::TestFind<const_map_type> test_find(map, num_inserts,
219                                                num_duplicates);
220       test_find.testit(find_errors);
221       EXPECT_EQ(0u, find_errors);
222     }
223 
224     map.begin_erase();
225     Impl::TestErase<map_type, false> test_erase(map, num_inserts,
226                                                 num_duplicates);
227     test_erase.testit();
228     map.end_erase();
229     EXPECT_EQ(0u, map.size());
230   }
231 }
232 #endif
233 
234 template <typename Device>
test_failed_insert(uint32_t num_nodes)235 void test_failed_insert(uint32_t num_nodes) {
236   using map_type = Kokkos::UnorderedMap<uint32_t, uint32_t, Device>;
237 
238   map_type map(num_nodes);
239   Impl::TestInsert<map_type> test_insert(map, 2u * num_nodes, 1u);
240   test_insert.testit(false /*don't rehash on fail*/);
241   typename Device::execution_space().fence();
242 
243   EXPECT_TRUE(map.failed_insert());
244 }
245 
246 template <typename Device>
test_deep_copy(uint32_t num_nodes)247 void test_deep_copy(uint32_t num_nodes) {
248   using map_type = Kokkos::UnorderedMap<uint32_t, uint32_t, Device>;
249   using const_map_type =
250       Kokkos::UnorderedMap<const uint32_t, const uint32_t, Device>;
251 
252   using host_map_type = typename map_type::HostMirror;
253 
254   map_type map;
255   map.rehash(num_nodes, false);
256 
257   {
258     Impl::TestInsert<map_type> test_insert(map, num_nodes, 1);
259     test_insert.testit();
260     ASSERT_EQ(map.size(), num_nodes);
261     ASSERT_FALSE(map.failed_insert());
262     {
263       uint32_t find_errors = 0;
264       Impl::TestFind<map_type> test_find(map, num_nodes, 1);
265       test_find.testit(find_errors);
266       EXPECT_EQ(find_errors, 0u);
267     }
268   }
269 
270   host_map_type hmap;
271   Kokkos::deep_copy(hmap, map);
272 
273   ASSERT_EQ(map.size(), hmap.size());
274   ASSERT_EQ(map.capacity(), hmap.capacity());
275   {
276     uint32_t find_errors = 0;
277     Impl::TestFind<host_map_type> test_find(hmap, num_nodes, 1);
278     test_find.testit(find_errors);
279     EXPECT_EQ(find_errors, 0u);
280   }
281 
282   map_type mmap;
283   Kokkos::deep_copy(mmap, hmap);
284 
285   const_map_type cmap = mmap;
286 
287   EXPECT_EQ(cmap.size(), num_nodes);
288 
289   {
290     uint32_t find_errors = 0;
291     Impl::TestFind<const_map_type> test_find(cmap, num_nodes, 1);
292     test_find.testit(find_errors);
293     EXPECT_EQ(find_errors, 0u);
294   }
295 }
296 
297 // FIXME_SYCL wrong results on Nvidia GPUs but correct on Host and Intel GPUs
298 // FIXME_HIP
299 // WORKAROUND MSVC
300 #if !(defined(KOKKOS_ENABLE_HIP) && (HIP_VERSION < 401)) && \
301     !defined(_WIN32) && !defined(KOKKOS_ENABLE_SYCL)
TEST(TEST_CATEGORY,UnorderedMap_insert)302 TEST(TEST_CATEGORY, UnorderedMap_insert) {
303   for (int i = 0; i < 500; ++i) {
304     test_insert<TEST_EXECSPACE>(100000, 90000, 100, true);
305     test_insert<TEST_EXECSPACE>(100000, 90000, 100, false);
306   }
307 }
308 #endif
309 
TEST(TEST_CATEGORY,UnorderedMap_failed_insert)310 TEST(TEST_CATEGORY, UnorderedMap_failed_insert) {
311   for (int i = 0; i < 1000; ++i) test_failed_insert<TEST_EXECSPACE>(10000);
312 }
313 
TEST(TEST_CATEGORY,UnorderedMap_deep_copy)314 TEST(TEST_CATEGORY, UnorderedMap_deep_copy) {
315   for (int i = 0; i < 2; ++i) test_deep_copy<TEST_EXECSPACE>(10000);
316 }
317 
TEST(TEST_CATEGORY,UnorderedMap_valid_empty)318 TEST(TEST_CATEGORY, UnorderedMap_valid_empty) {
319   using Key   = int;
320   using Value = int;
321   using Map   = Kokkos::UnorderedMap<Key, Value, TEST_EXECSPACE>;
322 
323   Map m{};
324   Map n{};
325   n = Map{m.capacity()};
326   n.rehash(m.capacity());
327   Kokkos::deep_copy(n, m);
328   ASSERT_TRUE(m.is_allocated());
329   ASSERT_TRUE(n.is_allocated());
330 }
331 
332 }  // namespace Test
333 
334 #endif  // KOKKOS_TEST_UNORDERED_MAP_HPP
335