Tinker with benchmarks. Looking at render performance
This commit is contained in:
@@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
// Test data for consistent benchmarks
|
// Test data for consistent benchmarks
|
||||||
constexpr int TEST_INT = 42;
|
constexpr int TEST_INT = 42;
|
||||||
constexpr double TEST_DOUBLE = 3.14159;
|
constexpr double TEST_DOUBLE =
|
||||||
|
3.141592653589793; // Exact IEEE 754 representation of π
|
||||||
const std::string TEST_STRING = "Hello World";
|
const std::string TEST_STRING = "Hello World";
|
||||||
|
|
||||||
// Benchmark simple string concatenation: "Hello " + "World" + "!"
|
// Benchmark simple string concatenation: "Hello " + "World" + "!"
|
||||||
@@ -23,20 +24,21 @@ void benchmark_simple_concatenation() {
|
|||||||
std::cout << "\n=== Simple String Concatenation: 'Hello World!' ===\n";
|
std::cout << "\n=== Simple String Concatenation: 'Hello World!' ===\n";
|
||||||
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.title("Simple Concatenation").unit("op").warmup(100).epochs(1000);
|
bench.title("Simple Concatenation").unit("op").warmup(100);
|
||||||
|
|
||||||
|
ArenaAllocator arena(64);
|
||||||
// Arena-based static_format
|
// Arena-based static_format
|
||||||
bench.run("static_format", [&] {
|
bench.run("static_format", [&] {
|
||||||
ArenaAllocator arena(64);
|
|
||||||
auto result = static_format(arena, "Hello ", "World", "!");
|
auto result = static_format(arena, "Hello ", "World", "!");
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Arena-based format
|
// Arena-based format
|
||||||
bench.run("format", [&] {
|
bench.run("format", [&] {
|
||||||
ArenaAllocator arena(64);
|
|
||||||
auto result = format(arena, "Hello %s!", "World");
|
auto result = format(arena, "Hello %s!", "World");
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// std::stringstream
|
// std::stringstream
|
||||||
@@ -54,16 +56,6 @@ void benchmark_simple_concatenation() {
|
|||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
});
|
});
|
||||||
#endif
|
#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"
|
// Benchmark mixed type formatting: "Count: 42, Rate: 3.14159"
|
||||||
@@ -71,21 +63,22 @@ void benchmark_mixed_types() {
|
|||||||
std::cout << "\n=== Mixed Type Formatting: 'Count: 42, Rate: 3.14159' ===\n";
|
std::cout << "\n=== Mixed Type Formatting: 'Count: 42, Rate: 3.14159' ===\n";
|
||||||
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.title("Mixed Types").unit("op").warmup(100).epochs(1000);
|
bench.title("Mixed Types").unit("op").warmup(100);
|
||||||
|
|
||||||
|
ArenaAllocator arena(128);
|
||||||
// Arena-based static_format
|
// Arena-based static_format
|
||||||
bench.run("static_format", [&] {
|
bench.run("static_format", [&] {
|
||||||
ArenaAllocator arena(128);
|
|
||||||
auto result =
|
auto result =
|
||||||
static_format(arena, "Count: ", TEST_INT, ", Rate: ", TEST_DOUBLE);
|
static_format(arena, "Count: ", TEST_INT, ", Rate: ", TEST_DOUBLE);
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Arena-based format
|
// Arena-based format
|
||||||
bench.run("format", [&] {
|
bench.run("format", [&] {
|
||||||
ArenaAllocator arena(128);
|
|
||||||
auto result = format(arena, "Count: %d, Rate: %.5f", TEST_INT, TEST_DOUBLE);
|
auto result = format(arena, "Count: %d, Rate: %.5f", TEST_INT, TEST_DOUBLE);
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// std::stringstream
|
// std::stringstream
|
||||||
@@ -104,17 +97,6 @@ void benchmark_mixed_types() {
|
|||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
});
|
});
|
||||||
#endif
|
#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
|
// Benchmark complex formatting with precision and alignment
|
||||||
@@ -122,14 +104,15 @@ void benchmark_complex_formatting() {
|
|||||||
std::cout << "\n=== Complex Formatting: '%-10s %5d %8.2f' ===\n";
|
std::cout << "\n=== Complex Formatting: '%-10s %5d %8.2f' ===\n";
|
||||||
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.title("Complex Formatting").unit("op").warmup(100).epochs(1000);
|
bench.title("Complex Formatting").unit("op").warmup(100);
|
||||||
|
|
||||||
|
ArenaAllocator arena(128);
|
||||||
// Arena-based format (static_format doesn't support printf specifiers)
|
// Arena-based format (static_format doesn't support printf specifiers)
|
||||||
bench.run("format", [&] {
|
bench.run("format", [&] {
|
||||||
ArenaAllocator arena(128);
|
|
||||||
auto result = format(arena, "%-10s %5d %8.2f", TEST_STRING.c_str(),
|
auto result = format(arena, "%-10s %5d %8.2f", TEST_STRING.c_str(),
|
||||||
TEST_INT, TEST_DOUBLE);
|
TEST_INT, TEST_DOUBLE);
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// std::stringstream
|
// std::stringstream
|
||||||
@@ -150,17 +133,6 @@ void benchmark_complex_formatting() {
|
|||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
});
|
});
|
||||||
#endif
|
#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)
|
// Benchmark error message formatting (common use case)
|
||||||
@@ -169,26 +141,27 @@ void benchmark_error_messages() {
|
|||||||
"(line 123)' ===\n";
|
"(line 123)' ===\n";
|
||||||
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.title("Error Messages").unit("op").warmup(100).epochs(1000);
|
bench.title("Error Messages").unit("op").warmup(100);
|
||||||
|
|
||||||
constexpr int error_code = 404;
|
constexpr int error_code = 404;
|
||||||
constexpr int line_number = 123;
|
constexpr int line_number = 123;
|
||||||
const std::string error_msg = "File not found";
|
const std::string error_msg = "File not found";
|
||||||
|
|
||||||
|
ArenaAllocator arena(128);
|
||||||
// Arena-based static_format (using string literals only)
|
// Arena-based static_format (using string literals only)
|
||||||
bench.run("static_format", [&] {
|
bench.run("static_format", [&] {
|
||||||
ArenaAllocator arena(128);
|
|
||||||
auto result = static_format(arena, "Error ", error_code, ": ",
|
auto result = static_format(arena, "Error ", error_code, ": ",
|
||||||
"File not found", " (line ", line_number, ")");
|
"File not found", " (line ", line_number, ")");
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Arena-based format
|
// Arena-based format
|
||||||
bench.run("format", [&] {
|
bench.run("format", [&] {
|
||||||
ArenaAllocator arena(128);
|
|
||||||
auto result = format(arena, "Error %d: %s (line %d)", error_code,
|
auto result = format(arena, "Error %d: %s (line %d)", error_code,
|
||||||
error_msg.c_str(), line_number);
|
error_msg.c_str(), line_number);
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// std::stringstream
|
// std::stringstream
|
||||||
@@ -210,37 +183,73 @@ void benchmark_error_messages() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Benchmark memory allocation overhead by testing arena reuse
|
// Benchmark simple double formatting (common in metrics)
|
||||||
void benchmark_memory_reuse() {
|
void benchmark_double_formatting() {
|
||||||
std::cout << "\n=== Memory Allocation Patterns ===\n";
|
std::cout << "\n=== Simple Double Formatting ===\n";
|
||||||
|
|
||||||
|
// Validate that all formatters produce identical output
|
||||||
|
ArenaAllocator arena(128);
|
||||||
|
|
||||||
|
auto static_result = static_format(arena, TEST_DOUBLE);
|
||||||
|
auto format_result = format(arena, "%.17g", TEST_DOUBLE);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::setprecision(17) << TEST_DOUBLE;
|
||||||
|
auto stringstream_result = ss.str();
|
||||||
|
|
||||||
|
#if HAS_STD_FORMAT
|
||||||
|
auto std_format_result = std::format("{}", TEST_DOUBLE);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::cout << "Validation (note: precision algorithms may differ):\n";
|
||||||
|
std::cout << " static_format: '" << static_result
|
||||||
|
<< "' (length: " << static_result.length() << ")\n";
|
||||||
|
std::cout << " format(%.17g): '" << format_result
|
||||||
|
<< "' (length: " << format_result.length() << ")\n";
|
||||||
|
std::cout << " std::stringstream: '" << stringstream_result
|
||||||
|
<< "' (length: " << stringstream_result.length() << ")\n";
|
||||||
|
#if HAS_STD_FORMAT
|
||||||
|
std::cout << " std::format: '" << std_format_result
|
||||||
|
<< "' (length: " << std_format_result.length() << ")\n";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::cout
|
||||||
|
<< "Note: Different formatters may use different precision algorithms\n";
|
||||||
|
std::cout << "Proceeding with performance comparison...\n";
|
||||||
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.title("Memory Patterns").unit("op").warmup(100).epochs(100);
|
bench.title("Double Formatting").unit("op").warmup(100);
|
||||||
|
|
||||||
// Arena with fresh allocation each time (realistic usage)
|
// Arena-based static_format (double only)
|
||||||
bench.run("fresh arena", [&] {
|
bench.run("static_format(double)", [&] {
|
||||||
ArenaAllocator arena(128);
|
auto result = static_format(arena, TEST_DOUBLE);
|
||||||
auto result = format(arena, "Test %d: %s %.2f", TEST_INT,
|
|
||||||
TEST_STRING.c_str(), TEST_DOUBLE);
|
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pre-allocated arena (reuse scenario)
|
// Arena-based format with equivalent precision
|
||||||
ArenaAllocator shared_arena(1024);
|
bench.run("format(%.17g)", [&] {
|
||||||
bench.run("reused arena", [&] {
|
// Use %.17g to match static_format's full precision behavior
|
||||||
auto result = format(shared_arena, "Test %d: %s %.2f", TEST_INT,
|
auto result = format(arena, "%.17g", TEST_DOUBLE);
|
||||||
TEST_STRING.c_str(), TEST_DOUBLE);
|
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
arena.reset();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fresh std::string allocations
|
// std::stringstream (full precision)
|
||||||
bench.run("std::stringstream", [&] {
|
bench.run("std::stringstream", [&] {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Test " << TEST_INT << ": " << TEST_STRING << " " << std::fixed
|
ss << std::setprecision(17) << TEST_DOUBLE;
|
||||||
<< std::setprecision(2) << TEST_DOUBLE;
|
|
||||||
auto result = ss.str();
|
auto result = ss.str();
|
||||||
ankerl::nanobench::doNotOptimizeAway(result);
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#if HAS_STD_FORMAT
|
||||||
|
// std::format (C++20) - default formatting
|
||||||
|
bench.run("std::format", [&] {
|
||||||
|
auto result = std::format("{}", TEST_DOUBLE);
|
||||||
|
ankerl::nanobench::doNotOptimizeAway(result);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
@@ -257,7 +266,7 @@ int main() {
|
|||||||
benchmark_mixed_types();
|
benchmark_mixed_types();
|
||||||
benchmark_complex_formatting();
|
benchmark_complex_formatting();
|
||||||
benchmark_error_messages();
|
benchmark_error_messages();
|
||||||
benchmark_memory_reuse();
|
benchmark_double_formatting();
|
||||||
|
|
||||||
std::cout << "\n=== Summary ===\n";
|
std::cout << "\n=== Summary ===\n";
|
||||||
std::cout
|
std::cout
|
||||||
@@ -267,8 +276,6 @@ int main() {
|
|||||||
std::cout
|
std::cout
|
||||||
<< "* std::stringstream: Flexible but slower due to heap allocation\n";
|
<< "* std::stringstream: Flexible but slower due to heap allocation\n";
|
||||||
std::cout << "* std::format: Modern C++20 alternative (if available)\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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,50 +156,6 @@ int main() {
|
|||||||
[&]() { histogram.observe(2.0); });
|
[&]() { histogram.observe(2.0); });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render performance scaling
|
|
||||||
{
|
|
||||||
// Test render performance as number of metrics increases
|
|
||||||
std::vector<metric::Counter> counters;
|
|
||||||
std::vector<metric::Gauge> gauges;
|
|
||||||
std::vector<metric::Histogram> histograms;
|
|
||||||
|
|
||||||
auto counter_family =
|
|
||||||
metric::create_counter("scale_counter", "Scale counter");
|
|
||||||
auto gauge_family = metric::create_gauge("scale_gauge", "Scale gauge");
|
|
||||||
auto histogram_family = metric::create_histogram(
|
|
||||||
"scale_histogram", "Scale histogram",
|
|
||||||
std::initializer_list<double>{0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0,
|
|
||||||
50.0});
|
|
||||||
|
|
||||||
// Create varying numbers of metrics
|
|
||||||
for (int scale : {10, 100, 1000}) {
|
|
||||||
// Clear previous metrics by creating new families
|
|
||||||
// (Note: In real usage, metrics persist for application lifetime)
|
|
||||||
for (int i = 0; i < scale; ++i) {
|
|
||||||
counters.emplace_back(
|
|
||||||
counter_family.create({{"id", std::to_string(i)}}));
|
|
||||||
gauges.emplace_back(gauge_family.create({{"id", std::to_string(i)}}));
|
|
||||||
histograms.emplace_back(
|
|
||||||
histogram_family.create({{"id", std::to_string(i)}}));
|
|
||||||
|
|
||||||
// Set some values
|
|
||||||
counters.back().inc(static_cast<double>(i));
|
|
||||||
gauges.back().set(static_cast<double>(i * 2));
|
|
||||||
histograms.back().observe(static_cast<double>(i) * 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArenaAllocator arena;
|
|
||||||
std::string bench_name =
|
|
||||||
"render() - " + std::to_string(scale) + " metrics each type";
|
|
||||||
|
|
||||||
bench.run(bench_name, [&]() {
|
|
||||||
auto output = metric::render(arena);
|
|
||||||
ankerl::nanobench::doNotOptimizeAway(output);
|
|
||||||
arena.reset();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback metrics performance
|
// Callback metrics performance
|
||||||
{
|
{
|
||||||
auto counter_family =
|
auto counter_family =
|
||||||
@@ -245,5 +201,51 @@ int main() {
|
|||||||
callback_updater.join();
|
callback_updater.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render performance scaling
|
||||||
|
{
|
||||||
|
bench.unit("metric");
|
||||||
|
// Test render performance as number of metrics increases
|
||||||
|
std::vector<metric::Counter> counters;
|
||||||
|
std::vector<metric::Gauge> gauges;
|
||||||
|
std::vector<metric::Histogram> histograms;
|
||||||
|
|
||||||
|
auto counter_family =
|
||||||
|
metric::create_counter("scale_counter", "Scale counter");
|
||||||
|
auto gauge_family = metric::create_gauge("scale_gauge", "Scale gauge");
|
||||||
|
auto histogram_family = metric::create_histogram(
|
||||||
|
"scale_histogram", "Scale histogram",
|
||||||
|
std::initializer_list<double>{0.1, 0.5, 1.0, 2.5, 5.0, 10.0, 25.0,
|
||||||
|
50.0});
|
||||||
|
|
||||||
|
// Create varying numbers of metrics
|
||||||
|
for (int scale : {10, 100, 1000}) {
|
||||||
|
bench.batch(scale);
|
||||||
|
// Clear previous metrics by creating new families
|
||||||
|
// (Note: In real usage, metrics persist for application lifetime)
|
||||||
|
for (int i = 0; i < scale; ++i) {
|
||||||
|
counters.emplace_back(
|
||||||
|
counter_family.create({{"id", std::to_string(i)}}));
|
||||||
|
gauges.emplace_back(gauge_family.create({{"id", std::to_string(i)}}));
|
||||||
|
histograms.emplace_back(
|
||||||
|
histogram_family.create({{"id", std::to_string(i)}}));
|
||||||
|
|
||||||
|
// Set some values
|
||||||
|
counters.back().inc(static_cast<double>(i));
|
||||||
|
gauges.back().set(static_cast<double>(i * 2));
|
||||||
|
histograms.back().observe(static_cast<double>(i) * 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArenaAllocator arena;
|
||||||
|
std::string bench_name =
|
||||||
|
"render() - " + std::to_string(scale) + " metrics each type";
|
||||||
|
|
||||||
|
bench.run(bench_name, [&]() {
|
||||||
|
auto output = metric::render(arena);
|
||||||
|
ankerl::nanobench::doNotOptimizeAway(output);
|
||||||
|
arena.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -948,13 +948,7 @@ char *to_chars(char *first, const char *last, double value) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
void DoubleTerm::write(char *&buf) const {
|
void DoubleTerm::write(char *&buf) const { buf = to_chars(buf, nullptr, s); }
|
||||||
char scratch[kMaxLength];
|
|
||||||
char *end = to_chars(scratch, nullptr, s);
|
|
||||||
const auto len = end - scratch;
|
|
||||||
std::memcpy(buf, scratch, static_cast<std::size_t>(len));
|
|
||||||
buf += len;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user