commit 1.2
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
BasedOnStyle: Google
|
||||||
|
AccessModifierOffset: -4
|
||||||
|
ColumnLimit: 120
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Always
|
||||||
|
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
add_executable(1brc
|
add_executable(1brc
|
||||||
main.cxx
|
main.cxx
|
||||||
|
io.cxx
|
||||||
|
map.cxx
|
||||||
|
tricks.cxx
|
||||||
)
|
)
|
||||||
target_sources(1brc
|
target_sources(1brc
|
||||||
PUBLIC
|
PUBLIC
|
||||||
FILE_SET 1brc_modules TYPE CXX_MODULES FILES
|
FILE_SET 1brc_modules TYPE CXX_MODULES FILES
|
||||||
|
io.ixx
|
||||||
|
map.ixx
|
||||||
|
tricks.ixx
|
||||||
# util/logger.ixx
|
# util/logger.ixx
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
module io;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
std::string read_file(const std::filesystem::path& path) {
|
||||||
|
auto file = std::ifstream(path);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error(std::format("could not open file: {}", path.string()));
|
||||||
|
}
|
||||||
|
auto stream = std::stringstream();
|
||||||
|
stream << file.rdbuf();
|
||||||
|
return stream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_file::mapped_file(const std::filesystem::path& path) {
|
||||||
|
const auto file_size = std::filesystem::file_size(path);
|
||||||
|
|
||||||
|
fd = open(path.generic_string().data(), O_RDONLY);
|
||||||
|
data = mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||||
|
length = file_size;
|
||||||
|
|
||||||
|
if (data == MAP_FAILED) {
|
||||||
|
close(fd);
|
||||||
|
throw std::runtime_error("failed to map file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_file::mapped_file(mapped_file&& other) noexcept
|
||||||
|
: fd(other.fd), data(std::exchange(other.data, nullptr)), length(other.length) {}
|
||||||
|
|
||||||
|
mapped_file& mapped_file::operator=(mapped_file&& other) noexcept {
|
||||||
|
if (&other == this) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto moved = std::move(other);
|
||||||
|
|
||||||
|
std::swap(moved.fd, this->fd);
|
||||||
|
std::swap(moved.data, this->data);
|
||||||
|
std::swap(moved.length, this->length);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_file::~mapped_file() {
|
||||||
|
if (data != nullptr) {
|
||||||
|
munmap(data, length);
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped_file::operator std::string_view() const noexcept {
|
||||||
|
return std::string_view(reinterpret_cast<const char*>(data), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t mapped_file::size() const noexcept { return length; }
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export module io;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
export [[nodiscard]] std::string read_file(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
export class mapped_file {
|
||||||
|
private:
|
||||||
|
int fd;
|
||||||
|
void* data = nullptr;
|
||||||
|
std::size_t length = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit mapped_file(const std::filesystem::path& path);
|
||||||
|
|
||||||
|
mapped_file(const mapped_file&) = delete;
|
||||||
|
mapped_file& operator=(const mapped_file&) = delete;
|
||||||
|
|
||||||
|
mapped_file(mapped_file&& rhs) noexcept;
|
||||||
|
mapped_file& operator=(mapped_file&& rhs) noexcept;
|
||||||
|
|
||||||
|
~mapped_file();
|
||||||
|
|
||||||
|
operator std::string_view() const noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t size() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <liburing.h>
|
||||||
|
|
||||||
|
export module liburing;
|
||||||
|
|
||||||
|
namespace uring {
|
||||||
|
|
||||||
|
using ::io_uring;
|
||||||
|
|
||||||
|
}
|
||||||
+114
-1
@@ -1,3 +1,116 @@
|
|||||||
import std;
|
import std;
|
||||||
|
import io;
|
||||||
|
import map;
|
||||||
|
import tricks;
|
||||||
|
|
||||||
auto main() -> int { std::println("hello world"); }
|
auto calculate_boundaries(std::string_view file, std::size_t thread_count) -> std::inplace_vector<std::size_t, 64> {
|
||||||
|
auto bounds = std::inplace_vector<std::size_t, 64>();
|
||||||
|
|
||||||
|
const auto step_estimate = file.size() / thread_count;
|
||||||
|
while (bounds.size() < thread_count) {
|
||||||
|
auto at_guess = file.data() + (step_estimate * (1 + bounds.size()));
|
||||||
|
while (*at_guess++ != '\n') [[likely]] {
|
||||||
|
}
|
||||||
|
bounds.push_back(at_guess - file.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto parse_temp(std::string_view sv) {
|
||||||
|
constexpr auto dot_bit_mask = std::uint64_t(0x10101000ULL);
|
||||||
|
|
||||||
|
auto word = std::uint64_t();
|
||||||
|
std::memcpy(&word, sv.data(), sizeof(word));
|
||||||
|
|
||||||
|
const auto decimal_sep_pos = std::countr_zero(~word & dot_bit_mask);
|
||||||
|
const auto dot_byte = decimal_sep_pos / 8;
|
||||||
|
const auto shift = 28 - decimal_sep_pos;
|
||||||
|
|
||||||
|
// I don't trust auto here
|
||||||
|
std::int64_t is_neg = (static_cast<std::int64_t>(~word) << 59) >> 63;
|
||||||
|
std::uint64_t sign_mask = ~(static_cast<std::uint64_t>(is_neg) & 0xFF);
|
||||||
|
std::uint64_t digits = ((word & sign_mask) << shift) & 0xFFFFFFFFFFULL;
|
||||||
|
|
||||||
|
std::int64_t abs_value = ((digits * 0x640a0001ULL) >> 32) & 0x3FF;
|
||||||
|
std::int64_t value = (abs_value ^ is_neg) - is_neg;
|
||||||
|
|
||||||
|
return std::make_pair(static_cast<std::int32_t>(value), dot_byte + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t BatchSize>
|
||||||
|
[[nodiscard]] std::inplace_vector<std::pair<std::int32_t, std::size_t>, BatchSize> parse_temp(
|
||||||
|
std::span<const std::string_view> batch) {
|
||||||
|
auto results = std::inplace_vector<std::pair<std::int32_t, std::size_t>, BatchSize>();
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < batch.size(); ++i) {
|
||||||
|
results.push_back(parse_temp(batch[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_chunk(std::string_view chunk, brc::map& results) {
|
||||||
|
auto it = chunk.begin();
|
||||||
|
while (it != chunk.end()) {
|
||||||
|
const auto semicolon = brc::find_delim({it, chunk.end()}, ';');
|
||||||
|
const auto city = std::string_view(it, it + semicolon);
|
||||||
|
|
||||||
|
const auto [value, len] = parse_temp({it + semicolon + 1, chunk.end()});
|
||||||
|
|
||||||
|
results.insert(city, value);
|
||||||
|
|
||||||
|
it = it + semicolon + 1 + len + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto main(int argc, char** argv) -> int {
|
||||||
|
if (argc != 2) {
|
||||||
|
std::println("usage: {} <file.txt>", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto filename = std::filesystem::path(argv[1]);
|
||||||
|
const auto file = brc::mapped_file(filename);
|
||||||
|
const auto view = std::string_view(file);
|
||||||
|
const auto thread_count = std::thread::hardware_concurrency() / 1;
|
||||||
|
|
||||||
|
const auto boundaries = calculate_boundaries(file, thread_count);
|
||||||
|
|
||||||
|
auto threads = std::vector<std::thread>();
|
||||||
|
auto results = std::vector<brc::map>();
|
||||||
|
for (std::size_t i = 0; i < thread_count; i++) {
|
||||||
|
results.emplace_back(1ull << 10ull);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < thread_count; i++) {
|
||||||
|
const auto begin = i == 0 ? 0 : boundaries[i - 1];
|
||||||
|
const auto end = boundaries[i];
|
||||||
|
const auto sv = std::string_view(view.begin() + begin, view.begin() + end);
|
||||||
|
threads.emplace_back([i, sv, &results]() { process_chunk(sv, results[i]); });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < thread_count; i++) {
|
||||||
|
threads[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 1; i < results.size(); i++) {
|
||||||
|
results[0].merge_against(results[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& map = results[0];
|
||||||
|
const auto entries = map.collapse();
|
||||||
|
std::print("{{");
|
||||||
|
for (auto it = entries.begin(); it != entries.end(); it++) {
|
||||||
|
const auto entry = *it;
|
||||||
|
const auto name = std::string_view(entry.key_ptr, entry.key_len);
|
||||||
|
const auto min = entry.min / 10.0f;
|
||||||
|
const auto mean = (entry.total / entry.count) / 10.0f;
|
||||||
|
const auto max = entry.max / 10.0f;
|
||||||
|
std::print("{}={:.1f}/{:.1f}/{:.1f}", name, min, mean, max);
|
||||||
|
if (it + 1 != entries.end()) {
|
||||||
|
std::print(",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::print("}}");
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
module map;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
map::map(std::size_t entry_count) : entries(std::make_unique<entry[]>(entry_count)), entry_count(entry_count) {}
|
||||||
|
|
||||||
|
std::uint64_t map::get_key(std::string_view city) noexcept {
|
||||||
|
std::uint64_t raw = 0;
|
||||||
|
const auto data_count = std::min(static_cast<int>(city.size()), 8);
|
||||||
|
std::memcpy(&raw, city.data(), data_count);
|
||||||
|
|
||||||
|
raw *= 0x9E3779B97F4A7C15ULL;
|
||||||
|
return raw ^ (raw >> 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t map::get_index(std::string_view city) const noexcept {
|
||||||
|
const auto key = get_key(city);
|
||||||
|
auto index = key & (entry_count - 1);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
auto& e = entries[index];
|
||||||
|
if (e.key_ptr == nullptr) {
|
||||||
|
e.key_hash = key;
|
||||||
|
e.key_ptr = city.data();
|
||||||
|
e.key_len = static_cast<std::uint16_t>(city.size());
|
||||||
|
break;
|
||||||
|
} else if (key == e.key_hash && city == std::string_view(e.key_ptr, e.key_len)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index = (index + 1 == entry_count) ? 0 : index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map::insert(std::string_view city, std::int32_t measurement) {
|
||||||
|
const auto index = get_index(city);
|
||||||
|
|
||||||
|
auto& entry = entries[index];
|
||||||
|
entry.count += 1;
|
||||||
|
entry.total += measurement;
|
||||||
|
entry.min = std::min(entry.min, measurement);
|
||||||
|
entry.max = std::max(entry.max, measurement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void map::merge_against(const map& other) {
|
||||||
|
for (std::size_t i = 0; i < other.entry_count; i++) {
|
||||||
|
if (other.entries[i].key_ptr == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& other_entry = other.entries[i];
|
||||||
|
auto& our_entry = entries[get_index(std::string_view(other_entry.key_ptr, other_entry.key_len))];
|
||||||
|
our_entry.min = std::min(our_entry.min, other_entry.min);
|
||||||
|
our_entry.max = std::max(our_entry.max, other_entry.max);
|
||||||
|
our_entry.count += other_entry.count;
|
||||||
|
our_entry.total += other_entry.total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<entry> map::collapse() const noexcept {
|
||||||
|
auto data = std::vector<entry>();
|
||||||
|
data.reserve(entry_count);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < entry_count; i++) {
|
||||||
|
if (entries[i].key_ptr == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push_back(entries[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(data.begin(), data.end(), [](const entry& a, const entry& b) {
|
||||||
|
const auto a_sv = std::string_view(a.key_ptr, a.key_len);
|
||||||
|
const auto b_sv = std::string_view(b.key_ptr, b.key_len);
|
||||||
|
return a_sv < b_sv;
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
export module map;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
export struct entry {
|
||||||
|
std::uint64_t key_hash = 0;
|
||||||
|
const char* key_ptr = nullptr; // 8, 8
|
||||||
|
std::int64_t total = 0; // 8, 26
|
||||||
|
std::int32_t min = std::numeric_limits<std::int32_t>::max(); // 4, 12
|
||||||
|
std::int32_t max = std::numeric_limits<std::int32_t>::min(); // 4, 16
|
||||||
|
std::uint32_t count = 0; // 4, 30
|
||||||
|
std::uint16_t key_len = 0; // 2, 32
|
||||||
|
};
|
||||||
|
|
||||||
|
export class map {
|
||||||
|
private:
|
||||||
|
std::size_t entry_count;
|
||||||
|
std::unique_ptr<entry[]> entries;
|
||||||
|
|
||||||
|
[[nodiscard]] static std::uint64_t get_key(std::string_view city) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] std::uint64_t get_index(std::string_view city) const noexcept;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit map(std::size_t entry_count);
|
||||||
|
|
||||||
|
void insert(std::string_view city, std::int32_t measurement);
|
||||||
|
|
||||||
|
[[nodiscard]] entry& operator[](std::string_view city);
|
||||||
|
|
||||||
|
void merge_against(const map& other);
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<entry> collapse() const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
module;
|
||||||
|
|
||||||
|
#include <simd>
|
||||||
|
|
||||||
|
module tricks;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
std::size_t find_delim(std::string_view string, char target) {
|
||||||
|
constexpr auto width = std::simd::vec<std::uint8_t>::size();
|
||||||
|
const auto needle = std::simd::vec<std::uint8_t>(static_cast<std::uint8_t>(target));
|
||||||
|
|
||||||
|
auto i = std::size_t(0);
|
||||||
|
for (; i + width <= string.size(); i += width) {
|
||||||
|
const auto chunk = std::simd::vec<std::uint8_t>(
|
||||||
|
std::span<const std::uint8_t, width>(reinterpret_cast<const std::uint8_t*>(string.data() + i), width));
|
||||||
|
|
||||||
|
const auto mask = chunk == needle;
|
||||||
|
if (const auto bits = mask.to_bitset(); bits.any()) {
|
||||||
|
return i + std::countr_zero(bits.to_ullong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < string.size(); ++i) {
|
||||||
|
if (string[i] == target) return i;
|
||||||
|
}
|
||||||
|
return string.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::inplace_vector<std::size_t, 4> find_delim_batch(std::string_view string, char target) {
|
||||||
|
constexpr auto BatchSize = 4;
|
||||||
|
auto sizes = std::inplace_vector<std::size_t, BatchSize>();
|
||||||
|
|
||||||
|
constexpr auto width = std::simd::vec<std::uint8_t>::size();
|
||||||
|
const auto needle = std::simd::vec<std::uint8_t>(static_cast<std::uint8_t>(target));
|
||||||
|
|
||||||
|
std::size_t i = 0;
|
||||||
|
|
||||||
|
for (; i + width <= string.size() && sizes.size() < BatchSize; i += width) {
|
||||||
|
const auto chunk = std::simd::vec<std::uint8_t>(
|
||||||
|
std::span<const std::uint8_t, width>(reinterpret_cast<const std::uint8_t*>(string.data() + i), width));
|
||||||
|
|
||||||
|
const auto mask = chunk == needle;
|
||||||
|
auto bits = mask.to_bitset().to_ullong();
|
||||||
|
|
||||||
|
while (bits != 0 && sizes.size() < BatchSize) {
|
||||||
|
const auto bit_pos = std::countr_zero(bits);
|
||||||
|
sizes.push_back(i + bit_pos);
|
||||||
|
bits &= bits - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < string.size() && sizes.size() < BatchSize; ++i) {
|
||||||
|
if (string[i] == target) {
|
||||||
|
sizes.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export module tricks;
|
||||||
|
|
||||||
|
import std;
|
||||||
|
|
||||||
|
namespace brc {
|
||||||
|
|
||||||
|
export [[nodiscard]] std::size_t find_delim(std::string_view string, char target);
|
||||||
|
|
||||||
|
export [[nodiscard]] std::inplace_vector<std::size_t, 4> find_delim_batch(std::string_view string, char target);
|
||||||
|
|
||||||
|
} // namespace brc
|
||||||
Reference in New Issue
Block a user