// MIT License // // Copyright (c) 2024-2026 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #pragma once #include "primbench.hpp" #include "benchmark_utils.hpp" #include "../common/utils_device_ptr.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BENCHMARK_CONFIG_TUNING #include #endif template constexpr auto config_name() { if constexpr(std::is_same_v) { return std::string("default"); } else { auto config = Config(); return primbench::json{} .add("bs", config.kernel_config.block_size) .add("ipt", config.kernel_config.items_per_thread); } } template struct device_merge_benchmark : public primbench::benchmark_interface { primbench::json meta() const override { return primbench::json{} .add("lvl", "device") .add("algo", "device_merge") .add("key_type", primbench::name()) .add("value_type", primbench::name()) .add("cfg", config_name()); } void run(primbench::state& state) override { do_run(std::forward(state)); } private: template auto do_run(primbench::state&& state) const { const auto& stream = state.stream; const auto& bytes = state.size; const auto& seed = state.seed; auto seeds = primbench::seeds<2>(seed); using key_type = KeyType; using value_type = ValueType; using compare_op_type = typename std::conditional::value, half_less, rocprim::less>::type; size_t items = bytes / sizeof(key_type); const size_t size1 = items / 2; const size_t size2 = items - size1; compare_op_type compare_op; // Generate data primbench::log("Generating random_range"); const auto random_range = limit_random_range(0, items); primbench::log("Generating keys_input1"); std::vector keys_input1 = get_random_data(size1, random_range.first, random_range.second, seeds[0]); primbench::log("Generating keys_input2"); std::vector keys_input2 = get_random_data(size2, random_range.first, random_range.second, seeds[1]); primbench::log("Sorting keys_input1"); std::sort(keys_input1.begin(), keys_input1.end(), compare_op); primbench::log("Sorting keys_input2"); std::sort(keys_input2.begin(), keys_input2.end(), compare_op); std::vector values_input1; std::vector values_input2; if constexpr(!std::is_same::value) { primbench::log("Allocating values_input1"); values_input1 = std::vector(size1); primbench::log("Allocating values_input2"); values_input2 = std::vector(size2); primbench::log("Filling values_input1"); std::iota(values_input1.begin(), values_input1.end(), 0); primbench::log("Filling values_input2"); std::iota(values_input2.begin(), values_input2.end(), size1); } primbench::log("Creating device pointers"); common::device_ptr d_keys_input1(keys_input1); common::device_ptr d_keys_input2(keys_input2); common::device_ptr d_keys_output(items); common::device_ptr d_values_output(items); common::device_ptr d_values_input1; common::device_ptr d_values_input2; if constexpr(!std::is_same::value) { d_values_input1 = common::device_ptr{values_input1}; d_values_input2 = common::device_ptr{values_input2}; } primbench::log("Calculating d_temporary_storage size"); common::device_ptr d_temporary_storage; size_t temporary_storage_bytes = 0; if constexpr(!std::is_same::value) { HIP_CHECK(rocprim::merge(d_temporary_storage.get(), temporary_storage_bytes, d_keys_input1.get(), d_keys_input2.get(), d_keys_output.get(), d_values_input1.get(), d_values_input2.get(), d_values_output.get(), size1, size2, compare_op, stream, false)); } else { HIP_CHECK(rocprim::merge(d_temporary_storage.get(), temporary_storage_bytes, d_keys_input1.get(), d_keys_input2.get(), d_keys_output.get(), size1, size2, compare_op, stream, false)); } primbench::log("Resizing d_temporary_storage"); d_temporary_storage.resize(temporary_storage_bytes); state.set_items(items); state.add_reads(items); if constexpr(!std::is_same::value) { state.add_reads(items); state.run( [&] { HIP_CHECK(rocprim::merge(d_temporary_storage.get(), temporary_storage_bytes, d_keys_input1.get(), d_keys_input2.get(), d_keys_output.get(), d_values_input1.get(), d_values_input2.get(), d_values_output.get(), size1, size2, compare_op, stream, false)); }); } else { state.run( [&] { HIP_CHECK(rocprim::merge(d_temporary_storage.get(), temporary_storage_bytes, d_keys_input1.get(), d_keys_input2.get(), d_keys_output.get(), size1, size2, compare_op, stream, false)); }); } } }; #ifdef BENCHMARK_CONFIG_TUNING template struct device_merge_benchmark_generator { template struct create_ipt { static constexpr unsigned int items_per_thread = 1u << ItemsPerThreadExponent; using generated_config = rocprim::merge_config; using benchmark_struct = device_merge_benchmark; void operator()(std::vector>& storage) { storage.emplace_back(std::make_unique()); } }; struct create_default_config { using benchmark_struct = device_merge_benchmark; void operator()(std::vector>& storage) { storage.emplace_back(std::make_unique()); } }; static void create(std::vector>& storage) { static constexpr unsigned int min_items_per_thread_exponent = 0u; // Very large block sizes don't work with large items_per_thread since // shared memory is limited static constexpr unsigned int max_shared_memory = TUNING_SHARED_MEMORY_MAX; static constexpr unsigned int max_size_per_element = sizeof(KeyType) + sizeof(ValueType); static constexpr unsigned int max_items_per_thread = max_shared_memory / (BlockSize * max_size_per_element); static constexpr unsigned int max_items_per_thread_exponent = rocprim::Log2::VALUE - 1; create_default_config()(storage); static_for_each, create_ipt>(storage); } }; #endif // BENCHMARK_CONFIG_TUNING