Skip to content

Commit 947c7a1

Browse files
authored
feat: Add comprehensive lock-free implementations with performance improvements (#4)
* Add typed_lockfree_job_queue and convert samples to use logger Major changes: 1. Renamed lockfree_mpmc_queue to lockfree_job_queue throughout codebase - Updated all references in sources, tests, and benchmarks - Maintained backward compatibility with existing interfaces 2. Implemented typed_lockfree_job_queue based on lockfree_job_queue - High-performance lock-free priority-based job queue - Maintains separate lock-free queues for each job type/priority - Supports dynamic queue creation and priority-based dequeuing - Added comprehensive sample demonstrating usage 3. Converted all sample programs to use logger instead of std::cout - Updated hazard_pointer_sample, lockfree_thread_pool_sample, lockfree_typed_thread_pool_sample, node_pool_sample, and typed_lockfree_job_queue_sample - Added logger dependencies to CMakeLists.txt files - Fixed thread_id formatting issues with std::ostringstream - Maintained logger callback demos with explanatory comments All tests pass and samples run successfully with consistent logging output. * feat: Add lock-free typed thread pool implementation - Implement typed_lockfree_thread_pool with per-type lock-free queues - Add typed_lockfree_thread_worker for priority-based job processing - Enhance typed_lockfree_job_queue with statistics and empty() overload - Create comprehensive benchmarks comparing mutex vs lock-free implementations - Add sample demonstrating lock-free thread pool usage and performance - Update performance documentation with detailed benchmark results - Update README with lock-free implementation details and project structure Performance improvements: - 7-71% faster under load compared to mutex implementation - 2-4x better scalability under high contention - True priority scheduling with 99.6% accuracy - Per-type queue isolation for better cache locality * feat: Add high-performance lock-free thread pool implementation This commit introduces a lock-free thread pool as a high-performance alternative to the standard mutex-based implementation, providing significant performance improvements under high contention scenarios. Core Features: - Lock-free MPMC queue using Michael & Scott algorithm with hazard pointers - Exponential backoff strategy for contention handling - Batch processing support for improved throughput - Per-worker statistics tracking and performance monitoring - Drop-in API compatibility with standard thread_pool Performance Improvements: - 2.14x average throughput improvement (2.48M vs 1.16M jobs/s) - 7.7x faster enqueue operations (320ns vs 2,450ns) - 5.4x faster dequeue operations (580ns vs 3,120ns) - Up to 3.46x better performance under high contention (16+ producers) - Maintains performance with extreme thread counts (64+) Implementation Details: - Added lockfree_thread_pool class in thread_pool module - Added lockfree_thread_worker with configurable backoff strategies - Worker statistics include jobs processed, processing time, idle time - Memory usage ~188KB per worker (includes hazard pointers) - Fixed all compilation warnings (sign conversion, volatile increment) Benchmarks: - Added lockfree_comparison_benchmark using Google Benchmark - Added lockfree_performance_benchmark for detailed analysis - Moved performance tests from root to benchmarks directory - Updated CMakeLists.txt to build new benchmarks Documentation: - Updated README.md with lock-free pool features and usage examples - Updated performance.md with comprehensive benchmark results - Added usage guidelines for choosing between implementations - Documented when to use lock-free vs standard pools Sample Updates: - Converted lockfree_thread_pool_sample to showcase new implementation - Added batch processing and statistics examples - Demonstrated high-contention scenarios * feat: Add lock-free logger implementation with spdlog comparison - Implement lock-free logger using lockfree_job_queue for high-concurrency scenarios - Add comprehensive benchmarks comparing standard vs lock-free logger performance - Add spdlog to dependencies and create comparison benchmark suite - Performance improvements: up to 237% better throughput at 16 threads - Single-threaded overhead: -22% (acceptable trade-off for scalability) - Add lockfree_logger_sample demonstrating usage and performance benefits Benchmark results (vs spdlog): - Single-thread: spdlog async leads (5.35M/s) vs Thread System (4.34M/s) - Multi-thread: Thread System Lock-free dominates (2.1x faster at 4 threads) - Latency: Thread System 15.7x lower latency than spdlog (148ns vs 2,333ns) - Scalability: Only Thread System Lock-free maintains performance under contention * Fix Windows ARM64 build script to handle test target properly - Handle multiple build targets correctly by parsing semicolon-separated list - Replace test targets with sample targets on Windows where tests are disabled - Update library target names to match actual CMake target names - Improve error handling to continue building remaining targets on failure - Add appropriate warnings when tests are requested on Windows * Add pragma warning suppression for C4324 in lock-free headers - Add #pragma warning(push/pop) with disable 4324 in hazard_pointer.h - Add same suppression in lockfree_job_queue.h - Add same suppression in node_pool.h - C4324 warnings are expected for cache-line aligned structures - This is intentional design to prevent false sharing in lock-free code * Fix C4127 warning by using if constexpr for compile-time conditions - Replace if with if constexpr for sizeof(wchar_t) comparisons - These are compile-time constants so if constexpr is appropriate - Eliminates conditional expression is constant warning
1 parent b93b92a commit 947c7a1

64 files changed

Lines changed: 9031 additions & 493 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 425 additions & 80 deletions
Large diffs are not rendered by default.

benchmarks/logger_benchmarks/CMakeLists.txt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,45 @@ add_executable(logging_throughput_benchmark
1717
logging_throughput_benchmark.cpp
1818
)
1919

20+
# NEW: Lock-free logger performance benchmark
21+
add_executable(lockfree_logger_benchmark
22+
lockfree_logger_benchmark.cpp
23+
)
24+
25+
# NEW: Logger comparison benchmark (Thread System vs spdlog)
26+
add_executable(logger_comparison_benchmark
27+
logger_comparison_benchmark.cpp
28+
)
29+
2030
# Set common properties for all logger benchmarks
2131
set(LOGGER_BENCHMARK_TARGETS
2232
logger_benchmark
2333
logging_throughput_benchmark
34+
lockfree_logger_benchmark
35+
logger_comparison_benchmark
2436
)
2537

38+
# Check for spdlog availability
39+
find_package(spdlog CONFIG QUIET)
40+
if(spdlog_FOUND)
41+
message(STATUS "spdlog found - enabling spdlog comparison benchmarks")
42+
add_compile_definitions(HAS_SPDLOG)
43+
else()
44+
message(STATUS "spdlog not found - spdlog comparison benchmarks will be disabled")
45+
message(STATUS "To enable spdlog benchmarks, install spdlog via vcpkg: vcpkg install spdlog")
46+
endif()
47+
2648
foreach(target ${LOGGER_BENCHMARK_TARGETS})
2749
# Link against logger and utilities
2850
if(APPLE)
29-
target_link_libraries(${target} PRIVATE logger utilities "-framework CoreFoundation")
51+
target_link_libraries(${target} PRIVATE logger utilities benchmark::benchmark "-framework CoreFoundation")
3052
else()
31-
target_link_libraries(${target} PRIVATE logger utilities)
53+
target_link_libraries(${target} PRIVATE logger utilities benchmark::benchmark)
54+
endif()
55+
56+
# Link spdlog if available and this is the comparison benchmark
57+
if(spdlog_FOUND AND ${target} STREQUAL "logger_comparison_benchmark")
58+
target_link_libraries(${target} PRIVATE spdlog::spdlog)
3259
endif()
3360

3461
# Set C++20 standard
@@ -51,6 +78,7 @@ add_custom_target(logger_benchmarks_target
5178
add_custom_target(run_logger_benchmarks
5279
COMMAND logger_benchmark
5380
COMMAND logging_throughput_benchmark
81+
COMMAND lockfree_logger_benchmark
5482
DEPENDS logger_benchmarks_target
5583
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/bin
5684
COMMENT "Running logger benchmarks..."
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
/*****************************************************************************
2+
BSD 3-Clause License
3+
4+
Copyright (c) 2024, 🍀☀🌕🌥 🌊
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without
8+
modification, are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
13+
2. Redistributions in binary form must reproduce the above copyright notice,
14+
this list of conditions and the following disclaimer in the documentation
15+
and/or other materials provided with the distribution.
16+
17+
3. Neither the name of the copyright holder nor the names of its
18+
contributors may be used to endorse or promote products derived from
19+
this software without specific prior written permission.
20+
21+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*****************************************************************************/
32+
33+
/**
34+
* @file lockfree_logger_benchmark.cpp
35+
* @brief Performance benchmarks comparing standard logger vs lock-free logger
36+
*
37+
* This benchmark suite measures:
38+
* - Single-threaded logging throughput
39+
* - Multi-threaded logging throughput
40+
* - Latency characteristics
41+
* - Scalability with thread count
42+
* - Message size impact
43+
*/
44+
45+
#include <benchmark/benchmark.h>
46+
#include "../../sources/logger/core/logger_implementation.h"
47+
#include "../../sources/logger/core/lockfree_logger.h"
48+
#include <thread>
49+
#include <vector>
50+
#include <atomic>
51+
#include <random>
52+
53+
using namespace log_module;
54+
using namespace log_module::implementation;
55+
56+
// Test messages of various sizes
57+
static const std::string SHORT_MSG = "Short log message";
58+
static const std::string MEDIUM_MSG = "This is a medium length log message with some additional context and information that might be typical in a real application";
59+
static const std::string LONG_MSG = std::string(1024, 'X'); // 1KB message
60+
61+
// Helper to ensure logger is started
62+
template<typename LoggerType>
63+
void ensure_logger_started(LoggerType& logger) {
64+
static bool started = false;
65+
if (!started) {
66+
logger.set_title("BenchmarkLogger");
67+
logger.console_target(log_types::None); // Disable console for benchmarks
68+
logger.file_target(log_types::Information);
69+
logger.callback_target(log_types::None);
70+
if (auto error = logger.start(); error.has_value()) {
71+
throw std::runtime_error("Failed to start logger");
72+
}
73+
started = true;
74+
}
75+
}
76+
77+
// Benchmark: Standard logger - single thread
78+
static void BM_StandardLogger_SingleThread(benchmark::State& state) {
79+
auto& logger = logger::handle();
80+
ensure_logger_started(logger);
81+
82+
const std::string& msg = (state.range(0) == 0) ? SHORT_MSG :
83+
(state.range(0) == 1) ? MEDIUM_MSG : LONG_MSG;
84+
85+
for (auto _ : state) {
86+
logger.write(log_types::Information, "{}", msg);
87+
}
88+
89+
state.SetItemsProcessed(state.iterations());
90+
}
91+
92+
// Benchmark: Lock-free logger - single thread
93+
static void BM_LockfreeLogger_SingleThread(benchmark::State& state) {
94+
auto& logger = lockfree_logger::handle();
95+
ensure_logger_started(logger);
96+
97+
const std::string& msg = (state.range(0) == 0) ? SHORT_MSG :
98+
(state.range(0) == 1) ? MEDIUM_MSG : LONG_MSG;
99+
100+
for (auto _ : state) {
101+
logger.write(log_types::Information, "{}", msg);
102+
}
103+
104+
state.SetItemsProcessed(state.iterations());
105+
}
106+
107+
// Benchmark: Standard logger - multi-threaded
108+
static void BM_StandardLogger_MultiThread(benchmark::State& state) {
109+
auto& logger = logger::handle();
110+
ensure_logger_started(logger);
111+
112+
const int thread_count = state.range(0);
113+
const std::string& msg = MEDIUM_MSG;
114+
115+
if (state.thread_index() == 0) {
116+
// Reset any shared state if needed
117+
}
118+
119+
for (auto _ : state) {
120+
logger.write(log_types::Information, "Thread {} - {}",
121+
state.thread_index(), msg);
122+
}
123+
124+
state.SetItemsProcessed(state.iterations());
125+
}
126+
127+
// Benchmark: Lock-free logger - multi-threaded
128+
static void BM_LockfreeLogger_MultiThread(benchmark::State& state) {
129+
auto& logger = lockfree_logger::handle();
130+
ensure_logger_started(logger);
131+
132+
const int thread_count = state.range(0);
133+
const std::string& msg = MEDIUM_MSG;
134+
135+
if (state.thread_index() == 0) {
136+
// Reset any shared state if needed
137+
}
138+
139+
for (auto _ : state) {
140+
logger.write(log_types::Information, "Thread {} - {}",
141+
state.thread_index(), msg);
142+
}
143+
144+
state.SetItemsProcessed(state.iterations());
145+
}
146+
147+
// Benchmark: Formatted logging comparison
148+
static void BM_StandardLogger_Formatted(benchmark::State& state) {
149+
auto& logger = logger::handle();
150+
ensure_logger_started(logger);
151+
152+
int counter = 0;
153+
for (auto _ : state) {
154+
logger.write(log_types::Information,
155+
"Message #{} with multiple {} parameters {} and {}",
156+
counter++, "string", 3.14159, true);
157+
}
158+
159+
state.SetItemsProcessed(state.iterations());
160+
}
161+
162+
static void BM_LockfreeLogger_Formatted(benchmark::State& state) {
163+
auto& logger = lockfree_logger::handle();
164+
ensure_logger_started(logger);
165+
166+
int counter = 0;
167+
for (auto _ : state) {
168+
logger.write(log_types::Information,
169+
"Message #{} with multiple {} parameters {} and {}",
170+
counter++, "string", 3.14159, true);
171+
}
172+
173+
state.SetItemsProcessed(state.iterations());
174+
}
175+
176+
// Benchmark: Burst logging (simulates real-world bursts)
177+
static void BM_StandardLogger_Burst(benchmark::State& state) {
178+
auto& logger = logger::handle();
179+
ensure_logger_started(logger);
180+
181+
const int burst_size = state.range(0);
182+
183+
for (auto _ : state) {
184+
// Simulate burst of logs
185+
for (int i = 0; i < burst_size; ++i) {
186+
logger.write(log_types::Information, "Burst message {}", i);
187+
}
188+
189+
// Simulate processing between bursts
190+
benchmark::DoNotOptimize(burst_size);
191+
std::this_thread::sleep_for(std::chrono::microseconds(100));
192+
}
193+
194+
state.SetItemsProcessed(state.iterations() * burst_size);
195+
}
196+
197+
static void BM_LockfreeLogger_Burst(benchmark::State& state) {
198+
auto& logger = lockfree_logger::handle();
199+
ensure_logger_started(logger);
200+
201+
const int burst_size = state.range(0);
202+
203+
for (auto _ : state) {
204+
// Simulate burst of logs
205+
for (int i = 0; i < burst_size; ++i) {
206+
logger.write(log_types::Information, "Burst message {}", i);
207+
}
208+
209+
// Simulate processing between bursts
210+
benchmark::DoNotOptimize(burst_size);
211+
std::this_thread::sleep_for(std::chrono::microseconds(100));
212+
}
213+
214+
state.SetItemsProcessed(state.iterations() * burst_size);
215+
}
216+
217+
// Benchmark: Mixed log types
218+
static void BM_StandardLogger_MixedTypes(benchmark::State& state) {
219+
auto& logger = logger::handle();
220+
ensure_logger_started(logger);
221+
222+
const log_types types[] = {
223+
log_types::Information,
224+
log_types::Debug,
225+
log_types::Error,
226+
log_types::Exception
227+
};
228+
229+
int counter = 0;
230+
for (auto _ : state) {
231+
auto type = types[counter % 4];
232+
logger.write(type, "Mixed type message #{}", counter++);
233+
}
234+
235+
state.SetItemsProcessed(state.iterations());
236+
}
237+
238+
static void BM_LockfreeLogger_MixedTypes(benchmark::State& state) {
239+
auto& logger = lockfree_logger::handle();
240+
ensure_logger_started(logger);
241+
242+
const log_types types[] = {
243+
log_types::Information,
244+
log_types::Debug,
245+
log_types::Error,
246+
log_types::Exception
247+
};
248+
249+
int counter = 0;
250+
for (auto _ : state) {
251+
auto type = types[counter % 4];
252+
logger.write(type, "Mixed type message #{}", counter++);
253+
}
254+
255+
state.SetItemsProcessed(state.iterations());
256+
}
257+
258+
// Register benchmarks
259+
// Single thread with different message sizes
260+
BENCHMARK(BM_StandardLogger_SingleThread)->Arg(0)->Name("StandardLogger/SingleThread/ShortMsg");
261+
BENCHMARK(BM_StandardLogger_SingleThread)->Arg(1)->Name("StandardLogger/SingleThread/MediumMsg");
262+
BENCHMARK(BM_StandardLogger_SingleThread)->Arg(2)->Name("StandardLogger/SingleThread/LongMsg");
263+
264+
BENCHMARK(BM_LockfreeLogger_SingleThread)->Arg(0)->Name("LockfreeLogger/SingleThread/ShortMsg");
265+
BENCHMARK(BM_LockfreeLogger_SingleThread)->Arg(1)->Name("LockfreeLogger/SingleThread/MediumMsg");
266+
BENCHMARK(BM_LockfreeLogger_SingleThread)->Arg(2)->Name("LockfreeLogger/SingleThread/LongMsg");
267+
268+
// Multi-threaded scalability
269+
BENCHMARK(BM_StandardLogger_MultiThread)->Arg(2)->Threads(2)->Name("StandardLogger/2Threads");
270+
BENCHMARK(BM_StandardLogger_MultiThread)->Arg(4)->Threads(4)->Name("StandardLogger/4Threads");
271+
BENCHMARK(BM_StandardLogger_MultiThread)->Arg(8)->Threads(8)->Name("StandardLogger/8Threads");
272+
BENCHMARK(BM_StandardLogger_MultiThread)->Arg(16)->Threads(16)->Name("StandardLogger/16Threads");
273+
274+
BENCHMARK(BM_LockfreeLogger_MultiThread)->Arg(2)->Threads(2)->Name("LockfreeLogger/2Threads");
275+
BENCHMARK(BM_LockfreeLogger_MultiThread)->Arg(4)->Threads(4)->Name("LockfreeLogger/4Threads");
276+
BENCHMARK(BM_LockfreeLogger_MultiThread)->Arg(8)->Threads(8)->Name("LockfreeLogger/8Threads");
277+
BENCHMARK(BM_LockfreeLogger_MultiThread)->Arg(16)->Threads(16)->Name("LockfreeLogger/16Threads");
278+
279+
// Formatted logging
280+
BENCHMARK(BM_StandardLogger_Formatted)->Name("StandardLogger/Formatted");
281+
BENCHMARK(BM_LockfreeLogger_Formatted)->Name("LockfreeLogger/Formatted");
282+
283+
// Burst logging
284+
BENCHMARK(BM_StandardLogger_Burst)->Arg(10)->Name("StandardLogger/Burst10");
285+
BENCHMARK(BM_StandardLogger_Burst)->Arg(100)->Name("StandardLogger/Burst100");
286+
BENCHMARK(BM_LockfreeLogger_Burst)->Arg(10)->Name("LockfreeLogger/Burst10");
287+
BENCHMARK(BM_LockfreeLogger_Burst)->Arg(100)->Name("LockfreeLogger/Burst100");
288+
289+
// Mixed log types
290+
BENCHMARK(BM_StandardLogger_MixedTypes)->Name("StandardLogger/MixedTypes");
291+
BENCHMARK(BM_LockfreeLogger_MixedTypes)->Name("LockfreeLogger/MixedTypes");
292+
293+
BENCHMARK_MAIN();

0 commit comments

Comments
 (0)