#include <iostream>
#include <random>
#include <vector>
#include <chrono>
#include <algorithm>
#include <numeric>
#include <iomanip>
#include "offset_allocator/offset_allocator.hpp"
using namespace mooncake::offset_allocator;
class OffsetAllocatorBenchHelper {
public:
OffsetAllocatorBenchHelper(uint64_t baseAddress, uint32_t poolSize,
uint32_t maxAllocs)
: pool_size_(poolSize),
allocated_size_(0),
allocator_(OffsetAllocator::create(baseAddress, poolSize, maxAllocs)),
rd_(),
gen_(rd_()) {}
void allocate(uint32_t size) {
while (true) {
auto handle = allocator_->allocate(size);
if (handle.has_value()) {
allocated_.push_back(std::move(*handle));
allocated_sizes_.push_back(size);
allocated_size_ += size;
break;
}
if (allocated_.size() == 0) {
break;
}
std::uniform_int_distribution<uint32_t> dist(0,
allocated_.size() - 1);
auto index = dist(gen_);
std::swap(allocated_[index], allocated_.back());
std::swap(allocated_sizes_[index], allocated_sizes_.back());
allocated_size_ -= allocated_sizes_.back();
allocated_.pop_back();
allocated_sizes_.pop_back();
}
}
double get_allocated_ratio() const {
return static_cast<double>(allocated_size_) / pool_size_;
}
private:
uint64_t pool_size_;
uint64_t allocated_size_;
std::shared_ptr<OffsetAllocator> allocator_;
std::vector<OffsetAllocationHandle> allocated_;
std::vector<uint32_t> allocated_sizes_;
std::random_device rd_;
std::mt19937 gen_;
};
template <typename BenchHelper>
void uniform_size_allocation_benchmark() {
std::cout << std::endl
<< "=== Uniform Size Allocation Benchmark ===" << std::endl;
const size_t max_pool_size = 2ull * 1024 * 1024 * 1024;
std::vector<uint32_t> allocation_sizes;
for (uint32_t i = 32; i < (1 << 26); i *= 4) {
allocation_sizes.push_back(i);
}
for (uint32_t i = 32; i < (1 << 26); i *= 4) {
allocation_sizes.push_back(i - 17);
}
for (uint32_t i = 32; i < (1 << 26); i *= 4) {
allocation_sizes.push_back(i + 17);
}
for (uint32_t i = 32; i < (1 << 26); i *= 4) {
allocation_sizes.push_back(i * 0.9);
}
for (uint32_t i = 32; i < (1 << 26); i *= 4) {
allocation_sizes.push_back(i * 1.1);
}
for (auto alloc_size : allocation_sizes) {
size_t pool_size =
alloc_size < 1024 ? max_pool_size / 16 : max_pool_size;
size_t max_allocs = pool_size / alloc_size + 10;
BenchHelper bench_helper(0x1000, pool_size, max_allocs);
int warmup_num = pool_size / alloc_size;
for (int i = 0; i < warmup_num; i++) {
bench_helper.allocate(alloc_size);
}
auto start_time = std::chrono::high_resolution_clock::now();
double min_util_ratio = 1.0;
double total_util_ratio = 0.0;
int benchmark_num = 1000000;
for (int i = 0; i < benchmark_num; i++) {
bench_helper.allocate(alloc_size);
double util_ratio = bench_helper.get_allocated_ratio();
if (util_ratio < min_util_ratio) {
min_util_ratio = util_ratio;
}
total_util_ratio += util_ratio;
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
end_time - start_time);
double avg_util_ratio = total_util_ratio / benchmark_num;
std::cout << "Alloc size: " << alloc_size
<< ", min util ratio: " << min_util_ratio
<< ", avg util ratio: " << avg_util_ratio
<< ", time: " << duration.count() / benchmark_num << " ns"
<< std::endl;
}
}
template <typename BenchHelper>
void random_size_allocation_benchmark() {
std::cout << std::endl
<< "=== Random Size Allocation Benchmark ===" << std::endl;
const size_t pool_size = 2ull * 1024 * 1024 * 1024;
const size_t max_alloc_size = 1ull << 26;
const size_t min_alloc_size = 1024;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32_t> dist(min_alloc_size,
max_alloc_size);
size_t max_allocs = pool_size / min_alloc_size + 10;
BenchHelper bench_helper(0x1000, pool_size, max_allocs);
for (size_t warmup_size = 0; warmup_size < pool_size;) {
size_t alloc_size = dist(gen);
bench_helper.allocate(alloc_size);
warmup_size += alloc_size;
}
int benchmark_num = 1000000;
std::vector<double> util_ratios;
util_ratios.reserve(benchmark_num);
auto start_time = std::chrono::high_resolution_clock::now();
for (int i = 0; i < benchmark_num; i++) {
size_t alloc_size = dist(gen);
bench_helper.allocate(alloc_size);
util_ratios.push_back(bench_helper.get_allocated_ratio());
}
auto end_time = std::chrono::high_resolution_clock::now();
const double avg_time_ns =
std::chrono::duration_cast<std::chrono::nanoseconds>(end_time -
start_time)
.count() /
static_cast<double>(benchmark_num);
std::sort(util_ratios.begin(), util_ratios.end());
const double min_util = util_ratios.front();
const double max_util = util_ratios.back();
const double p50 = util_ratios[util_ratios.size() * 0.50];
const double p90 = util_ratios[util_ratios.size() * 0.10];
const double p99 = util_ratios[util_ratios.size() * 0.01];
const double mean_util =
std::accumulate(util_ratios.begin(), util_ratios.end(), 0.0) /
util_ratios.size();
std::cout << std::fixed << std::setprecision(6);
std::cout << "util ratio (min / p99 / p90 / p50 / max / avg): " << min_util
<< " / " << p99 << " / " << p90 << " / " << p50 << " / "
<< max_util << " / " << mean_util << std::endl;
std::cout << "avg alloc time: " << avg_time_ns << " ns/op" << std::endl;
}
int main() {
std::cout << "=== OffsetAllocator Benchmark ===" << std::endl;
uniform_size_allocation_benchmark<OffsetAllocatorBenchHelper>();
random_size_allocation_benchmark<OffsetAllocatorBenchHelper>();
}