Add format benchmarks
This commit is contained in:
@@ -217,6 +217,11 @@ add_executable(bench_thread_pipeline benchmarks/bench_thread_pipeline.cpp)
|
||||
target_link_libraries(bench_thread_pipeline nanobench Threads::Threads)
|
||||
target_include_directories(bench_thread_pipeline PRIVATE src)
|
||||
|
||||
add_executable(bench_format_comparison benchmarks/bench_format_comparison.cpp
|
||||
src/arena_allocator.cpp src/format.cpp)
|
||||
target_link_libraries(bench_format_comparison nanobench)
|
||||
target_include_directories(bench_format_comparison PRIVATE src)
|
||||
|
||||
# Debug tools
|
||||
add_executable(
|
||||
debug_arena tools/debug_arena.cpp src/json_commit_request_parser.cpp
|
||||
@@ -238,3 +243,4 @@ add_test(NAME arena_allocator_benchmarks COMMAND bench_arena_allocator)
|
||||
add_test(NAME commit_request_benchmarks COMMAND bench_commit_request)
|
||||
add_test(NAME parser_comparison_benchmarks COMMAND bench_parser_comparison)
|
||||
add_test(NAME thread_pipeline_benchmarks COMMAND bench_thread_pipeline)
|
||||
add_test(NAME format_comparison_benchmarks COMMAND bench_format_comparison)
|
||||
|
||||
275
benchmarks/bench_format_comparison.cpp
Normal file
275
benchmarks/bench_format_comparison.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include "arena_allocator.hpp"
|
||||
#include "format.hpp"
|
||||
#include <cstdio>
|
||||
#include <iomanip>
|
||||
#include <nanobench.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#if __cpp_lib_format >= 201907L
|
||||
#include <format>
|
||||
#define HAS_STD_FORMAT 1
|
||||
#else
|
||||
#define HAS_STD_FORMAT 0
|
||||
#endif
|
||||
|
||||
// Test data for consistent benchmarks
|
||||
constexpr int TEST_INT = 42;
|
||||
constexpr double TEST_DOUBLE = 3.14159;
|
||||
const std::string TEST_STRING = "Hello World";
|
||||
|
||||
// Benchmark simple string concatenation: "Hello " + "World" + "!"
|
||||
void benchmark_simple_concatenation() {
|
||||
std::cout << "\n=== Simple String Concatenation: 'Hello World!' ===\n";
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench.title("Simple Concatenation").unit("op").warmup(100).epochs(1000);
|
||||
|
||||
// Arena-based static_format
|
||||
bench.run("static_format", [&] {
|
||||
ArenaAllocator arena(64);
|
||||
auto result = static_format(arena, "Hello ", "World", "!");
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// Arena-based format
|
||||
bench.run("format", [&] {
|
||||
ArenaAllocator arena(64);
|
||||
auto result = format(arena, "Hello %s!", "World");
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// std::stringstream
|
||||
bench.run("std::stringstream", [&] {
|
||||
std::stringstream ss;
|
||||
ss << "Hello " << "World" << "!";
|
||||
auto result = ss.str();
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
#if HAS_STD_FORMAT
|
||||
// std::format (C++20)
|
||||
bench.run("std::format", [&] {
|
||||
auto result = std::format("Hello {}!", "World");
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
#endif
|
||||
|
||||
// Raw snprintf with malloc
|
||||
bench.run("snprintf + malloc", [&] {
|
||||
char buffer[64];
|
||||
int len = std::snprintf(buffer, sizeof(buffer), "Hello %s!", "World");
|
||||
char *result = static_cast<char *>(std::malloc(len + 1));
|
||||
std::memcpy(result, buffer, len + 1);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
std::free(result);
|
||||
});
|
||||
}
|
||||
|
||||
// Benchmark mixed type formatting: "Count: 42, Rate: 3.14159"
|
||||
void benchmark_mixed_types() {
|
||||
std::cout << "\n=== Mixed Type Formatting: 'Count: 42, Rate: 3.14159' ===\n";
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench.title("Mixed Types").unit("op").warmup(100).epochs(1000);
|
||||
|
||||
// Arena-based static_format
|
||||
bench.run("static_format", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
auto result =
|
||||
static_format(arena, "Count: ", TEST_INT, ", Rate: ", TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// Arena-based format
|
||||
bench.run("format", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
auto result = format(arena, "Count: %d, Rate: %.5f", TEST_INT, TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// std::stringstream
|
||||
bench.run("std::stringstream", [&] {
|
||||
std::stringstream ss;
|
||||
ss << "Count: " << TEST_INT << ", Rate: " << std::fixed
|
||||
<< std::setprecision(5) << TEST_DOUBLE;
|
||||
auto result = ss.str();
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
#if HAS_STD_FORMAT
|
||||
// std::format (C++20)
|
||||
bench.run("std::format", [&] {
|
||||
auto result = std::format("Count: {}, Rate: {:.5f}", TEST_INT, TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
#endif
|
||||
|
||||
// Raw snprintf with malloc
|
||||
bench.run("snprintf + malloc", [&] {
|
||||
char buffer[128];
|
||||
int len = std::snprintf(buffer, sizeof(buffer), "Count: %d, Rate: %.5f",
|
||||
TEST_INT, TEST_DOUBLE);
|
||||
char *result = static_cast<char *>(std::malloc(len + 1));
|
||||
std::memcpy(result, buffer, len + 1);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
std::free(result);
|
||||
});
|
||||
}
|
||||
|
||||
// Benchmark complex formatting with precision and alignment
|
||||
void benchmark_complex_formatting() {
|
||||
std::cout << "\n=== Complex Formatting: '%-10s %5d %8.2f' ===\n";
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench.title("Complex Formatting").unit("op").warmup(100).epochs(1000);
|
||||
|
||||
// Arena-based format (static_format doesn't support printf specifiers)
|
||||
bench.run("format", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
auto result = format(arena, "%-10s %5d %8.2f", TEST_STRING.c_str(),
|
||||
TEST_INT, TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// std::stringstream
|
||||
bench.run("std::stringstream", [&] {
|
||||
std::stringstream ss;
|
||||
ss << std::left << std::setw(10) << TEST_STRING << " " << std::right
|
||||
<< std::setw(5) << TEST_INT << " " << std::setw(8) << std::fixed
|
||||
<< std::setprecision(2) << TEST_DOUBLE;
|
||||
auto result = ss.str();
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
#if HAS_STD_FORMAT
|
||||
// std::format (C++20)
|
||||
bench.run("std::format", [&] {
|
||||
auto result = std::format("{:<10} {:>5} {:>8.2f}", TEST_STRING, TEST_INT,
|
||||
TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
#endif
|
||||
|
||||
// Raw snprintf with malloc
|
||||
bench.run("snprintf + malloc", [&] {
|
||||
char buffer[128];
|
||||
int len = std::snprintf(buffer, sizeof(buffer), "%-10s %5d %8.2f",
|
||||
TEST_STRING.c_str(), TEST_INT, TEST_DOUBLE);
|
||||
char *result = static_cast<char *>(std::malloc(len + 1));
|
||||
std::memcpy(result, buffer, len + 1);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
std::free(result);
|
||||
});
|
||||
}
|
||||
|
||||
// Benchmark error message formatting (common use case)
|
||||
void benchmark_error_messages() {
|
||||
std::cout << "\n=== Error Message Formatting: 'Error 404: File not found "
|
||||
"(line 123)' ===\n";
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench.title("Error Messages").unit("op").warmup(100).epochs(1000);
|
||||
|
||||
constexpr int error_code = 404;
|
||||
constexpr int line_number = 123;
|
||||
const std::string error_msg = "File not found";
|
||||
|
||||
// Arena-based static_format (using std::string_view)
|
||||
bench.run("static_format", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
std::string_view error_msg_sv = error_msg;
|
||||
auto result = static_format(arena, "Error ", error_code, ": ", error_msg_sv,
|
||||
" (line ", line_number, ")");
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// Arena-based format
|
||||
bench.run("format", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
auto result = format(arena, "Error %d: %s (line %d)", error_code,
|
||||
error_msg.c_str(), line_number);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// std::stringstream
|
||||
bench.run("std::stringstream", [&] {
|
||||
std::stringstream ss;
|
||||
ss << "Error " << error_code << ": " << error_msg << " (line "
|
||||
<< line_number << ")";
|
||||
auto result = ss.str();
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
#if HAS_STD_FORMAT
|
||||
// std::format (C++20)
|
||||
bench.run("std::format", [&] {
|
||||
auto result = std::format("Error {}: {} (line {})", error_code, error_msg,
|
||||
line_number);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
// Benchmark memory allocation overhead by testing arena reuse
|
||||
void benchmark_memory_reuse() {
|
||||
std::cout << "\n=== Memory Allocation Patterns ===\n";
|
||||
|
||||
ankerl::nanobench::Bench bench;
|
||||
bench.title("Memory Patterns").unit("op").warmup(100).epochs(100);
|
||||
|
||||
// Arena with fresh allocation each time (realistic usage)
|
||||
bench.run("fresh arena", [&] {
|
||||
ArenaAllocator arena(128);
|
||||
auto result = format(arena, "Test %d: %s %.2f", TEST_INT,
|
||||
TEST_STRING.c_str(), TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// Pre-allocated arena (reuse scenario)
|
||||
ArenaAllocator shared_arena(1024);
|
||||
bench.run("reused arena", [&] {
|
||||
auto result = format(shared_arena, "Test %d: %s %.2f", TEST_INT,
|
||||
TEST_STRING.c_str(), TEST_DOUBLE);
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
|
||||
// Fresh std::string allocations
|
||||
bench.run("std::stringstream", [&] {
|
||||
std::stringstream ss;
|
||||
ss << "Test " << TEST_INT << ": " << TEST_STRING << " " << std::fixed
|
||||
<< std::setprecision(2) << TEST_DOUBLE;
|
||||
auto result = ss.str();
|
||||
ankerl::nanobench::doNotOptimizeAway(result);
|
||||
});
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Format Function Benchmark Comparison\n";
|
||||
std::cout << "====================================\n";
|
||||
|
||||
#if HAS_STD_FORMAT
|
||||
std::cout << "C++20 std::format: Available\n";
|
||||
#else
|
||||
std::cout << "C++20 std::format: Not available\n";
|
||||
#endif
|
||||
|
||||
benchmark_simple_concatenation();
|
||||
benchmark_mixed_types();
|
||||
benchmark_complex_formatting();
|
||||
benchmark_error_messages();
|
||||
benchmark_memory_reuse();
|
||||
|
||||
std::cout << "\n=== Summary ===\n";
|
||||
std::cout
|
||||
<< "* static_format: Best for simple concatenation with known types\n";
|
||||
std::cout
|
||||
<< "* format: Best for printf-style formatting with arena allocation\n";
|
||||
std::cout
|
||||
<< "* std::stringstream: Flexible but slower due to heap allocation\n";
|
||||
std::cout << "* std::format: Modern C++20 alternative (if available)\n";
|
||||
std::cout << "* snprintf + malloc: Low-level but requires manual memory "
|
||||
"management\n";
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -254,6 +254,25 @@ inline constexpr int max_decimal_length_v = decltype(term(T{}))::kMaxLength;
|
||||
|
||||
inline constexpr DoubleTerm term(double s) { return DoubleTerm(s); }
|
||||
|
||||
// Runtime string term for std::string_view (works with std::string, const
|
||||
// char*, etc.)
|
||||
struct StringViewTerm {
|
||||
explicit constexpr StringViewTerm(std::string_view s) : s(s) {}
|
||||
static constexpr int kMaxLength =
|
||||
512; // Conservative upper bound for runtime strings
|
||||
void write(char *&buf) const {
|
||||
std::memcpy(buf, s.data(), s.size());
|
||||
buf += s.size();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string_view s;
|
||||
};
|
||||
|
||||
inline constexpr StringViewTerm term(std::string_view s) {
|
||||
return StringViewTerm(s);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user