diff --git a/.gitmodules b/.gitmodules index c2c74ebc..12e87b1d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,9 @@ [submodule "external/zstd"] path = external/zstd url = https://github.com/facebook/zstd.git +[submodule "external/abseil-cpp"] + path = external/abseil-cpp + url = https://github.com/abseil/abseil-cpp.git +[submodule "external/oneTBB"] + path = external/oneTBB + url = https://github.com/oneapi-src/oneTBB.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2825c9f8..94121133 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ else() endif() add_subdirectory(external ${EXLUSION_SPECIFIER}) + add_subdirectory(src) set(BENCHMARK_ENABLE_GTEST_TESTS OFF) add_subdirectory(bench ${EXLUSION_SPECIFIER}) diff --git a/bench/riptide_bench/riptide_bench/CMakeLists.txt b/bench/riptide_bench/riptide_bench/CMakeLists.txt index 41c64327..a09cce29 100644 --- a/bench/riptide_bench/riptide_bench/CMakeLists.txt +++ b/bench/riptide_bench/riptide_bench/CMakeLists.txt @@ -41,11 +41,11 @@ add_executable(${TARGET_NAME} ${HEADERS} ${SOURCES}) get_target_property(RT_SOURCE_DIR riptide_cpp SOURCE_DIR) -target_include_directories(${TARGET_NAME} PRIVATE ${RT_SOURCE_DIR} ${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS}) +target_include_directories(${TARGET_NAME} PRIVATE ${RT_SOURCE_DIR} external/oneTBB/include ${Python3_INCLUDE_DIRS} ${Python3_NumPy_INCLUDE_DIRS}) target_link_directories(${TARGET_NAME} PRIVATE ${Python3_LIBRARY_DIRS}) -target_link_libraries(${TARGET_NAME} PRIVATE riptide_cpp ${Python3_Libraries} benchmark::benchmark $<$:pthread> $<$:rt>) +target_link_libraries(${TARGET_NAME} PRIVATE riptide_cpp TBB::tbb ${Python3_Libraries} benchmark::benchmark $<$:pthread> $<$:rt>) if(WIN32) set(_TARGET_DIR $) diff --git a/bench/riptide_bench/riptide_bench/hash_linear_bench.cpp b/bench/riptide_bench/riptide_bench/hash_linear_bench.cpp index bb834066..732f2de4 100644 --- a/bench/riptide_bench/riptide_bench/hash_linear_bench.cpp +++ b/bench/riptide_bench/riptide_bench/hash_linear_bench.cpp @@ -1,8 +1,10 @@ #include "RipTide.h" #include "HashLinear.h" +#include "flat_hash_map.h" #include "benchmark/benchmark.h" +#include #include #include #include @@ -10,10 +12,34 @@ namespace { -#if 0 - std::vector test_data(1024ULL * 1024ULL * 1024ULL); + std::vector test_data(2ULL * 1024ULL * 1024ULL); std::random_device dev{}; CHashLinear hasher{}; + fhm_hasher new_hasher{}; + std::vector needles(1024ULL * 1024ULL); + std::array output{}; + std::array bools{}; + + void bench_IsMemberHash64(benchmark::State & state) + { + std::mt19937 engine(dev()); + std::uniform_int_distribution dist(3002950000, test_data.size() + 3002950000); + std::iota(std::begin(test_data), std::end(test_data), 3002954500); + std::generate(std::begin(needles), std::end(needles), [&] { return dist(engine); }); + + for (auto _ : state) + { + IsMemberHash64(needles.size(), needles.data(), test_data.size(), test_data.data(), output.data(), bools.data(), 8, HASH_MODE(1), 0); + benchmark::DoNotOptimize(test_data.data()); + benchmark::DoNotOptimize(output.data()); + benchmark::DoNotOptimize(bools.data()); + benchmark::DoNotOptimize(needles.data()); + benchmark::ClobberMemory(); + } + + } + + BENCHMARK(bench_IsMemberHash64)->Unit(benchmark::kMillisecond)->UseRealTime(); void bench_MakeHashLocation(benchmark::State & state) { @@ -27,6 +53,39 @@ namespace } } - BENCHMARK(bench_MakeHashLocation); -#endif + BENCHMARK(bench_MakeHashLocation)->Unit(benchmark::kMillisecond)->UseRealTime(); + + void bench_is_member(benchmark::State & state) + { + std::mt19937 engine(dev()); + std::uniform_int_distribution dist(3002950000, test_data.size() + 3002950000); + std::iota(std::begin(test_data), std::end(test_data), 3002954500); + std::generate(std::begin(needles), std::end(needles), [&] { return dist(engine); }); + + for (auto _ : state) + { + is_member(needles.size(), reinterpret_cast(needles.data()), test_data.size(), reinterpret_cast(test_data.data()), output.data(), bools.data(), uint64_t{}); + benchmark::DoNotOptimize(test_data.data()); + benchmark::DoNotOptimize(output.data()); + benchmark::DoNotOptimize(bools.data()); + benchmark::DoNotOptimize(needles.data()); + benchmark::ClobberMemory(); + } + } + + BENCHMARK(bench_is_member)->Unit(benchmark::kMillisecond)->UseRealTime(); + + void bench_make_hash(benchmark::State & state) + { + std::iota(std::begin(test_data), std::end(test_data), 3002954500); + std::shuffle(std::begin(test_data), std::end(test_data), std::mt19937{ dev() }); + for (auto _ : state) + { + new_hasher.make_hash(test_data.size(), reinterpret_cast(test_data.data()), 0); + benchmark::DoNotOptimize(test_data.data()); + benchmark::ClobberMemory(); + } + } + + BENCHMARK(bench_make_hash)->Unit(benchmark::kMillisecond)->UseRealTime(); } diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 203aa699..4b5c54ea 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -10,3 +10,11 @@ set(ZSTD_MULTITHREAD_SUPPORT OFF CACHE BOOL "" FORCE) add_subdirectory(zstd/build/cmake) set(EXTERNAL_ZSTD_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/zstd/lib PARENT_SCOPE) + +# abseil-cpp +set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE) +add_subdirectory(abseil-cpp) + +set(TBB_DISABLE_HWLOC_AUTOMATIC_SEARCH ON CACHE BOOL "" FORCE) +add_subdirectory(oneTBB) diff --git a/external/abseil-cpp b/external/abseil-cpp new file mode 160000 index 00000000..0c6302fe --- /dev/null +++ b/external/abseil-cpp @@ -0,0 +1 @@ +Subproject commit 0c6302fe427963ec5c471d3ee660120682ab15f7 diff --git a/external/oneTBB b/external/oneTBB new file mode 160000 index 00000000..9d2a3477 --- /dev/null +++ b/external/oneTBB @@ -0,0 +1 @@ +Subproject commit 9d2a3477ce276d437bf34b1582781e5b11f9b37a diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4844d18c..bd4a3a89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -47,6 +47,7 @@ set(HEADERS DateTime.h Ema.h #FileReadWrite.h + flat_hash_map.h GroupBy.h HashFunctions.h HashLinear.h @@ -86,6 +87,7 @@ set(SOURCES DateTime.cpp Ema.cpp #FileReadWrite.cpp + flat_hash_map.cpp GroupBy.cpp HashFunctions.cpp HashLinear.cpp @@ -117,12 +119,17 @@ get_target_property(RT_SOURCE_DIR riptide_cpp SOURCE_DIR) target_include_directories(${TARGET_NAME} PRIVATE ${EXTERNAL_ZSTD_INCLUDE_DIR} + ../external/oneTBB/include ${Python3_INCLUDE_DIRS} - ${Python3_NumPy_INCLUDE_DIRS} ) + ${Python3_NumPy_INCLUDE_DIRS} + ${ABSL_COMMON_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} Python3::Python libzstd_static + absl::base + absl::flat_hash_map + TBB::tbb $<$:rt>) # Configure the library name to identify it as a Python extension module. diff --git a/src/HashFunctions.cpp b/src/HashFunctions.cpp index 21e4ad47..ea348519 100644 --- a/src/HashFunctions.cpp +++ b/src/HashFunctions.cpp @@ -1,7 +1,11 @@ #include "RipTide.h" +#include "flat_hash_map.h" #include "HashFunctions.h" #include "HashLinear.h" #include "ndarray.h" +#include "one_input.h" + +#include // struct ndbuf; // typedef struct ndbuf { @@ -107,15 +111,27 @@ PyObject * IsMember64(PyObject * self, PyObject * args) } else { - if (arrayType1 == NPY_FLOAT32 || arrayType1 == NPY_FLOAT64) + if (std::getenv("RT_NEW_HASH")) { - IsMemberHash64(arraySize1, pDataIn1, arraySize2, pDataIn2, pDataOut2, pDataOut1, sizeType1 + 100, - HASH_MODE(hashMode), hintSize); + throw std::runtime_error("We're executing the new code path"); + auto [opt_op_trait, opt_type_trait] = riptable_cpp::set_traits(0, arrayType1); + riptable_cpp::data_type_t variant = *opt_type_trait; + [[maybe_unused]] int retval = is_member_for_type( + arraySize1, reinterpret_cast(pDataIn1), arraySize2, reinterpret_cast(pDataIn2), + pDataOut2, pDataOut1, variant, std::make_index_sequence>{}); } else { - IsMemberHash64(arraySize1, pDataIn1, arraySize2, pDataIn2, pDataOut2, pDataOut1, sizeType1, - HASH_MODE(hashMode), hintSize); + if (arrayType1 == NPY_FLOAT32 || arrayType1 == NPY_FLOAT64) + { + IsMemberHash64(arraySize1, pDataIn1, arraySize2, pDataIn2, pDataOut2, pDataOut1, sizeType1 + 100, + HASH_MODE(hashMode), hintSize); + } + else + { + IsMemberHash64(arraySize1, pDataIn1, arraySize2, pDataIn2, pDataOut2, pDataOut1, sizeType1, + HASH_MODE(hashMode), hintSize); + } } PyObject * retObject = Py_BuildValue("(OO)", boolArray, indexArray); @@ -168,7 +184,8 @@ PyObject * IsMemberCategorical(PyObject * self, PyObject * args) int sizeType1 = (int)NpyItemSize((PyObject *)inArr1); int sizeType2 = (int)NpyItemSize((PyObject *)inArr2); - LOGGING("IsMember32 %s vs %s size: %d %d\n", NpyToString(arrayType1), NpyToString(arrayType2), sizeType1, sizeType2); + LOGGING("IsMemberCategorical %s vs %s size: %d %d\n", NpyToString(arrayType1), NpyToString(arrayType2), sizeType1, + sizeType2); switch (arrayType1) { diff --git a/src/HashLinear.cpp b/src/HashLinear.cpp index 74da5458..18723b5e 100644 --- a/src/HashLinear.cpp +++ b/src/HashLinear.cpp @@ -2,11 +2,14 @@ #include #include "CommonInc.h" #include "RipTide.h" +#include "flat_hash_map.h" #include "HashLinear.h" #include "MathWorker.h" +#include "one_input.h" #include "Recycler.h" #include +#include #ifndef LogError #define LogError(...) @@ -5769,55 +5772,125 @@ PyObject * IsMember32(PyObject * self, PyObject * args) } else { - if (arrayType1 == NPY_FLOAT32 || arrayType1 == NPY_FLOAT64) + if (std::getenv("RT_NEW_HASH")) { - LOGGING("Calling float!\n"); - sizeType1 += 100; - } + if (arrayType1 == NPY_FLOAT32 || arrayType1 == NPY_FLOAT64) + { + LOGGING("Calling float!\n"); + sizeType1 += 100; + } - int dtype = NPY_INT8; + int dtype = NPY_INT8; - if (arraySize2 < 100) - { - dtype = NPY_INT8; - } - else if (arraySize2 < 30000) - { - dtype = NPY_INT16; - } - else if (arraySize2 < 2000000000) - { - dtype = NPY_INT32; + if (arraySize2 < 100) + { + dtype = NPY_INT8; + } + else if (arraySize2 < 30000) + { + dtype = NPY_INT16; + } + else if (arraySize2 < 2000000000) + { + dtype = NPY_INT32; + } + else + { + dtype = NPY_INT64; + } + + indexArray = AllocateLikeNumpyArray(inArr1, dtype); + + // make sure allocation succeeded + if (indexArray) + { + auto [opt_op_trait, opt_type_trait] = riptable_cpp::set_traits(0, arrayType1); + riptable_cpp::data_type_t variant = *opt_type_trait; + switch( dtype ) + { + case NPY_INT8: + { + [[maybe_unused]] int retval8 = is_member_for_type( + arraySize1, reinterpret_cast(pDataIn1), arraySize2, reinterpret_cast(pDataIn2), + reinterpret_cast(PyArray_BYTES(indexArray)), pDataOut1, variant, std::make_index_sequence>{}); + } + break; + case NPY_INT16: + { + [[maybe_unused]] int retval16 = is_member_for_type( + arraySize1, reinterpret_cast(pDataIn1), arraySize2, reinterpret_cast(pDataIn2), + reinterpret_cast(PyArray_BYTES(indexArray)), pDataOut1, variant, std::make_index_sequence>{}); + } + break; + case NPY_INT32: + { + [[maybe_unused]] int retval32 = is_member_for_type( + arraySize1, reinterpret_cast(pDataIn1), arraySize2, reinterpret_cast(pDataIn2), + reinterpret_cast(PyArray_BYTES(indexArray)), pDataOut1, variant, std::make_index_sequence>{}); + } + break; + default: + { + [[maybe_unused]] int retval64 = is_member_for_type( + arraySize1, reinterpret_cast(pDataIn1), arraySize2, reinterpret_cast(pDataIn2), + reinterpret_cast(PyArray_BYTES(indexArray)), pDataOut1, variant, std::make_index_sequence>{}); + } + break; + } + } } else { - dtype = NPY_INT64; - } + if (arrayType1 == NPY_FLOAT32 || arrayType1 == NPY_FLOAT64) + { + LOGGING("Calling float!\n"); + sizeType1 += 100; + } - indexArray = AllocateLikeNumpyArray(inArr1, dtype); + int dtype = NPY_INT8; - // make sure allocation succeeded - if (indexArray) - { - void * pDataOut2 = PyArray_BYTES(indexArray); - switch (dtype) + if (arraySize2 < 100) { - case NPY_INT8: - IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int8_t *)pDataOut2, pDataOut1, - sizeType1, HASH_MODE(hashMode), hintSize); - break; - case NPY_INT16: - IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int16_t *)pDataOut2, pDataOut1, - sizeType1, HASH_MODE(hashMode), hintSize); - break; - CASE_NPY_INT32: - IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int32_t *)pDataOut2, pDataOut1, - sizeType1, HASH_MODE(hashMode), hintSize); - break; - CASE_NPY_INT64: - IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int64_t *)pDataOut2, pDataOut1, - sizeType1, HASH_MODE(hashMode), hintSize); - break; + dtype = NPY_INT8; + } + else if (arraySize2 < 30000) + { + dtype = NPY_INT16; + } + else if (arraySize2 < 2000000000) + { + dtype = NPY_INT32; + } + else + { + dtype = NPY_INT64; + } + + indexArray = AllocateLikeNumpyArray(inArr1, dtype); + + // make sure allocation succeeded + if (indexArray) + { + void * pDataOut2 = PyArray_BYTES(indexArray); + switch (dtype) + { + case NPY_INT8: + IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int8_t *)pDataOut2, pDataOut1, + sizeType1, HASH_MODE(hashMode), hintSize); + break; + case NPY_INT16: + IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int16_t *)pDataOut2, pDataOut1, + sizeType1, HASH_MODE(hashMode), hintSize); + break; + CASE_NPY_INT32: + IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int32_t *)pDataOut2, pDataOut1, + sizeType1, HASH_MODE(hashMode), hintSize); + break; + CASE_NPY_INT64: + IsMemberHash32(arraySize1, pDataIn1, arraySize2, pDataIn2, (int64_t *)pDataOut2, pDataOut1, + sizeType1, HASH_MODE(hashMode), hintSize); + break; + } } } } diff --git a/src/flat_hash_map.cpp b/src/flat_hash_map.cpp new file mode 100644 index 00000000..7daa967c --- /dev/null +++ b/src/flat_hash_map.cpp @@ -0,0 +1,48 @@ +#include "CommonInc.h" +#include "RipTide.h" +#include "MathWorker.h" +#include "Recycler.h" +#include "flat_hash_map.h" + +#include "absl/container/flat_hash_map.h" + +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; +template struct fhm_hasher; diff --git a/src/flat_hash_map.h b/src/flat_hash_map.h new file mode 100644 index 00000000..8d8cb787 --- /dev/null +++ b/src/flat_hash_map.h @@ -0,0 +1,200 @@ +#ifndef RIPTIDE_CPP_FLAT_HASH_MAP_H + #define RIPTIDE_CPP_FLAG_HASH_MAP_H + + #include "CommonInc.h" + #include "overload.h" + #include "absl/container/flat_hash_map.h" +#include "oneapi/tbb/concurrent_hash_map.h" +#include "oneapi/tbb/blocked_range.h" +#include "oneapi/tbb/parallel_for.h" + + #include + #include + #include + #include +#include +#include + + #if defined(_WIN32) && ! defined(__GNUC__) + #define dll_export __declspec(dllexport) + #else + #define dll_export + #endif + +namespace +{ + using is_member_allowed_types_t = + std::variant; + + template< typename KeyT > + struct hash_function + { + size_t const operator()(KeyT const & key) const + { + if constexpr( std::is_integral_v) + { + return static_cast(key) % 3145739; + } + else + { + size_t retval{}; + if constexpr( sizeof( KeyT ) > sizeof(size_t)) + { + memcpy( &retval, &key, sizeof(size_t) ); + return retval % 3145739; + } + else + { + memcpy(&retval, &key, sizeof(KeyT)); + return retval % 3245739; + } + } + } + }; + + std::vector< char > backing(128ULL * 1024ULL * 1024ULL); +} + +template +struct type_deducer; + + +template +struct fhm_hasher +{ +// std::pmr::monotonic_buffer_resource mono{ backing.data(), backing.capacity() }; +// std::pmr::unsynchronized_pool_resource pool{ &mono }; +// std::pmr::memory_resource * resource{&mono}; + + oneapi::tbb::concurrent_hash_map hasher; +// absl::flat_hash_map hasher{}; +// absl::flat_hash_map, +// absl::container_internal::hash_default_eq, +// std::pmr::polymorphic_allocator>> hasher{resource}; +// std::pmr::unordered_map> hasher{resource}; +// std::unordered_map hasher{}; + KeyT const * data_series_p{}; + + dll_export fhm_hasher() {} + + dll_export void make_hash(size_t array_size, char const * hash_list_p, size_t hint_size) + { + do_make_hash(array_size, reinterpret_cast(hash_list_p), hint_size); + } + + void do_make_hash(size_t array_size, KeyT const * hash_list_p, size_t hint_size) + { + if (not hint_size) + { + hint_size = array_size; + } + +// hasher.reserve(hint_size); + + data_series_p = hash_list_p; + + for (IndexT i{ 0 }; i != hint_size; ++i) + { +// hasher.emplace(hash_list_p[i], i); + typename oneapi::tbb::concurrent_hash_map::accessor a; + hasher.insert(a, hash_list_p[i]); + a->second = i; + } + } + + int64_t find(KeyT const * key) const noexcept + { + typename oneapi::tbb::concurrent_hash_map::const_accessor finder; + bool found = hasher.find(finder, *key); + // bool found = hasher.contains(*key); + // bool found = hasher.find(*key) != hasher.end(); + + //return found ? hasher.at(*key) : -1ll; + return found ? finder->second : -1ll; + } +}; + +template +struct is_member_check +{ + fhm_hasher mutable hash{}; + + int operator()(size_t needles_size, char const * needles_p, size_t haystack_size, char const * haystack_p, IndexT * output_p, + int8_t * bool_out_p) + { + KeyT const * typed_needles_p{ reinterpret_cast(needles_p) }; + + hash.make_hash(haystack_size, haystack_p, 0); + + for (ptrdiff_t elem{ 0 }; elem != needles_size; ++elem) + { + IndexT found_at = static_cast(hash.find(typed_needles_p + elem)); + if (found_at >= 0) + { + *(output_p + elem) = found_at; + *(bool_out_p + elem) = 1; + } + else + { + *(output_p + elem) = std::numeric_limits::min(); + *(bool_out_p + elem) = 0; + } + } + return 0; + } +}; + +template +dll_export inline int is_member(size_t needles_size, char const * needles_p, size_t haystack_size, char const * haystack_p, + out_t * output_p, int8_t * bool_out_p, is_member_allowed_types_t sample_value) +{ + auto hasher_calls = overload{ + [=](uint8_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](uint16_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](uint32_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](uint64_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](int8_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](int16_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](int32_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](int64_t) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](float) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + [=](double) -> int + { return is_member_check{}(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p); }, + }; + + return std::visit(hasher_calls, sample_value); +} + +template +inline int is_member_shim(size_t needles_size, char const * needles_p, size_t haystack_size, char const * haystack_p, + out_t * output_p, int8_t * bool_out_p, data_trait_t const * data_p) +{ + if ( data_p ) + { + using T = typename data_trait_t::data_type; + T const sample_value{}; + return is_member( needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p, sample_value ); + } + else + { + return 0; + } +} + +template< typename variant_t, typename out_t, size_t... Is> +dll_export inline int is_member_for_type( size_t needles_size, char const * needles_p, size_t haystack_size, char const * haystack_p, + out_t * output_p, int8_t * bool_out_p, variant_t data_type_traits, std::index_sequence) +{ + return (is_member_shim(needles_size, needles_p, haystack_size, haystack_p, output_p, bool_out_p, std::get_if(&data_type_traits)) + ...); +} +#endif diff --git a/src/one_input.cpp b/src/one_input.cpp index a7528527..b7303d6b 100644 --- a/src/one_input.cpp +++ b/src/one_input.cpp @@ -197,6 +197,9 @@ namespace riptable_cpp case MATH_OPERATION::SQRT: retval.first = sqrt_op{}; break; + default: + retval.first = none_op{}; + break; } return retval; diff --git a/src/one_input_impl.h b/src/one_input_impl.h index ee6bc1a8..e3b9f1d0 100644 --- a/src/one_input_impl.h +++ b/src/one_input_impl.h @@ -564,6 +564,14 @@ namespace riptable_cpp } } + template + decltype(auto) calculate(char const * in_p, none_op const * requested_op, calculation_t const * in_type, + wide_ops_t wide_ops) + { + using T = typename calculation_t::data_type const; + return T{}; + } + // numpy standard is to treat stride as bytes template void perform_operation(char const * in_p, char * out_p, ptrdiff_t & starting_element, int64_t const in_array_stride, diff --git a/src/operation_traits.h b/src/operation_traits.h index 2bd0421a..aa2844e8 100644 --- a/src/operation_traits.h +++ b/src/operation_traits.h @@ -191,11 +191,16 @@ namespace riptable_cpp static constexpr bool value_return = true; using simd_implementation = std::false_type; }; + struct none_op + { + static constexpr bool value_return = true; + using simd_implementation = std::false_type; + }; using operation_t = std::variant; + exp_op, exp2_op, cbrt_op, tan_op, cos_op, sin_op, signbit_op, none_op>; } // namespace riptable_cpp diff --git a/src/overload.h b/src/overload.h new file mode 100644 index 00000000..67a4fb9a --- /dev/null +++ b/src/overload.h @@ -0,0 +1,19 @@ +#ifndef RIPTIDECPP_OVERLOAD_H +#define RIPTIDECPP_OVERLOAD_H + +template +struct overload : Fs... +{ + template + overload(Ts &&... ts) + : Fs{ std::forward(ts) }... + { + } + + using Fs::operator()...; +}; + +template +overload(Ts &&...) -> overload...>; + +#endif diff --git a/test/riptide_python_test/CMakeLists.txt b/test/riptide_python_test/CMakeLists.txt index 4b57b4a5..3519a2d7 100644 --- a/test/riptide_python_test/CMakeLists.txt +++ b/test/riptide_python_test/CMakeLists.txt @@ -73,4 +73,5 @@ endif() target_link_libraries(${TARGET_NAME} Python3::Python - riptide_cpp) + riptide_cpp + ut) diff --git a/test/riptide_python_test/hash_linear_tests.cpp b/test/riptide_python_test/hash_linear_tests.cpp index c1f0e236..4699f28b 100644 --- a/test/riptide_python_test/hash_linear_tests.cpp +++ b/test/riptide_python_test/hash_linear_tests.cpp @@ -3,7 +3,7 @@ #include "HashLinear.h" #define BOOST_UT_DISABLE_MODULE -#include "../ut/include/boost/ut.hpp" +#include "boost/ut.hpp" #include #include @@ -25,7 +25,7 @@ namespace { "ismember64_uint64_too_many_uniques"_test = [&] { - std::vector haystack(2 * 128 * 1024ULL * 1024ULL); + std::vector haystack(128ULL * 1024ULL * 1024ULL); std::vector needles(1024ULL * 1024ULL); std::random_device dev{}; std::mt19937 engine(dev()); diff --git a/test/riptide_test/CMakeLists.txt b/test/riptide_test/CMakeLists.txt index 2fa433b3..21699f06 100644 --- a/test/riptide_test/CMakeLists.txt +++ b/test/riptide_test/CMakeLists.txt @@ -36,6 +36,7 @@ set(TARGET_NAME riptide_test) set(CMAKE_VERBOSE_MAKEFILE on) set(SOURCES + flat_hash_map_tests.cpp main.cpp test_one_input.cpp) @@ -48,7 +49,17 @@ get_target_property(RT_SOURCE_DIR riptide_cpp SOURCE_DIR) target_include_directories(${TARGET_NAME} PRIVATE ${RT_SOURCE_DIR} ${Python3_INCLUDE_DIRS} - ${Python3_NumPy_INCLUDE_DIRS}) + ${Python3_NumPy_INCLUDE_DIRS} + ${ABSL_COMMON_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} - riptide_cpp) + riptide_cpp + ut) + +if(WIN32) + set(_TARGET_DIR $) + + add_custom_command(TARGET ${TARGET_NAME} POST_BUILD + COMMENT "Copying runtime dependencies from riptide_cpp to ${_TARGET_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${_TARGET_DIR}) +endif() diff --git a/test/riptide_test/flat_hash_map_tests.cpp b/test/riptide_test/flat_hash_map_tests.cpp new file mode 100644 index 00000000..6b55625a --- /dev/null +++ b/test/riptide_test/flat_hash_map_tests.cpp @@ -0,0 +1,146 @@ +#include "flat_hash_map.h" + +#define BOOST_UT_DISABLE_MODULE +#include "boost/ut.hpp" + +#include +#include +#include +#include +#include +#include + +using namespace boost::ut; +using boost::ut::suite; + +namespace +{ + std::array output; + std::array bools; + std::random_device dev{}; + std::mt19937 engine{ dev() }; + + suite flat_hash_map_ops = [] + { + "make_hash"_test = [&](DataT arg) + { + boost::ut::log << "Data Type: " << reflection::type_name() << " "; + std::array data{}; + if constexpr (std::is_floating_point_v) + { + std::uniform_real_distribution dist(0, std::numeric_limits::max()); + std::generate(std::begin(data), std::end(data), [&] { return dist(engine); }); + } + else + { + std::uniform_int_distribution dist(0, std::numeric_limits::max()); + std::generate(std::begin(data), std::end(data), [&] { return dist(engine); }); + } + + fhm_hasher hash{}; + + hash.make_hash(data.size(), reinterpret_cast(data.data()), 0); + + if constexpr (not std::is_same_v>) + { + expect(hash.hasher.find(data[0])->second == 0u); + expect(hash.hasher.find(data[1])->second == 1u); + expect(hash.hasher.find(data[2])->second == 2u); + expect(hash.hasher.find(data[3])->second == 3u); + expect(hash.hasher.find(data[4])->second == 4u); + expect(hash.hasher.find(data[5])->second == 5u); + expect(hash.hasher.find(data[6])->second == 6u); + expect(hash.hasher.find(data[7])->second == 7u); + expect(hash.hasher.find(data[8])->second == 8u); + expect(hash.hasher.find(data[9])->second == 9u); + expect(hash.hasher.find(data[10])->second == 10u); + expect(hash.hasher.find(data[11])->second == 11u); + expect(hash.hasher.find(data[12])->second == 12u); + expect(hash.hasher.find(data[13])->second == 13u); + expect(hash.hasher.find(data[14])->second == 14u); + expect(hash.hasher.find(data[15])->second == 15u); + expect(hash.hasher.find(data[16])->second == 16u); + expect(hash.hasher.find(data[17])->second == 17u); + } + else + { + using const_acc = typename oneapi::tbb::concurrent_hash_map::const_accessor; + const oneapi::tbb::concurrent_hash_map & const_map = hash.hasher; + const_acc ca{}; + expect(hash.hasher.find(ca, data[0])); + expect(ca->second == 0_i); + expect(hash.hasher.find(ca, data[1])); + expect(ca->second == 1_i); + expect(hash.hasher.find(ca, data[2])); + expect(ca->second == 2_i); + expect(hash.hasher.find(ca, data[3])); + expect(ca->second == 3_i); + expect(hash.hasher.find(ca, data[4])); + expect(ca->second == 4_i); + expect(hash.hasher.find(ca, data[5])); + expect(ca->second == 5_i); + expect(hash.hasher.find(ca, data[6])); + expect(ca->second == 6_i); + expect(hash.hasher.find(ca, data[7])); + expect(ca->second == 7_i); + expect(hash.hasher.find(ca, data[8])); + expect(ca->second == 8_i); + expect(hash.hasher.find(ca, data[9])); + expect(ca->second == 9_i); + expect(hash.hasher.find(ca, data[10])); + expect(ca->second == 10_i); + expect(hash.hasher.find(ca, data[11])); + expect(ca->second == 11_i); + expect(hash.hasher.find(ca, data[12])); + expect(ca->second == 12_i); + expect(hash.hasher.find(ca, data[13])); + expect(ca->second == 13_i); + expect(hash.hasher.find(ca, data[14])); + expect(ca->second == 14_i); + expect(hash.hasher.find(ca, data[15])); + expect(ca->second == 15_i); + expect(hash.hasher.find(ca, data[16])); + expect(ca->second == 16_i); + expect(hash.hasher.find(ca, data[17])); + expect(ca->second == 17_i); + } + } | std::tuple{}; + + "is_member_uint64"_test = [&] + { + std::vector haystack(10); + std::uniform_int_distribution dist(0, ULLONG_MAX - 2); + + std::generate(std::begin(haystack), std::end(haystack), [&] { return dist(engine); }); + std::vector needles{ haystack[3], haystack[9], haystack[0], haystack[5], ULLONG_MAX - 1 }; + + expect(is_member(needles.size(), reinterpret_cast(needles.data()), haystack.size(), + reinterpret_cast(haystack.data()), output.data(), bools.data(), needles[0]) == 0_i); + + expect( output[ 0 ] == 3_i ); + expect( output[ 1 ] == 9_i ); + expect( output[ 2 ] == 0_i ); + expect( output[ 3 ] == 5_i ); + expect( output[ 4 ] < -9'223'372'036'854'775'807_ll ); + + expect( bools[ 0 ] == 1_i ); + expect( bools[ 1 ] == 1_i ); + expect( bools[ 2 ] == 1_i ); + expect( bools[ 3 ] == 1_i ); + expect( bools[ 4 ] == 0_i ); + }; + + "is_member_many_uniques"_test = [&] + { + std::vector haystack(128 * 1024ULL * 1024ULL); + std::vector needles(1024ULL * 1024ULL); + std::uniform_int_distribution dist(3002950000, haystack.size() + 3002950000); + + std::iota(std::begin(haystack), std::end(haystack), 3002954000); + std::generate(std::begin(needles), std::end(needles), [&] { return dist(engine); }); + + expect(is_member(needles.size(), reinterpret_cast(needles.data()), haystack.size(), + reinterpret_cast(haystack.data()), output.data(), bools.data(), needles[0]) == 0_i); + }; + }; +}