-
-
Save ismaelsadeeq/48e7f704ccf481bd5ff5ec5328f033a4 to your computer and use it in GitHub Desktop.
This code snippet is used to bench Bitcoin core `get_block` RPC for various verbosity level using sequential and thread pool strategy
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <algorithm> | |
#include <chrono> | |
#include <curl/curl.h> | |
#include <future> | |
#include <iomanip> | |
#include <iostream> | |
#include <mutex> | |
#include <numeric> | |
#include <queue> | |
#include <sstream> | |
#include <string> | |
#include <thread> | |
#include <vector> | |
#include "json.hpp" | |
using json = nlohmann::json; | |
// Configuration | |
constexpr int START_BLOCK = 840000; | |
constexpr int BLOCKS_TO_FETCH = 1000; | |
constexpr int BATCH_SIZE = 2; | |
constexpr int ITERATIONS = 3; | |
struct Config { | |
std::string url; | |
std::string auth; | |
}; | |
// Benchmark result structure | |
struct BenchmarkResult { | |
std::string method; | |
int verbosity; | |
std::vector<double> times; | |
double mean() const { | |
return std::accumulate(times.begin(), times.end(), 0.0) / times.size(); | |
} | |
double stddev() const { | |
if (times.size() <= 1) return 0.0; | |
double m = mean(); | |
double sq_sum = std::accumulate(times.begin(), times.end(), 0.0, | |
[m](double acc, double x) { return acc + (x - m) * (x - m); }); | |
return std::sqrt(sq_sum / (times.size() - 1)); | |
} | |
}; | |
// Helper function for curl write callback | |
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* userp) { | |
userp->append((char*)contents, size * nmemb); | |
return size * nmemb; | |
} | |
// RPC request class | |
class RPCRequest { | |
private: | |
CURL* curl; | |
std::string response; | |
struct curl_slist* headers; | |
Config config; | |
public: | |
RPCRequest(const Config& cfg) : config(cfg) { | |
curl = curl_easy_init(); | |
headers = curl_slist_append(nullptr, "content-type: application/json"); | |
curl_easy_setopt(curl, CURLOPT_URL, config.url.c_str()); | |
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); | |
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
curl_easy_setopt(curl, CURLOPT_USERPWD, config.auth.c_str()); | |
} | |
~RPCRequest() { | |
curl_slist_free_all(headers); | |
curl_easy_cleanup(curl); | |
} | |
json makeRequest(const json& payload) { | |
std::string data = payload.dump(); | |
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); | |
response.clear(); | |
CURLcode res = curl_easy_perform(curl); | |
if (res != CURLE_OK) { | |
throw std::runtime_error(curl_easy_strerror(res)); | |
} | |
return json::parse(response)["result"]; | |
} | |
CURL* getCurl() { return curl; } | |
}; | |
class BlockFetcher { | |
protected: | |
int num_threads; | |
Config config; | |
public: | |
BlockFetcher(const Config& cfg) : num_threads(std::thread::hardware_concurrency()), config(cfg) {} | |
virtual ~BlockFetcher() = default; | |
virtual double fetchBlocks(int start_block, int num_blocks, int verbosity) = 0; | |
std::string getBlockHash(RPCRequest& rpc, int block_number) { | |
json payload = { | |
{"method", "getblockhash"}, | |
{"params", {block_number}}, | |
{"id", 1} | |
}; | |
return rpc.makeRequest(payload); | |
} | |
json getBlock(RPCRequest& rpc, const std::string& hash, int verbosity) { | |
json payload = { | |
{"method", "getblock"}, | |
{"params", {hash, verbosity}}, | |
{"id", 1} | |
}; | |
return rpc.makeRequest(payload); | |
} | |
}; | |
class SequentialFetcher : public BlockFetcher { | |
public: | |
using BlockFetcher::BlockFetcher; | |
double fetchBlocks(int start_block, int num_blocks, int verbosity) override { | |
auto start_time = std::chrono::high_resolution_clock::now(); | |
RPCRequest rpc(config); | |
for (int i = 0; i < num_blocks; ++i) { | |
auto hash = getBlockHash(rpc, start_block + i); | |
std::cout << " Sequential Getting info for block " << start_block + i << std::endl; | |
auto block = getBlock(rpc, hash, verbosity); | |
} | |
auto end_time = std::chrono::high_resolution_clock::now(); | |
return std::chrono::duration<double>(end_time - start_time).count(); | |
} | |
}; | |
class ThreadPoolFetcher : public BlockFetcher { | |
public: | |
using BlockFetcher::BlockFetcher; | |
double fetchBlocks(int start_block, int num_blocks, int verbosity) override { | |
auto start_time = std::chrono::high_resolution_clock::now(); | |
std::vector<std::future<void>> futures; | |
for (int i = 0; i < num_threads; ++i) { | |
futures.push_back(std::async(std::launch::async, [&, i]() { | |
RPCRequest rpc(config); | |
for (int j = i; j < num_blocks; j += num_threads) { | |
auto hash = getBlockHash(rpc, start_block + j); | |
std::cout << "thread approach getting block for height " << start_block + j << std::endl; | |
auto block = getBlock(rpc, hash, verbosity); | |
} | |
})); | |
} | |
for (auto& future : futures) { | |
future.wait(); | |
} | |
auto end_time = std::chrono::high_resolution_clock::now(); | |
return std::chrono::duration<double>(end_time - start_time).count(); | |
} | |
}; | |
void runBenchmark() { | |
Config config{"http://127.0.0.1:8332", "abubakar:sadeeq"}; | |
SequentialFetcher sequential(config); | |
ThreadPoolFetcher threadPool(config); | |
std::vector<std::pair<std::string, BlockFetcher*>> strategies = { | |
{"Sequential", &sequential}, | |
{"Thread Pool", &threadPool}, | |
}; | |
for (int verbosity = 1; verbosity <= 3; ++verbosity) { | |
std::cout << "Verbosity: " << verbosity << std::endl; | |
for (const auto& [name, fetcher] : strategies) { | |
std::cout << "Strategy: " << name << std::endl; | |
std::vector<double> times; | |
for (int iteration = 0; iteration < ITERATIONS; ++iteration) { | |
double time = fetcher->fetchBlocks(START_BLOCK, BLOCKS_TO_FETCH, verbosity); | |
times.push_back(time); | |
std::cout << "Iteration " << iteration + 1 << ": " << time << " seconds" << std::endl; | |
} | |
double mean = std::accumulate(times.begin(), times.end(), 0.0) / times.size(); | |
double sq_sum = std::inner_product(times.begin(), times.end(), times.begin(), 0.0); | |
double stdev = std::sqrt(sq_sum / times.size() - mean * mean); | |
std::cout << "Mean time: " << mean << " seconds" << std::endl; | |
std::cout << "Standard deviation: " << stdev << " seconds" << std::endl; | |
} | |
} | |
} | |
int main() { | |
try { | |
runBenchmark(); | |
} catch (const std::exception& ex) { | |
std::cerr << "Error: " << ex.what() << std::endl; | |
return 1; | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment