Browse Source

feat: parallel test helper

master
Dnomd343 2 months ago
parent
commit
4fdcaaa2d5
  1. 5
      src/core_test/CMakeLists.txt
  2. 15
      src/core_test/codec/common_code.cc
  3. 91
      src/core_test/codec/helper/codec.cc
  4. 66
      src/core_test/codec/helper/codec.h
  5. 15
      src/core_test/codec/raw_code.cc
  6. 15
      src/core_test/codec/short_code.cc
  7. 0
      src/core_test/codec/test_samples.h
  8. 18
      src/core_test/helper/expect.h
  9. 4
      src/core_test/helper/internal/parallel.cc
  10. 20
      src/core_test/helper/internal/parallel.inl
  11. 3
      src/core_test/helper/mirror.h
  12. 34
      src/core_test/helper/parallel.h

5
src/core_test/CMakeLists.txt

@ -13,7 +13,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# ------------------------------------------------------------------------------------ # # ------------------------------------------------------------------------------------ #
add_library(test_helper add_library(test_helper
helper/internal/impl.cc helper/internal/parallel.cc
) )
target_link_libraries(test_helper PRIVATE klotski_core bs::thread_pool) target_link_libraries(test_helper PRIVATE klotski_core bs::thread_pool)
@ -50,11 +50,10 @@ set(KLSK_TEST_CODEC_SRC
codec/raw_code.cc codec/raw_code.cc
codec/short_code.cc codec/short_code.cc
codec/common_code.cc codec/common_code.cc
codec/helper/codec.cc
) )
add_executable(test_klotski_codec ${KLSK_TEST_CODEC_SRC}) add_executable(test_klotski_codec ${KLSK_TEST_CODEC_SRC})
target_link_libraries(test_klotski_codec PRIVATE ${KLSK_TEST_DEPS}) target_link_libraries(test_klotski_codec PRIVATE ${KLSK_TEST_DEPS} test_helper)
add_test(NAME klotski_codec COMMAND test_klotski_codec) add_test(NAME klotski_codec COMMAND test_klotski_codec)
# ------------------------------------------------------------------------------------ # # ------------------------------------------------------------------------------------ #

15
src/core_test/codec/common_code.cc

@ -1,9 +1,10 @@
#include <algorithm> #include <algorithm>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <BS_thread_pool.hpp>
#include "helper/codec.h" #include "test_samples.h"
#include "helper/sample.h" #include "helper/expect.h"
#include "helper/parallel.h"
#include "raw_code/raw_code.h" #include "raw_code/raw_code.h"
#include "all_cases/all_cases.h" #include "all_cases/all_cases.h"
#include "short_code/short_code.h" #include "short_code/short_code.h"
@ -197,14 +198,14 @@ TEST(CommonCode, code_string) {
} }
TEST(CommonCode, DISABLED_global_verify) { TEST(CommonCode, DISABLED_global_verify) {
const auto result = parallel_spawn(0x10'0000'0000ULL, [](uint64_t start, uint64_t end) { const auto result = SCOPE_PARALLEL(0x10'0000'0000ULL, [](uint64_t start, uint64_t end) {
std::vector<uint64_t> codes; std::vector<CommonCode> codes;
for (uint64_t common_code = start; common_code < end; ++common_code) { // brute-force search for (uint64_t common_code = start; common_code < end; ++common_code) { // brute-force search
if (CommonCode::check(common_code)) { if (CommonCode::check(common_code)) {
codes.emplace_back(common_code); // found valid common code codes.emplace_back(CommonCode::unsafe_create(common_code)); // found valid common code
} }
} }
return codes; return codes;
}); });
EXPECT_EQ(result, all_common_codes()); EXPECT_EQ(result, AllCases::instance().fetch().codes());
} }

91
src/core_test/codec/helper/codec.cc

@ -1,91 +0,0 @@
#include <BS_thread_pool.hpp>
#include "codec.h"
using klotski::cases::AllCases;
using klotski::cases::ALL_CASES_NUM_;
void head_parallel(std::function<void(uint64_t head)> &&func) {
constexpr auto heads = std::to_array({
0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14
});
BS::thread_pool pool;
for (auto head : heads) {
pool.detach_task([head, &func] {
func(head);
});
}
pool.wait();
}
std::vector<uint64_t> all_common_codes() {
// TODO: using `std::ranges`
std::vector<uint64_t> common_codes;
common_codes.reserve(ALL_CASES_NUM_);
for (uint64_t head = 0; head < 16; ++head) {
for (auto range : AllCases::instance().fetch()[head]) {
common_codes.emplace_back(head << 32 | range);
}
}
return common_codes;
}
void common_code_parallel(std::function<void(std::span<CommonCode>)> &&func) {
static auto codes = AllCases::instance().fetch().codes();
BS::thread_pool pool;
// TODO: enhance performance
pool.detach_blocks((uint64_t)0, codes.size(), [func = std::move(func)](auto start, auto end) {
func(std::span<CommonCode> {codes.data() + start, end - start});
}, 16);
pool.wait();
}
static std::vector<RawCode> convert(const std::vector<CommonCode> &codes) {
std::vector<RawCode> result;
result.reserve(29334498);
for (auto code : codes) {
result.emplace_back(RawCode::from_common_code(code));
}
return result;
}
void raw_code_parallel(std::function<void(std::span<RawCode>)> &&func) {
static auto codes = convert(AllCases::instance().fetch().codes());
BS::thread_pool pool;
pool.detach_blocks((uint64_t)0, codes.size(), [func = std::move(func)](auto start, auto end) {
func(std::span<RawCode> {codes.data() + start, end - start});
}, 16);
pool.wait();
}
void short_code_parallel(std::function<void(std::span<ShortCode>)> &&func) {
static auto codes = []() {
std::vector<uint32_t> v (klotski::codec::SHORT_CODE_LIMIT);
std::iota(v.begin(), v.end(), 0);
return v;
}();
BS::thread_pool pool;
pool.detach_blocks((uint64_t)0, codes.size(), [func = std::move(func)](auto start, auto end) {
auto span = std::span<uint32_t> {codes.data() + start, end - start};
func(std::bit_cast<std::span<ShortCode>>(span));
}, 16);
pool.wait();
}

66
src/core_test/codec/helper/codec.h

@ -1,66 +0,0 @@
#pragma once
#include <span>
#include <functional>
#include "raw_code/raw_code.h"
#include "all_cases/all_cases.h"
#include "short_code/short_code.h"
#include "common_code/common_code.h"
using klotski::codec::RawCode;
using klotski::codec::ShortCode;
using klotski::codec::CommonCode;
/// Build all valid CommonCodes.
std::vector<uint64_t> all_common_codes();
// ----------------------------------------------------------------------------------------- //
/// Capture ostream output as string.
template <typename T>
std::string ostream_capture(T obj) {
std::ostringstream out;
out << obj; // ostream capture
return out.str();
}
#define EXPECT_OSTREAM(obj, expect) EXPECT_EQ(ostream_capture(obj), expect)
// ----------------------------------------------------------------------------------------- //
/// Spawn all valid RawCodes in parallel.
void raw_code_parallel(std::function<void(std::span<RawCode>)> &&func);
/// Spawn all valid ShortCodes in parallel.
void short_code_parallel(std::function<void(std::span<ShortCode>)> &&func);
/// Spawn all valid CommonCodes in parallel.
void common_code_parallel(std::function<void(std::span<CommonCode>)> &&func);
#define CODE_PARALLEL(Type, type, impl) \
type##_code_parallel([](std::span<Type##Code> codes) { \
for (auto code : codes) {impl} \
})
#define RAW_CODE_PARALLEL(impl) CODE_PARALLEL(Raw, raw, impl)
#define SHORT_CODE_PARALLEL(impl) CODE_PARALLEL(Short, short, impl)
#define COMMON_CODE_PARALLEL(impl) CODE_PARALLEL(Common, common, impl)
// ----------------------------------------------------------------------------------------- //
/// Calculate multiple ranges separately and combine the results.
template<typename T, typename F>
requires std::is_integral_v<T> && std::is_invocable_v<F, T, T>
auto parallel_spawn(T limit, F &&func) -> std::invoke_result_t<F, T, T> {
BS::thread_pool pool;
std::invoke_result_t<F, T, T> result;
for (auto &&future : pool.submit_blocks((T)0, limit, func, 0x1000)) {
const auto data = future.get();
result.insert(result.end(), std::begin(data), std::end(data)); // combine sections
}
pool.wait();
return result;
}
// ----------------------------------------------------------------------------------------- //

15
src/core_test/codec/raw_code.cc

@ -1,9 +1,10 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <BS_thread_pool.hpp>
#include "test_samples.h"
#include "helper/expect.h"
#include "helper/parallel.h"
#include "utils/common.h" #include "utils/common.h"
#include "helper/codec.h"
#include "helper/sample.h"
#include "raw_code/raw_code.h" #include "raw_code/raw_code.h"
#include "all_cases/all_cases.h" #include "all_cases/all_cases.h"
#include "common_code/common_code.h" #include "common_code/common_code.h"
@ -166,14 +167,14 @@ TEST(RawCode, DISABLED_global_verify) {
return raw_code; return raw_code;
}; };
const auto result = parallel_spawn(0x10'0000'0000ULL, [](uint64_t start, uint64_t end) { const auto result = SCOPE_PARALLEL(0x10'0000'0000ULL, [](uint64_t start, uint64_t end) {
std::vector<uint64_t> codes; std::vector<CommonCode> codes;
for (uint64_t common_code = start; common_code < end; ++common_code) { for (uint64_t common_code = start; common_code < end; ++common_code) {
if (RawCode::check(force_convert(common_code))) { if (RawCode::check(force_convert(common_code))) {
codes.emplace_back(common_code); // store valid code codes.emplace_back(CommonCode::unsafe_create(common_code)); // store valid code
} }
} }
return codes; return codes;
}); });
EXPECT_EQ(result, all_common_codes()); EXPECT_EQ(result, AllCases::instance().fetch().codes());
} }

15
src/core_test/codec/short_code.cc

@ -1,11 +1,12 @@
#include <algorithm> #include <algorithm>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <BS_thread_pool.hpp>
#include "helper/codec.h" #include "test_samples.h"
#include "helper/sample.h" #include "helper/expect.h"
#include "helper/parallel.h"
#include "utility/exposer.h" #include "utility/exposer.h"
#include "utility/concurrent.h" #include "utility/concurrent.h"
#include "all_cases/all_cases.h" #include "all_cases/all_cases.h"
#include "short_code/short_code.h" #include "short_code/short_code.h"
#include "common_code/common_code.h" #include "common_code/common_code.h"
@ -221,15 +222,15 @@ TEST(ShortCode, code_string) {
TEST(ShortCode, DISABLED_global_verify) { TEST(ShortCode, DISABLED_global_verify) {
speed_up_reset(); speed_up_reset();
const auto result = parallel_spawn(SHORT_CODE_LIMIT, [](uint32_t start, uint32_t end) { const auto result = SCOPE_PARALLEL(SHORT_CODE_LIMIT, [](uint32_t start, uint32_t end) {
std::vector<uint64_t> codes; std::vector<CommonCode> codes;
codes.reserve(end - start); codes.reserve(end - start);
for (uint32_t short_code = start; short_code < end; ++short_code) { for (uint32_t short_code = start; short_code < end; ++short_code) {
auto common_code = CommonCode::from_short_code(short_code).value(); // ShortCode::tiny_decode auto common_code = CommonCode::from_short_code(short_code).value(); // ShortCode::tiny_decode
EXPECT_EQ(common_code.to_short_code(), short_code); // ShortCode::tiny_encode EXPECT_EQ(common_code.to_short_code(), short_code); // ShortCode::tiny_encode
codes.emplace_back(common_code.unwrap()); codes.emplace_back(common_code);
} }
return codes; return codes;
}); });
EXPECT_EQ(result, all_common_codes()); EXPECT_EQ(result, AllCases::instance().fetch().codes());
} }

0
src/core_test/codec/helper/sample.h → src/core_test/codec/test_samples.h

18
src/core_test/helper/expect.h

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <gtest/gtest.h>
// ----------------------------------------------------------------------------------------- //
/// Capture ostream output as string.
template <typename T>
std::string ostream_capture(T obj) {
std::ostringstream out;
out << obj; // ostream capture
return out.str();
}
#define EXPECT_OSTREAM(obj, expect) EXPECT_EQ(ostream_capture(obj), expect)
// ----------------------------------------------------------------------------------------- //

4
src/core_test/helper/internal/impl.cc → src/core_test/helper/internal/parallel.cc

@ -10,6 +10,10 @@ using klotski::cases::AllCases;
using klotski::cases::TYPE_ID_LIMIT; using klotski::cases::TYPE_ID_LIMIT;
using klotski::cases::ALL_CASES_NUM_; using klotski::cases::ALL_CASES_NUM_;
void helper::group_parallel(std::function<void(Group)> &&func) {
// TODO: spawn all Groups
}
void helper::type_id_parallel(std::function<void(uint32_t type_id)> &&func) { void helper::type_id_parallel(std::function<void(uint32_t type_id)> &&func) {
BS::thread_pool pool; BS::thread_pool pool;

20
src/core_test/helper/internal/parallel.inl

@ -0,0 +1,20 @@
#pragma once
#include <BS_thread_pool.hpp>
namespace helper {
template<typename T, typename F>
requires std::is_integral_v<T> && std::is_invocable_v<F, T, T>
auto scope_parallel(T limit, F &&func) -> std::invoke_result_t<F, T, T> {
BS::thread_pool pool;
std::invoke_result_t<F, T, T> result;
for (auto &&future : pool.submit_blocks((T)0, limit, func, 0x1000)) {
const auto data = future.get();
result.insert(result.end(), std::begin(data), std::end(data)); // combine sections
}
pool.wait();
return result;
}
} // namespace helper

3
src/core_test/helper/mirror.h

@ -0,0 +1,3 @@
#pragma once
// TODO: mirror convert of RawCode

34
src/core_test/helper/parallel.h

@ -1,30 +1,42 @@
#pragma once #pragma once
#include <span> #include <span>
#include <cstdint>
#include <functional>
#include "group/group.h"
#include "raw_code/raw_code.h" #include "raw_code/raw_code.h"
#include "short_code/short_code.h" #include "short_code/short_code.h"
#include "common_code/common_code.h" #include "common_code/common_code.h"
using klotski::cases::Group;
using klotski::cases::GroupUnion;
using klotski::codec::RawCode; using klotski::codec::RawCode;
using klotski::codec::ShortCode; using klotski::codec::ShortCode;
using klotski::codec::CommonCode; using klotski::codec::CommonCode;
using klotski::cases::GroupUnion;
namespace helper { namespace helper {
// ----------------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------------- //
/// Spawn all valid Groups in parallel.
void group_parallel(std::function<void(Group group)> &&func);
/// Spawn all valid type_ids in parallel.
void type_id_parallel(std::function<void(uint32_t type_id)> &&func); void type_id_parallel(std::function<void(uint32_t type_id)> &&func);
/// Spawn all valid GroupUnions in parallel.
void group_union_parallel(std::function<void(GroupUnion group_union)> &&func); void group_union_parallel(std::function<void(GroupUnion group_union)> &&func);
#define GROUP_PARALLEL(impl) \
::helper::group_parallel([](Group group) {impl})
#define TYPE_ID_PARALLEL(impl) \ #define TYPE_ID_PARALLEL(impl) \
helper::type_id_parallel([](uint32_t type_id) {impl}) ::helper::type_id_parallel([](uint32_t type_id) {impl})
#define GROUP_UNION_PARALLEL(impl) \ #define GROUP_UNION_PARALLEL(impl) \
helper::group_union_parallel([](GroupUnion group_union) {impl}) ::helper::group_union_parallel([](GroupUnion group_union) {impl})
// ----------------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------------- //
@ -38,7 +50,7 @@ void short_code_parallel(std::function<void(std::span<ShortCode>)> &&func);
void common_code_parallel(std::function<void(std::span<CommonCode>)> &&func); void common_code_parallel(std::function<void(std::span<CommonCode>)> &&func);
#define CODE_PARALLEL(Type, type, impl) \ #define CODE_PARALLEL(Type, type, impl) \
helper::type##_code_parallel([](std::span<Type##Code> codes) { \ ::helper::type##_code_parallel([](std::span<Type##Code> codes) { \
for (auto code : codes) {impl} \ for (auto code : codes) {impl} \
}) })
@ -48,4 +60,16 @@ void common_code_parallel(std::function<void(std::span<CommonCode>)> &&func);
// ----------------------------------------------------------------------------------------- // // ----------------------------------------------------------------------------------------- //
/// Calculate multiple scopes separately and combine the results.
template<typename T, typename F>
requires std::is_integral_v<T> && std::is_invocable_v<F, T, T> // func(start, end)
auto scope_parallel(T limit, F &&func) -> std::invoke_result_t<F, T, T>;
#define SCOPE_PARALLEL(limit, impl) \
::helper::scope_parallel(limit, impl)
// ----------------------------------------------------------------------------------------- //
} // namespace helper } // namespace helper
#include "internal/parallel.inl"

Loading…
Cancel
Save