mirror of
https://github.com/cculianu/Fulcrum.git
synced 2026-08-13 12:33:27 +02:00
279 lines
10 KiB
C++
279 lines
10 KiB
C++
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdlib>
|
|
#include <cinttypes>
|
|
#include <csignal>
|
|
#include <cstring>
|
|
#include <functional>
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <list>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <type_traits>
|
|
|
|
#include <rocksdb/db.h>
|
|
//#include <db/memtable.h>
|
|
//#include "rocksdb/memtablefactory.h"
|
|
//#include <memtable/inlineskiplist.h>
|
|
|
|
using namespace std;
|
|
using namespace rocksdb;
|
|
using namespace std::chrono;
|
|
|
|
template<typename T>
|
|
std::enable_if_t<std::is_integral_v<T>, T>
|
|
/* T */ swap_if_big_endian(T val) {
|
|
static_assert(CHAR_BIT == 8, "CHAR_BIT != 8");
|
|
// test if already little endian
|
|
if (const T test = 0x7f; reinterpret_cast<const std::byte *>(&test)[0] == std::byte{0x7f}) {
|
|
// already little endian
|
|
return val;
|
|
}
|
|
|
|
auto *ptr = reinterpret_cast<std::byte *>(&val);
|
|
constexpr auto SIZE = sizeof(val);
|
|
// swap the two ends
|
|
for (size_t front = 0; front < SIZE/2; ++front) {
|
|
const auto back = SIZE - front - 1;
|
|
ptr[front] ^= ptr[back];
|
|
ptr[back] ^= ptr[front];
|
|
ptr[front] ^= ptr[back];
|
|
}
|
|
return val;
|
|
}
|
|
|
|
/*
|
|
rocksdb::TableFactory *makeDictionaryTableFactory() {
|
|
auto block_opts = rocksdb::BlockBasedTableOptions{};
|
|
block_opts.checksum = ChecksumType::kCRC32c;
|
|
block_opts.no_block_cache = true;
|
|
return rocksdb::NewBlockBasedTableFactory(block_opts);
|
|
}
|
|
*/
|
|
|
|
static std::string readLine()
|
|
{
|
|
std::array<char, 256> buf;
|
|
std::cin.getline(buf.data(), buf.size());
|
|
return buf.data();
|
|
}
|
|
|
|
static std::atomic_bool sigCaught = false;
|
|
|
|
int main(int argc, char *argv[]) {
|
|
bool doWrite = true;
|
|
|
|
auto printUsageAndExit = [argv] {
|
|
std::cerr << "Usage: " << argv[0] << " database_path [r]" << std::endl;
|
|
std::exit(1);
|
|
};
|
|
|
|
if (argc < 2 || argc > 3)
|
|
printUsageAndExit();
|
|
|
|
const std::string dbPath = argv[1];
|
|
|
|
if (argc == 3) {
|
|
if (argv[2][0] != 'r')
|
|
printUsageAndExit();
|
|
doWrite = false;
|
|
}
|
|
|
|
std::cout << "We will attempt to " << (doWrite ? "CREATE the database" : "OPEN the existing database for reading") << " at: "
|
|
<< dbPath << std::endl << std::flush;
|
|
std::cout << "Hit enter if this is correct, Ctrl+C to abort: " << std::flush;
|
|
readLine();
|
|
|
|
DB *db_raw{};
|
|
std::unique_ptr<DB> db;
|
|
Options options;
|
|
// Optimize RocksDB. This is the easiest way to get RocksDB to perform well
|
|
options.IncreaseParallelism(8); // TESTING
|
|
options.OptimizeLevelStyleCompaction();
|
|
// create the DB if it's not already present
|
|
|
|
options.create_if_missing = doWrite;
|
|
options.error_if_exists = doWrite;
|
|
//options.db_write_buffer_size = 10 * 1024 * 1024;
|
|
options.compression = CompressionType::kNoCompression;
|
|
//options.statistics = rocksdb::CreateDBStatistics();
|
|
//options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
// testing
|
|
options.max_open_files = 100;//50;//100;//60; //10; ///< this affects memory usage see: https://github.com/facebook/rocksdb/issues/4112. -1 means unlimited.
|
|
options.keep_log_file_num = 5;
|
|
|
|
|
|
// open DB
|
|
Status s = DB::Open(options, dbPath.c_str(), &db_raw);
|
|
if (db_raw)
|
|
db.reset(db_raw);
|
|
|
|
const auto ChkErr = [](const Status & s, const std::string & prefix = {}){
|
|
if (!s.ok()) {
|
|
if (!prefix.empty())
|
|
std::cerr << prefix << ": ";
|
|
std::cerr << s.ToString() << std::endl;
|
|
std::exit(1);
|
|
}
|
|
};
|
|
|
|
ChkErr(s);
|
|
|
|
//ColumnFamilyOptions cf_options{};
|
|
|
|
//cf_options.table_factory.reset(makeDictionaryTableFactory());
|
|
//cf_options.prefix_extractor.reset(rocksdb::NewNoopTransform());
|
|
//cf_options.memtable_prefix_bloom_size_ratio = 0;
|
|
//cf_options.write_buffer_size = 10 * 1024 * 1024;
|
|
|
|
//std::string name("Name");
|
|
//ColumnFamilyHandle *cf;
|
|
//Status status = db->CreateColumnFamily(cf_options, name, &cf);
|
|
|
|
//ChkErr(s);
|
|
static constexpr std::size_t KEYSIZE = 4;
|
|
|
|
std::signal(SIGINT, [](int sig [[maybe_unused]]){
|
|
std::cerr << "\nSignal caught, aborting loop..." << std::endl;
|
|
sigCaught = true;
|
|
});
|
|
|
|
constexpr uint64_t ITERS = 250'000'000;//7'500'000'000ULL;//10'000'000'000ULL;
|
|
if (doWrite) {
|
|
auto lastTS = uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(system_clock::now().time_since_epoch()).count());
|
|
uint64_t lastCt = 0;
|
|
const auto last = ITERS-1;
|
|
bool islast = false;
|
|
uint64_t buffer[KEYSIZE];
|
|
char *pointer = reinterpret_cast<char *>(buffer);
|
|
WriteBatch writeBatch{};
|
|
uint64_t max = 10'000'000;
|
|
|
|
Slice key(pointer, sizeof(uint64_t) * KEYSIZE);
|
|
Slice value(reinterpret_cast<char *>(&max), sizeof(max));
|
|
for (uint64_t i = 0; i < ITERS; ++i) {
|
|
*(buffer) = swap_if_big_endian(i);
|
|
*(buffer + 1) = swap_if_big_endian(i + 1);
|
|
*(buffer + 2) = swap_if_big_endian(i + 2);
|
|
*(buffer + 3) = swap_if_big_endian(i + 3);
|
|
|
|
writeBatch.Put(/*cf*/nullptr, key, value);
|
|
|
|
islast = i == last;
|
|
if (i % 1000 == 0 || islast) {
|
|
const auto nIts = swap_if_big_endian(i);
|
|
const Slice nItems{reinterpret_cast<const char *>(&nIts), sizeof(nIts)};
|
|
writeBatch.Put(nullptr, "NUM_ITEMS", nItems);
|
|
Status s1 = db->Write(WriteOptions(), &writeBatch);
|
|
ChkErr(s1);
|
|
writeBatch.Clear();
|
|
if (sigCaught)
|
|
break;
|
|
}
|
|
|
|
if ((i && i % 1'000'000 == 0) || islast) {
|
|
uint64_t end = uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(system_clock::now().time_since_epoch()).count());
|
|
double time = (end - lastTS) / 1e9;
|
|
lastTS = end;
|
|
const auto diff = i - lastCt;
|
|
lastCt = i;
|
|
double delta = diff / time;
|
|
std::cout << "Write Speed: " << std::to_string(delta) << " [ " << std::setprecision(3) << ((double(i+1)/ITERS)*100.) << "% ]\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
ReadOptions ropts{};
|
|
uint64_t READ_ITERS = 0;
|
|
// read back iteration count
|
|
{
|
|
PinnableSlice val;
|
|
s = db->Get(ropts, db->DefaultColumnFamily(), "NUM_ITEMS", &val);
|
|
ChkErr(s);
|
|
if (val.size() != sizeof(READ_ITERS)) {
|
|
std::cerr << "Error reading db NUM_ITEMS key, aborting" << std::endl;
|
|
std::exit(1);
|
|
}
|
|
std::memcpy(&READ_ITERS, val.data(), sizeof(READ_ITERS));
|
|
READ_ITERS = swap_if_big_endian(READ_ITERS);
|
|
}
|
|
|
|
if (!sigCaught) {
|
|
std::atomic<uint64_t> COUNTER = 0;
|
|
constexpr int NTHR = 2;
|
|
std::cout << "Reading " << READ_ITERS << " items back in " << NTHR << " threads ..." << std::endl;
|
|
std::function<void(bool)> threadFunc = [READ_ITERS, db=db.get(), ropts, &ChkErr, &COUNTER](bool printerThread) {
|
|
uint64_t buffer[KEYSIZE];
|
|
char *pointer = reinterpret_cast<char *>(buffer);
|
|
uint64_t max = 10'000'000;
|
|
Slice key(pointer, sizeof(uint64_t) * KEYSIZE);
|
|
Slice value(reinterpret_cast<char *>(&max), sizeof(max));
|
|
|
|
uint64_t i = printerThread ? 0 : READ_ITERS - 1;
|
|
static_assert (!std::is_signed_v<decltype(i)>);
|
|
const auto last = printerThread ? READ_ITERS - 1 : 0;
|
|
using BFunc = std::function<bool()>;
|
|
using VFunc = std::function<void()>;
|
|
static_assert (NTHR==2); // for now we only really support 2 threads
|
|
const BFunc loopCondition = printerThread
|
|
? BFunc{[&] { return i < READ_ITERS && !sigCaught; }}
|
|
: BFunc{[&] { return i+1U != 0U && !sigCaught; }};
|
|
const VFunc loopIncr = printerThread ? VFunc{[&] { ++i; }} : VFunc{[&] { --i; }};
|
|
auto lastTS = uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(system_clock::now().time_since_epoch()).count());
|
|
uint64_t lastCt = 0;
|
|
const uint64_t totalCt = READ_ITERS * NTHR;
|
|
for (; loopCondition(); loopIncr()) {
|
|
//std::string val;
|
|
PinnableSlice val;
|
|
*(buffer) = swap_if_big_endian(i);
|
|
*(buffer + 1) = swap_if_big_endian(i + 1);
|
|
*(buffer + 2) = swap_if_big_endian(i + 2);
|
|
*(buffer + 3) = swap_if_big_endian(i + 3);
|
|
|
|
auto s = db->Get(ropts, db->DefaultColumnFamily(), key, &val);
|
|
ChkErr(s);
|
|
|
|
if (val.size() != value.size() || std::memcmp(val.data(), value.data(), val.size()) != 0) {
|
|
std::cerr << "Error reading value " << i << ": not equal!" << std::endl;;
|
|
break;
|
|
}
|
|
const auto ct = ++COUNTER;
|
|
//val.Reset();
|
|
|
|
if (ct == totalCt || (printerThread && ((i && i % 100'000L == 0) || i == last))) {
|
|
const auto end = uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(system_clock::now().time_since_epoch()).count());
|
|
double time = (end - lastTS) / 1e9;
|
|
lastTS = end;
|
|
const auto diffCt = ct - lastCt;
|
|
lastCt = ct;
|
|
double delta = diffCt / time;
|
|
std::cout << "Read Speed: " << std::to_string(delta)
|
|
<< " [ " << std::setprecision(3) << ((double(double(ct)+1.)/totalCt)*100.) << "% ]"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
};
|
|
std::list<std::thread> threads;
|
|
for (int i = 0; i < NTHR; ++i)
|
|
threads.emplace_back(threadFunc, i == 0);
|
|
for (auto &thread : threads)
|
|
thread.join();
|
|
}
|
|
|
|
std::cerr << "Press enter to close db" << std::endl;
|
|
readLine();
|
|
|
|
//s = db->DestroyColumnFamilyHandle(cf);
|
|
s = db->Close();
|
|
ChkErr(s);
|
|
db.reset();
|
|
|
|
std::cerr << "DB closed, press enter to exit" << std::endl;
|
|
readLine();
|
|
|
|
return 0;
|
|
}
|