Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 129 additions & 48 deletions lbs_solver/incl/solver/index.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,149 @@

#include <cth/io/log.hpp>

#include <array>
#include <cstdint>
#include <filesystem>
#include <ranges>
#include <span>
#include <string_view>
#include <vector>

#if __has_include(<simd>)
#include <simd>
#endif


namespace lbs {
namespace dev {
[[nodiscard]] constexpr std::vector<size_t> split(
std::span<char const> data,
char separator,
size_t separatorDistGuess = 3
);
}

/**
* @return the end index of every word, the last one is data.size()
*/
[[nodiscard]] constexpr std::vector<size_t> word_ends(
std::span<char const> data,
char separator,
size_t separatorDistGuess = 3
);

struct words_data {
std::vector<std::string_view> words;
std::vector<char_mask_t> wordHashes;
};


/**
* filters the words and builds hashes
* @param compressionIndex char -> index in [0, 11]
* @param csv words
* @param wordEnds word end indices
* @return filtered words & their hashes
*/
[[nodiscard]] constexpr words_data filter_and_hash(
compression_index_t const& compressionIndex,
std::string_view csv,
std::span<size_t const> wordEnds
);


}

namespace lbs::dev {

#if defined(__cpp_lib_simd) || defined(__glibcxx_simd)
[[nodiscard]] constexpr std::vector<size_t> split(
std::span<char const> data,
char separator,
size_t separator_dist_guess = 3
size_t separatorDistGuess
) {
using mask_t = uint64_t;
std::vector<size_t> indices{};
indices.reserve(data.size() / separator_dist_guess);
static constexpr size_t BLOCK_SIZE = sizeof(uint64_t) * 8;
using block_t = std::simd::vec<char, BLOCK_SIZE>;


std::vector<size_t> separatorIdxs{};
separatorIdxs.reserve(data.size() / separatorDistGuess);

static constexpr size_t BLOCK_SIZE = sizeof(mask_t) * 8;
auto const blocks = data.size() / BLOCK_SIZE;

auto const chunkedEnd = (data.size() / BLOCK_SIZE) * BLOCK_SIZE;
auto const uncheckedEnd = BLOCK_SIZE * blocks;

static constexpr auto extract_indices = [](
std::vector<size_t>& buffer,
mask_t mask,
size_t offset
) {
auto extract_separator_idxs = [&separatorIdxs](size_t offset, uint64_t mask) {
while(mask != 0) {
auto const idx = std::countr_zero(mask);
buffer.push_back(offset + idx);
mask &= mask - mask_t{1}; // Clear lowest set bit
separatorIdxs.push_back(offset + std::countr_zero(mask));
mask &= mask - 1;
}
};

// simd friendly processing
for(size_t blockBegin = 0; blockBegin < chunkedEnd; blockBegin += BLOCK_SIZE) {
mask_t blockMask = 0;

// simd separator into mask
for(size_t i = 0; i < BLOCK_SIZE; i++)
if(data[blockBegin + i] == separator)
blockMask |= mask_t{1} << i;

extract_indices(indices, blockMask, blockBegin);
for(size_t i = 0; i < uncheckedEnd; i += block_t::size) {
auto simdBlock = std::simd::unchecked_load<block_t>(data.subspan(i, block_t::size));
extract_separator_idxs(i, (simdBlock == block_t{separator}).to_ullong());
}

//tail cleanup
for(size_t i = chunkedEnd; i < data.size(); i++)
if(data[i] == separator)
indices.push_back(i);
// bug in gcc, std::simd::partial_load fails to load byte 3 when exactly 4 bytes are remaining
std::array<char, BLOCK_SIZE> lastChars{};
std::ranges::copy(data.subspan(uncheckedEnd), lastChars.begin());
auto lastBlock = std::simd::unchecked_load<block_t>(lastChars);

extract_separator_idxs(
uncheckedEnd,
(lastBlock == block_t{separator}).to_ullong()
);


return indices;
return separatorIdxs;
}

#else

[[nodiscard]] constexpr std::vector<size_t> split(
std::span<char const> data,
char separator,
size_t separatorDistGuess
) {
static constexpr size_t BLOCK_SIZE = 256;

std::vector<size_t> separatorIdxs{};
separatorIdxs.reserve(data.size() / separatorDistGuess);

std::array<size_t, BLOCK_SIZE> blockSeparatorIdxs{};

for(size_t i = 0; i < data.size(); i += BLOCK_SIZE) {
auto const blockEnd = std::min(i + BLOCK_SIZE, data.size());
size_t separatorC = 0;

for(size_t j = i; j < blockEnd; j++) {
blockSeparatorIdxs[separatorC] = j;
separatorC += data[j] == separator;
}

separatorIdxs.append_range(std::span{blockSeparatorIdxs}.first(separatorC));
}

return separatorIdxs;
}
#endif

}

namespace lbs {
[[nodiscard]] constexpr std::vector<size_t> word_ends(
std::span<char const> data,
char separator,
size_t separatorDistGuess
) {
auto wordEnds = dev::split(data, separator, separatorDistGuess);
if(wordEnds.empty() || wordEnds.back() != data.size())
wordEnds.push_back(data.size());
return wordEnds;
}
}

namespace lbs {

Expand Down Expand Up @@ -89,13 +176,12 @@ namespace lbs {
* @details Masks set whole sides to filter same side char sequences.
* Invalid chars overlap with everything. Any overlap => invalid word
*
* @param compression_index to base masks on
* @param compressionIndex to base masks on
* @return uint32_t masks
*/
[[nodiscard]] constexpr auto gen_char_filter_masks(
compression_index_t const& compression_index
compression_index_t const& compressionIndex
) {

// a side mask sets all bits of a side to 1
static constexpr auto SIDE_MASKS = [] {
// first side (first n bits to 1)
Expand All @@ -112,13 +198,13 @@ namespace lbs {
filterFlags.fill(INVALID_CHAR_FLAG);


for(size_t i = 0; i < compression_index.size(); i++) {
auto const& compressedIdx = compression_index[i];
for(size_t i = 0; i < compressionIndex.size(); i++) {
auto const& compressedIdx = compressionIndex[i];
char_mask_t flag{};
if(compressedIdx == INVALID_CHAR_FLAG)
flag = INVALID_CHAR_FLAG;
else {
auto const side = compression_index[i] / CHARS_PER_SIDE;
auto const side = compressionIndex[i] / CHARS_PER_SIDE;
flag = SIDE_MASKS[side];
}
filterFlags[i] = flag;
Expand All @@ -128,40 +214,35 @@ namespace lbs {
return filterFlags;
}

struct words_data {
std::vector<std::string_view> words;
std::vector<char_mask_t> wordHashes;
};

[[nodiscard]] constexpr words_data filter_and_hash(
compression_index_t const& compression_index,
compression_index_t const& compressionIndex,
std::string_view csv,
std::span<size_t const> splits
std::span<size_t const> wordEnds
) {
auto const charFilterMasks = gen_char_filter_masks(compression_index);
auto const charFilterMasks = gen_char_filter_masks(compressionIndex);

static constexpr size_t FILTER_FREQ = 2;

words_data out{};
out.words.reserve(splits.size() / FILTER_FREQ);
out.wordHashes.reserve(splits.size() / FILTER_FREQ);
out.words.reserve(wordEnds.size() / FILTER_FREQ);
out.wordHashes.reserve(wordEnds.size() / FILTER_FREQ);

size_t begin = 0;

// <= to handle tail
for(size_t splitI = 0; splitI <= splits.size(); splitI++) {
auto const end = splitI == splits.size() ? csv.size() : splits[splitI];
for(auto const end : wordEnds) {
auto const size = end - begin;

if(size > 0) {
auto const word = csv.substr(begin, size);

char_mask_t wordHash = bit_flag(word[0], compression_index);
char_mask_t wordHash = bit_flag(word[0], compressionIndex);
size_t i = 1;

for(; i < size; i++) {
auto const& charMask = charFilterMasks[idx(word[i])];
auto const& prevCharMask = charFilterMasks[idx(word[i - 1])];
wordHash |= bit_flag(word[i], compression_index);
wordHash |= bit_flag(word[i], compressionIndex);
if((charMask & prevCharMask) != 0)
break;
}
Expand Down
26 changes: 13 additions & 13 deletions lbs_solver/runner/bench/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ constexpr std::string_view PUZZLES_PATH = "assets/nyt_puzzles.txt";
struct word_list {
std::vector<std::byte> data;
std::string_view csv;
std::vector<size_t> splits;
std::vector<size_t> wordEnds;
};

struct puzzle {
Expand All @@ -39,8 +39,8 @@ struct puzzle {
[[nodiscard]] word_list load_word_list() {
auto data = cth::io::file::read<std::byte>(WORD_LIST_PATH);
std::string_view const csv{reinterpret_cast<char const*>(data.data()), data.size()};
auto splits = split(csv, ',');
return {std::move(data), csv, std::move(splits)};
auto wordEnds = word_ends(csv, ',');
return {std::move(data), csv, std::move(wordEnds)};
}

/**
Expand All @@ -58,7 +58,7 @@ struct puzzle {

void register_benchmarks(std::span<puzzle const> puzzles, word_list const& list) {
auto const csv = list.csv;
auto const& splits = list.splits;
auto const& wordEnds = list.wordEnds;

benchmark::RegisterBenchmark("lbs/load/word_list/words_easy", [](benchmark::State& state) {
for(auto _ : state) {
Expand All @@ -67,20 +67,20 @@ void register_benchmarks(std::span<puzzle const> puzzles, word_list const& list)
}
});

benchmark::RegisterBenchmark("lbs/split/word_list/words_easy", [csv](benchmark::State& state) {
benchmark::RegisterBenchmark("lbs/word_ends/word_list/words_easy", [csv](benchmark::State& state) {
for(auto _ : state) {
auto splits = split(csv, ',');
benchmark::DoNotOptimize(splits);
auto wordEnds = word_ends(csv, ',');
benchmark::DoNotOptimize(wordEnds);
}
});

for(auto const& p : puzzles) {
auto const name = [&p](std::string_view family) { return std::format("{}/{}/{}", family, p.group, p.date); };

benchmark::RegisterBenchmark(name("lbs/filter_and_hash"), [&p, csv, &splits](benchmark::State& state) {
benchmark::RegisterBenchmark(name("lbs/filter_and_hash"), [&p, csv, &wordEnds](benchmark::State& state) {
for(auto _ : state) {
auto const index = gen_compression_index(p.letters);
auto wordsData = filter_and_hash(index, csv, splits);
auto wordsData = filter_and_hash(index, csv, wordEnds);
benchmark::DoNotOptimize(wordsData);
}
});
Expand All @@ -92,10 +92,10 @@ void register_benchmarks(std::span<puzzle const> puzzles, word_list const& list)
}
});

benchmark::RegisterBenchmark(name("lbs/total"), [&p, csv, &splits](benchmark::State& state) {
benchmark::RegisterBenchmark(name("lbs/total"), [&p, csv, &wordEnds](benchmark::State& state) {
for(auto _ : state) {
auto const index = gen_compression_index(p.letters);
auto const& [words, wordHashes] = filter_and_hash(index, csv, splits);
auto const& [words, wordHashes] = filter_and_hash(index, csv, wordEnds);
auto solutions = solve(index, words, wordHashes);
benchmark::DoNotOptimize(solutions);
}
Expand All @@ -105,7 +105,7 @@ void register_benchmarks(std::span<puzzle const> puzzles, word_list const& list)
for(auto _ : state) {
auto const list = load_word_list();
auto const index = gen_compression_index(p.letters);
auto const& [words, wordHashes] = filter_and_hash(index, list.csv, list.splits);
auto const& [words, wordHashes] = filter_and_hash(index, list.csv, list.wordEnds);
auto solutions = solve(index, words, wordHashes);
benchmark::DoNotOptimize(solutions);
}
Expand Down Expand Up @@ -144,7 +144,7 @@ int main(int argc, char** argv) {

for(auto& [date, letters] : load_puzzles()) {
auto const index = lbs::gen_compression_index(letters);
auto wordsData = lbs::filter_and_hash(index, list.csv, list.splits);
auto wordsData = lbs::filter_and_hash(index, list.csv, list.wordEnds);

bool const solvable = !lbs::solve(index, wordsData.words, wordsData.wordHashes).empty();
disagreements += solvable == gpt.run(letters, WORD_LIST_PATH).empty();
Expand Down
16 changes: 8 additions & 8 deletions lbs_solver/runner/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace lbs {
struct word_list_t {
std::string_view csv;
std::vector<size_t> splits;
std::vector<size_t> wordEnds;
std::vector<std::byte> data{};
};

Expand All @@ -18,16 +18,16 @@ struct word_list_t {
word_list_t dyn_load(std::string_view word_list_path) {
auto data = cth::io::file::read<std::byte>(word_list_path);
std::string_view csv{reinterpret_cast<char const*>(data.data()), data.size()};
auto splits = lbs::split(csv, ',');
return {csv, std::move(splits), std::move(data)};
auto wordEnds = lbs::word_ends(csv, ',');
return {csv, std::move(wordEnds), std::move(data)};
}

void print_solutions(std::string_view characters, std::string_view csv, std::span<size_t const> splits) {
void print_solutions(std::string_view characters, std::string_view csv, std::span<size_t const> wordEnds) {
auto const compressionIndex = gen_compression_index(characters);
auto const& [words, wordHashes] = filter_and_hash(
compressionIndex,
csv,
splits
wordEnds
);

auto const solutions = lbs::solve(compressionIndex, words, wordHashes);
Expand Down Expand Up @@ -55,17 +55,17 @@ namespace {
constexpr std::string_view WORD_LIST_CSV{dev::raw_word_list, sizeof(dev::raw_word_list)};

void solve_manual(std::string_view characters) {
constexpr auto splits = cth::dt::as_cxpr_array<[] { return lbs::split(WORD_LIST_CSV, ','); }>();
constexpr auto wordEnds = cth::dt::as_cxpr_array<[] { return lbs::word_ends(WORD_LIST_CSV, ','); }>();

lbs::print_solutions(characters, WORD_LIST_CSV, splits);
lbs::print_solutions(characters, WORD_LIST_CSV, wordEnds);
}
#else
constexpr std::string_view WORD_LIST_PATH{DYNAMIC_WORD_LIST_PATH_DEF};

void solve_manual(std::string_view characters) {
auto const wordList = lbs::dyn_load(WORD_LIST_PATH);

lbs::print_solutions(characters, wordList.csv, wordList.splits);
lbs::print_solutions(characters, wordList.csv, wordList.wordEnds);
}
#endif

Expand Down
Loading
Loading