diff --git a/bench/producer_profile.cpp b/bench/producer_profile.cpp index ee0abe3..0bc8240 100644 --- a/bench/producer_profile.cpp +++ b/bench/producer_profile.cpp @@ -28,6 +28,7 @@ constexpr const char* kQueueBackend = "mutex_deque"; #endif std::uint64_t g_observer = 0; +std::atomic g_queue_completed{0}; std::size_t env_size(const char* name, std::size_t fallback) { if (const char* value = std::getenv(name)) { @@ -52,6 +53,7 @@ class ProfilingSink final : public logit::ILogger { public: enum class Mode { Synchronous, + ConstructAndInvokeTaskOnly, AsyncFullMessage, }; @@ -68,11 +70,16 @@ class ProfilingSink final : public logit::ILogger { } std::string payload = message; - logit::detail::TaskExecutor::get_instance().add_task( + std::function task = [this, payload = std::move(payload)]() mutable { g_observer += payload.size(); m_count.fetch_add(1, std::memory_order_relaxed); - }); + }; + if (m_mode == Mode::ConstructAndInvokeTaskOnly) { + task(); + return; + } + logit::detail::TaskExecutor::get_instance().add_task(std::move(task)); } std::string get_string_param(const logit::LoggerParam&) const override { return {}; } @@ -191,6 +198,67 @@ int main() { [](std::size_t) {}, warmup, total, repeats); std::cout << "case=logrecord_construct ns_per_call=" << record_ns << '\n'; + const double task_marker_ns = median_ns_per_call( + [](std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + std::function task = []() { ++g_observer; }; + task(); + } + }, + [](std::size_t) {}, warmup, total, repeats); + std::cout << "case=task_object_marker_only ns_per_call=" << task_marker_ns << '\n'; + + const double task_payload_ns = median_ns_per_call( + [](std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + std::string payload = kMessage; + std::function task = + [payload = std::move(payload)]() mutable { + g_observer += payload.size(); + }; + task(); + } + }, + [](std::size_t) {}, warmup, total, repeats); + std::cout << "case=task_object_full_message ns_per_call=" << task_payload_ns << '\n'; + + auto& task_executor = logit::detail::TaskExecutor::get_instance(); + const double queue_noop_ns = median_ns_per_call( + [&task_executor](std::size_t count) { + std::function task = []() { + g_queue_completed.fetch_add(1, std::memory_order_relaxed); + }; + for (std::size_t i = 0; i < count; ++i) { + task_executor.add_task(task); + } + }, + [&task_executor](std::size_t expected) { + task_executor.wait(); + const auto completed = g_queue_completed.load(std::memory_order_relaxed); + if (completed != expected) { + std::cerr << "prebuilt TaskExecutor completion mismatch\n"; + std::exit(7); + } + g_queue_completed.store(0, std::memory_order_relaxed); + }, warmup, total, repeats); + std::cout << "case=taskexecutor_enqueue_prebuilt_noop ns_per_call=" + << queue_noop_ns << '\n'; + + const double queue_payload_ns = median_ns_per_call( + [&task_executor](std::size_t count) { + for (std::size_t i = 0; i < count; ++i) { + std::string payload = kMessage; + task_executor.add_task( + [payload = std::move(payload)]() mutable { + g_observer += payload.size(); + }); + } + }, + [&task_executor](std::size_t) { task_executor.wait(); }, + warmup, total, repeats); + std::cout << "case=taskexecutor_enqueue_full_message ns_per_call=" + << queue_payload_ns << '\n'; + sink_ptr->set_mode(ProfilingSink::Mode::Synchronous); const double sync_ns = median_ns_per_call( log_prepared, @@ -205,6 +273,21 @@ int main() { warmup, total, repeats); std::cout << "case=logger_log_sync_null ns_per_call=" << sync_ns << '\n'; + sink_ptr->set_mode(ProfilingSink::Mode::ConstructAndInvokeTaskOnly); + const double dispatch_construct_invoke_ns = median_ns_per_call( + log_prepared, + [&logger, sink_ptr](std::size_t expected) { + logger.wait(); + if (sink_ptr->count() != expected) { + std::cerr << "construct-only sink count mismatch\n"; + std::exit(8); + } + sink_ptr->reset_count(); + }, + warmup, total, repeats); + std::cout << "case=logger_log_construct_and_invoke_full ns_per_call=" + << dispatch_construct_invoke_ns << '\n'; + sink_ptr->set_mode(ProfilingSink::Mode::AsyncFullMessage); auto task_completed = std::make_shared>(0); const double enqueue_ns = median_ns_per_call( diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 811980f..469cf7a 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -257,10 +257,22 @@ overlap rather than form an additive decomposition: - `string_copy_only` measures copying the prepared 200-byte payload; - `logrecord_construct` measures construction of the same `LogRecord` shape used by `LogItAdapter`; +- `task_object_marker_only` measures local `std::function` construction and + invocation without a payload; +- `task_object_full_message` adds the 200-byte payload ownership and capture; +- `taskexecutor_enqueue_prebuilt_noop` measures admission of a prebuilt + no-op task; the source callable is constructed once, but each `add_task` + call still includes by-value `std::function` copy/ownership transfer; +- `taskexecutor_enqueue_full_message` combines payload capture with queue + admission; +- `taskexecutor_enqueue_noop` retains the original shared-state capture probe; + it is intentionally not a pure queue-admission measurement; - `logger_log_sync_null` measures dispatch of a prepared record to a synchronous counting sink; -- `taskexecutor_enqueue_noop` measures direct `TaskExecutor` task admission with a no-op - completion task; +- `logger_log_construct_and_invoke_full` measures dispatch plus full task + construction and inline task-body invocation instead of enqueueing it; + the inline body is part of this timed producer-side probe and is not present + in the async producer timing region; - `logger_log_async_full_prepared` measures prepared-record dispatch plus full message task admission; - `prepared_record_plus_logger_async_full` adds per-call `LogRecord`