Browse Source

perf: update Range module

master
Dnomd343 1 month ago
parent
commit
8686e25bf2
  1. 2
      src/core/CMakeLists.txt
  2. 5
      src/core/all_cases/all_cases.h
  3. 20
      src/core/all_cases/internal/all_cases.cc
  4. 12
      src/core/all_cases/internal/basic_ranges.cc
  5. 39
      src/core/benchmark/group.cc
  6. 8
      src/core/main.cc
  7. 48
      src/core/ranges/internal/derive.cc
  8. 57
      src/core/ranges/internal/head.cc
  9. 60
      src/core/ranges/internal/ranges.cc
  10. 70
      src/core/ranges/ranges.h
  11. 2
      src/core/utils/utility.h
  12. 2
      src/core_test/cases/all_cases.cc
  13. 6
      src/core_test/cases/basic_ranges.cc

2
src/core/CMakeLists.txt

@ -25,7 +25,7 @@ set(KLOTSKI_CORE_SRC
group/internal/group.cc group/internal/group.cc
ranges/internal/ranges.cc ranges/internal/ranges.cc
ranges/internal/head.cc ranges/internal/derive.cc
) )
add_library(klotski_core STATIC ${KLOTSKI_CORE_SRC}) add_library(klotski_core STATIC ${KLOTSKI_CORE_SRC})

5
src/core/all_cases/all_cases.h

@ -58,7 +58,7 @@ typedef std::function<void(std::function<void()>&&)> Executor;
// ------------------------------------------------------------------------------------- // // ------------------------------------------------------------------------------------- //
constexpr auto BASIC_RANGES_NUM = 7311921; constexpr auto BASIC_RANGES_NUM = 7311885;
constexpr std::array ALL_CASES_NUM { constexpr std::array ALL_CASES_NUM {
2942906, 2260392, 2942906, 0, 2942906, 2260392, 2942906, 0,
@ -124,9 +124,6 @@ private:
/// Get static singleton variable. /// Get static singleton variable.
static RangesUnion& get_cases(); static RangesUnion& get_cases();
/// Build all valid ranges of the specified head.
static void build_cases(int head, Ranges &release);
KLSK_INSTANCE(AllCases) KLSK_INSTANCE(AllCases)
}; };

20
src/core/all_cases/internal/all_cases.cc

@ -2,11 +2,14 @@
#include "all_cases/all_cases.h" #include "all_cases/all_cases.h"
using klotski::cases::Ranges;
using klotski::cases::AllCases; using klotski::cases::AllCases;
using klotski::cases::BasicRanges;
using klotski::cases::ALL_CASES_NUM;
/// Calculate all possible klotski heads. /// Generate all possible klotski heads.
consteval static std::array<int, 12> case_heads() { consteval static std::array<int, 12> heads() {
std::array<int, 12> heads = {}; std::array<int, 12> heads {};
for (int i = 0, head = 0; head < 15; ++head) { for (int i = 0, head = 0; head < 15; ++head) {
if (head % 4 != 3) { if (head % 4 != 3) {
heads[i++] = head; heads[i++] = head;
@ -15,10 +18,11 @@ consteval static std::array<int, 12> case_heads() {
return heads; return heads;
} }
void AllCases::build_cases(const int head, Ranges &release) { /// Build all valid ranges of the specified head.
static void build_cases(const int head, Ranges &release) {
release.clear(); release.clear();
release.reserve(ALL_CASES_NUM[head]); release.reserve(ALL_CASES_NUM[head]);
BasicRanges::instance().fetch().with_head(head, release); BasicRanges::instance().fetch().derive(head, release);
} }
void AllCases::build() { void AllCases::build() {
@ -36,7 +40,7 @@ void AllCases::build_parallel(Executor &&executor) {
return; // data is already available return; // data is already available
} }
std::vector<std::future<void>> futures; std::vector<std::future<void>> futures;
for (auto head : case_heads()) { for (auto head : heads()) {
auto promise = std::make_shared<std::promise<void>>(); auto promise = std::make_shared<std::promise<void>>();
futures.emplace_back(promise->get_future()); futures.emplace_back(promise->get_future());
executor([head, promise = std::move(promise)]() { executor([head, promise = std::move(promise)]() {
@ -63,10 +67,10 @@ void AllCases::build_parallel_async(Executor &&executor, Notifier &&callback) {
} }
auto counter = std::make_shared<std::atomic<int>>(0); auto counter = std::make_shared<std::atomic<int>>(0);
auto all_done = std::make_shared<Notifier>(std::move(callback)); auto all_done = std::make_shared<Notifier>(std::move(callback));
for (auto head : case_heads()) { for (auto head : heads()) {
executor([this, head, counter, all_done]() { executor([this, head, counter, all_done]() {
build_cases(head, get_cases()[head]); build_cases(head, get_cases()[head]);
if (counter->fetch_add(1) == case_heads().size() - 1) { // all tasks done if (counter->fetch_add(1) == heads().size() - 1) { // all tasks done
available_ = true; available_ = true;
building_.unlock(); // release building mutex building_.unlock(); // release building mutex
all_done->operator()(); // trigger callback all_done->operator()(); // trigger callback

12
src/core/all_cases/internal/basic_ranges.cc

@ -8,14 +8,16 @@ using klotski::cases::Ranges;
using klotski::cases::BasicRanges; using klotski::cases::BasicRanges;
typedef std::tuple<int, int, int> RangeType; typedef std::tuple<int, int, int> RangeType;
typedef std::array<RangeType, 204> RangeTypeUnion; typedef std::array<RangeType, 203> RangeTypeUnion;
/// Generate all possible basic-ranges permutations. /// Generate all possible basic-ranges permutations.
consteval static RangeTypeUnion range_types() { consteval static RangeTypeUnion range_types() {
RangeTypeUnion data; RangeTypeUnion data;
for (int i = 0, n = 0; n <= 7; ++n) { // 1x2 + 2x1 -> 0 ~ 7 for (int i = 0, n = 0; n <= 7; ++n) { // 1x2 + 2x1 -> 0 ~ 7
for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) { // 2x1 -> 0 ~ n for (int n_2x1 = 0; n_2x1 <= n; ++n_2x1) { // 2x1 -> 0 ~ n
// TODO: skip n == 7 && n_2x1 == 7 if (n == 7 && n_2x1 == 7) {
break;
}
for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) { // 1x1 -> 0 ~ (14 - 2n) for (int n_1x1 = 0; n_1x1 <= (14 - n * 2); ++n_1x1) { // 1x1 -> 0 ~ (14 - 2n)
data[i++] = {n, n_2x1, n_1x1}; data[i++] = {n, n_2x1, n_1x1};
} }
@ -48,7 +50,7 @@ void BasicRanges::build_ranges(Ranges &ranges) {
std::list flags { ranges.begin() }; std::list flags { ranges.begin() };
for (auto [n, n_2x1, n_1x1] : range_types()) { for (auto [n, n_2x1, n_1x1] : range_types()) {
ranges.spawn_more(n, n_2x1, n_1x1); ranges.spawn(n, n_2x1, n_1x1);
flags.emplace_back(ranges.end()); // mark ordered interval flags.emplace_back(ranges.end()); // mark ordered interval
} }
@ -61,5 +63,7 @@ void BasicRanges::build_ranges(Ranges &ranges) {
} }
} while (flags.size() > 2); // merge until only one interval remains } while (flags.size() > 2); // merge until only one interval remains
ranges.reverse(); // flip every 2-bit for (auto &x : ranges) {
x = range_reverse(x); // flip every 2-bit
}
} }

39
src/core/benchmark/group.cc

@ -16,7 +16,7 @@ using klotski::cases::AllCases;
static std::vector<uint64_t> all_common_codes() { static std::vector<uint64_t> all_common_codes() {
std::vector<uint64_t> codes; std::vector<uint64_t> codes;
for (uint64_t head = 0; head < 16; ++head) { for (uint64_t head = 0; head < 16; ++head) {
for (const auto range : AllCases::instance().fetch()[head].ranges_) { for (const auto range : AllCases::instance().fetch()[head]) {
codes.emplace_back(head << 32 | range); codes.emplace_back(head << 32 | range);
} }
} }
@ -159,7 +159,7 @@ static void SpawnRanges(benchmark::State &state) {
kk.reserve(7311921); kk.reserve(7311921);
for (auto [n, n_2x1, n_1x1] : nums) { for (auto [n, n_2x1, n_1x1] : nums) {
kk.spawn_more(n, n_2x1, n_1x1); kk.spawn(n, n_2x1, n_1x1);
} }
} }
@ -172,6 +172,35 @@ static void OriginBasicRanges(benchmark::State &state) {
} }
} }
static void OriginAllCases(benchmark::State &state) {
klotski::cases::BasicRanges::instance().build();
for (auto _ : state) {
auto &pp = klotski::cases::AllCases::instance();
pp.available_ = false;
pp.build();
}
}
static void RangesDerive(benchmark::State &state) {
auto &basic_ranges = klotski::cases::BasicRanges::instance().fetch();
klotski::cases::Ranges results;
results.reserve(klotski::cases::ALL_CASES_NUM[5]);
for (auto _ : state) {
results.clear();
basic_ranges.derive(5, results);
}
// std::cout << results.size() << " vs " << klotski::cases::ALL_CASES_NUM[5] << std::endl;
}
// BENCHMARK(CommonCodeToTypeId)->Arg(8)->Arg(64)->Arg(256); // BENCHMARK(CommonCodeToTypeId)->Arg(8)->Arg(64)->Arg(256);
// BENCHMARK(RawCodeToTypeId)->Arg(8)->Arg(64)->Arg(256); // BENCHMARK(RawCodeToTypeId)->Arg(8)->Arg(64)->Arg(256);
@ -181,6 +210,10 @@ static void OriginBasicRanges(benchmark::State &state) {
// BENCHMARK(SpawnRanges)->Unit(benchmark::kMillisecond); // BENCHMARK(SpawnRanges)->Unit(benchmark::kMillisecond);
BENCHMARK(OriginBasicRanges)->Unit(benchmark::kMillisecond); // BENCHMARK(OriginBasicRanges)->Unit(benchmark::kMillisecond);
// BENCHMARK(OriginAllCases)->Unit(benchmark::kMillisecond);
BENCHMARK(RangesDerive)->Unit(benchmark::kMillisecond);
BENCHMARK_MAIN(); BENCHMARK_MAIN();

8
src/core/main.cc

@ -27,14 +27,6 @@ using klotski::codec::SHORT_CODE_LIMIT;
int main() { int main() {
const auto start = clock(); const auto start = clock();
// auto kk = klotski::cases::RangesDemo();
//
// for (auto x : kk) {
// std::cout << x << std::endl;
// }
// klotski::cases::spawn_ranges(2, 1, 4, 4);
// auto raw_code = RawCode::from_common_code(0x1A9BF0C00)->unwrap(); // auto raw_code = RawCode::from_common_code(0x1A9BF0C00)->unwrap();
// auto ret = klotski::cases::group_extend_from_seed(raw_code); // auto ret = klotski::cases::group_extend_from_seed(raw_code);
// //

48
src/core/ranges/internal/derive.cc

@ -0,0 +1,48 @@
#include "utils/utility.h"
#include "ranges/ranges.h"
using klotski::cases::Ranges;
/// Check whether the combination of head and range is valid.
static int check_range(const int head, uint32_t range) {
uint32_t flags = 0b110011 << head; // fill 2x2 block
for (int addr = 0, offset = 1; range; range >>= 2, ++offset) { // traverse every 2-bit
const auto num = std::countr_one(flags);
addr += num; // next unfilled block
flags >>= num;
switch (range & 0b11) {
case 0b00: // space
case 0b11: // 1x1 block
flags |= 0b1;
continue;
case 0b01: // 1x2 block
if (flags & 0b10 || addr % 4 == 3) { // invalid case
return offset; // broken offset
}
flags |= 0b11;
continue;
case 0b10: // 2x1 block
if (flags & 0b10000 || addr > 15) { // invalid case
return offset; // broken offset
}
flags |= 0b10001;
}
}
return 0; // pass check
}
void Ranges::derive(const int head, Ranges &output) const {
for (uint32_t index = 0; index < size(); ++index) {
if (const auto offset = check_range(head, (*this)[index])) { // invalid case
uint32_t tmp = 1U << (32 - offset * 2); // distance to next possible range
/// !! <- broken
/// ( xx xx xx ) xx xx xx ... [reversed range]
/// +1 00 00 00 ... (delta)
tmp += range_reverse((*this)[index]) & ~(tmp - 1);
while (range_reverse((*this)[++index]) < tmp) {} // located next range
--index;
continue;
}
output.emplace_back(range_reverse((*this)[index])); // release valid case
}
}

57
src/core/ranges/internal/head.cc

@ -1,57 +0,0 @@
#include "utils/utility.h"
#include "ranges/ranges.h"
/// Check whether the combination of head and range is valid.
static int check_range(const int head, uint32_t range) noexcept {
constexpr uint32_t M_1x1 = 0b00000001;
constexpr uint32_t M_1x2 = 0b00000011;
constexpr uint32_t M_2x1 = 0b00010001;
constexpr uint32_t M_2x2 = 0b00110011;
uint32_t flags = M_2x2 << head; // fill 2x2 block
for (int addr = 0, offset = 1; range; range >>= 2, ++offset) { // traverse every 2-bit
const auto num = klotski::low_zero_num(~flags);
addr += num; // next unfilled block
flags >>= num;
switch (range & 0b11) {
case 0b00: // space
case 0b11: // 1x1 block
flags |= M_1x1;
continue;
case 0b10: // 2x1 block
if ((flags >> 4) & 0b1 || addr > 15) { // invalid case
return offset; // broken offset
}
flags |= M_2x1;
continue;
case 0b01: // 1x2 block
if ((flags >> 1) & 0b1 || (addr & 0b11) == 0b11) { // invalid case
return offset; // broken offset
}
flags |= M_1x2;
continue;
}
}
return 0; // pass check
}
void klotski::cases::Ranges::with_head(const int head, Ranges &release) const {
// release.clear();
// release.reserve(ALL_CASES_NUM[head]);
// auto &basic_ranges = BasicRanges::instance().fetch();
for (uint32_t index = 0; index < ranges_.size(); ++index) {
auto offset = check_range(head, ranges_[index]);
if (offset) { // invalid case
auto tmp = (uint32_t)0b1 << (32 - offset * 2); // distance to next possible range
/// !! <- broken
/// ( xx xx xx ) xx xx xx ... [reversed range]
/// +1 00 00 00 ... (delta)
tmp += klotski::range_reverse(ranges_[index]) & ~(tmp - 1);
while (klotski::range_reverse(ranges_[++index]) < tmp); // located next range
--index;
continue;
}
release.ranges_.emplace_back(klotski::range_reverse(ranges_[index])); // release valid case
}
}

60
src/core/ranges/internal/ranges.cc

@ -1,54 +1,40 @@
#include <list>
#include <algorithm> #include <algorithm>
#include "ranges/ranges.h"
#include "utils/utility.h" #include "utils/utility.h"
#include "ranges/ranges.h"
template<int N> using klotski::cases::Ranges;
static void demo(std::vector<uint32_t> &ranges, int n_10, int n_11) {
constexpr auto num = 16 - N;
constexpr auto offset = (16 - num) << 1; // offset of low bits
int n_00 = 16 - N * 2 - n_11; template<int N>
static void build_ranges(std::vector<uint32_t> &ranges, int n_10, int n_11) {
int n_01 = N - n_10; int n_01 = N - n_10;
int n_00 = 16 - N * 2 - n_11;
std::array<int, num> series {}; std::array<int, 16 - N> series {};
std::fill_n(series.begin() + n_00, n_01, 0b01);
auto kk = std::fill_n(series.begin() + n_00, n_01, 0b01); std::fill_n(series.begin() + n_00 + n_01, n_10, 0b10);
auto pp = std::fill_n(kk, n_10, 0b10); std::fill_n(series.begin() + n_00 + n_01 + n_10, n_11, 0b11);
std::fill_n(pp, n_11, 0b11);
// std::vector<uint32_t> ranges;
do { do {
uint32_t range = 0; uint32_t range = 0;
for (const auto x : series) // store every 2-bit for (const auto x : series) { // store every 2-bit
(range <<= 2) |= x; (range <<= 2) |= x;
ranges.emplace_back(range << offset); }
ranges.emplace_back(range << (N * 2));
} while (std::ranges::next_permutation(series).found); } while (std::ranges::next_permutation(series).found);
// return ranges;
} }
// void klotski::cases::spawn_ranges(std::vector<uint32_t> &ranges, int n, int n_2x1, int n_1x1) { void Ranges::spawn(const int n, const int n_2x1, const int n_1x1) {
// KLSK_ASSUME(n >= 0 && n_2x1 >= 0 && n_1x1 >= 0);
// KLSK_ASSUME(n <= 7 && n_2x1 <= n && n_1x1 + n * 2 <= 14);
// }
void klotski::cases::Ranges::spawn_more(int n, int n_2x1, int n_1x1) {
// spawn_ranges(ranges_, n, n_2x1, n_1x1);
switch (n) { switch (n) {
case 0: return demo<0>(ranges_, n_2x1, n_1x1); case 0: return build_ranges<0>(*this, n_2x1, n_1x1);
case 1: return demo<1>(ranges_, n_2x1, n_1x1); case 1: return build_ranges<1>(*this, n_2x1, n_1x1);
case 2: return demo<2>(ranges_, n_2x1, n_1x1); case 2: return build_ranges<2>(*this, n_2x1, n_1x1);
case 3: return demo<3>(ranges_, n_2x1, n_1x1); case 3: return build_ranges<3>(*this, n_2x1, n_1x1);
case 4: return demo<4>(ranges_, n_2x1, n_1x1); case 4: return build_ranges<4>(*this, n_2x1, n_1x1);
case 5: return demo<5>(ranges_, n_2x1, n_1x1); case 5: return build_ranges<5>(*this, n_2x1, n_1x1);
case 6: return demo<6>(ranges_, n_2x1, n_1x1); case 6: return build_ranges<6>(*this, n_2x1, n_1x1);
case 7: return demo<7>(ranges_, n_2x1, n_1x1); case 7: return build_ranges<7>(*this, n_2x1, n_1x1);
default: return;
} }
} }

70
src/core/ranges/ranges.h

@ -3,75 +3,15 @@
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include "utils/utility.h"
namespace klotski::cases { namespace klotski::cases {
// void spawn_ranges(std::vector<uint32_t> &ranges, int n, int n_2x1, int n_1x1); class Ranges : public std::vector<uint32_t> {
// std::vector<uint32_t> basic_ranges();
// TODO: should we inherit on `std::vector<uint32_t>` ?
class Ranges {
public: public:
void spawn_more(int n, int n_2x1, int n_1x1); /// Spawn klotski-ranges that match the specified block numbers.
void spawn(int n, int n_2x1, int n_1x1);
using iterator = std::vector<uint32_t>::iterator;
using size_type = std::vector<uint32_t>::size_type;
using const_iterator = std::vector<uint32_t>::const_iterator;
using value_type = std::vector<uint32_t>::value_type;
using reference = std::vector<uint32_t>::reference;
using const_reference = std::vector<uint32_t>::const_reference;
iterator begin() {
return ranges_.begin();
}
iterator end() {
return ranges_.end();
}
[[nodiscard]] const_iterator begin() const {
return ranges_.begin();
}
[[nodiscard]] const_iterator end() const {
return ranges_.end();
}
void clear() {
ranges_.clear();
}
[[nodiscard]] size_type size() const {
return ranges_.size();
}
void reserve(const size_type cap) {
ranges_.reserve(cap);
}
void with_head(int head, Ranges &release) const;
void reverse() {
for (auto &x : ranges_) {
x = range_reverse(x); // flip every 2-bit
}
}
const_reference operator[](const size_type n) const {
return ranges_[n];
}
[[nodiscard]] const value_type* data() const {
return ranges_.data();
}
// private: /// Derive the legal klotski-ranges with specified head.
std::vector<uint32_t> ranges_ {}; void derive(int head, Ranges &output) const;
}; };
} // namespace klotski::cases } // namespace klotski::cases

2
src/core/utils/utility.h

@ -18,6 +18,8 @@
#define KLSK_INLINE __attribute__((always_inline)) #define KLSK_INLINE __attribute__((always_inline))
#define KLSK_ASSUME(expr) __builtin_assume(expr)
namespace klotski { namespace klotski {
/// Get the number of consecutive `0` in the low bits. /// Get the number of consecutive `0` in the low bits.

2
src/core_test/cases/all_cases.cc

@ -39,7 +39,7 @@ protected:
EXPECT_EQ(all_cases_num, ALL_CASES_NUM_); // verify all cases global size EXPECT_EQ(all_cases_num, ALL_CASES_NUM_); // verify all cases global size
for (int head = 0; head < 16; ++head) { for (int head = 0; head < 16; ++head) {
EXPECT_EQ(hash::xxh3(all_cases[head].ranges_), ALL_CASES_XXH3[head]); // verify all cases checksum EXPECT_EQ(hash::xxh3(all_cases[head]), ALL_CASES_XXH3[head]); // verify all cases checksum
} }
} }
}; };

6
src/core_test/cases/basic_ranges.cc

@ -1,7 +1,7 @@
#include "hash.h" #include "hash.h"
#include "helper.h" #include "helper.h"
static constexpr uint64_t BASIC_RANGES_XXH3 = 0x82b040060044e336; static constexpr uint64_t BASIC_RANGES_XXH3 = 0x2ced674494fe904d;
class BasicRangesTest : public testing::Test, public Concurrent { class BasicRangesTest : public testing::Test, public Concurrent {
protected: protected:
@ -24,12 +24,12 @@ protected:
static void Verify() { static void Verify() {
const auto &basic_ranges = BasicRanges::instance().fetch(); const auto &basic_ranges = BasicRanges::instance().fetch();
EXPECT_EQ(basic_ranges.size(), BASIC_RANGES_NUM); // verify basic ranges size EXPECT_EQ(basic_ranges.size(), BASIC_RANGES_NUM); // verify basic ranges size
EXPECT_EQ(hash::xxh3(basic_ranges.ranges_), BASIC_RANGES_XXH3); // verify basic ranges checksum EXPECT_EQ(hash::xxh3(basic_ranges), BASIC_RANGES_XXH3); // verify basic ranges checksum
} }
}; };
TEST_FF(BasicRanges, constant) { TEST_FF(BasicRanges, constant) {
EXPECT_EQ(BASIC_RANGES_NUM, 7311921); EXPECT_EQ(BASIC_RANGES_NUM, 7311885);
} }
TEST_FF(BasicRanges, basic_ranges) { TEST_FF(BasicRanges, basic_ranges) {

Loading…
Cancel
Save