Skip to content

Commit

Permalink
Merge pull request #13 from mjshakir/fix_cmake
Browse files Browse the repository at this point in the history
Fix cmake
  • Loading branch information
mjshakir authored Dec 1, 2024
2 parents b30bed4 + 15c2d55 commit a47dd2f
Show file tree
Hide file tree
Showing 15 changed files with 566 additions and 7 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/ubuntu_X86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v2

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libgtest-dev cmake
run: sudo apt-get update && sudo apt-get install -y libgtest-dev libbenchmark-dev cmake

- name: Setup CMake and Ninja
uses: lukka/[email protected]
Expand All @@ -35,4 +35,13 @@ jobs:

- name: Test
working-directory: build
run: ctest --output-on-failure
run: ctest --output-on-failure

- name: Run PriorityQueue Benchmarks
run: ./build/benchmarkbin/ThreadPool_PriorityQueue_Benchmark

- name: Run ThreadTask Benchmarks
run: ./build/benchmarkbin/ThreadPool_ThreadTask_Benchmark

- name: Run ThreadPool Benchmarks
run: ./build/benchmarkbin/ThreadPool_ThreadPool_Benchmark
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu_arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v2

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libgtest-dev cmake
run: sudo apt-get update && sudo apt-get install -y libgtest-dev libbenchmark-dev cmake

- name: Setup CMake and Ninja
uses: lukka/[email protected]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu_riscv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@v2

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libgtest-dev cmake
run: sudo apt-get update && sudo apt-get install -y libgtest-dev libbenchmark-dev cmake

- name: Setup CMake and Ninja
uses: lukka/[email protected]
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/windows_arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ jobs:
vcpkg search gtest
vcpkg install gtest:arm64-windows
vcpkg integrate install
- name: Install Benchmark
run: |
vcpkg search benchmark
vcpkg install benchmark:x64-windows
vcpkg integrate install
- name: Setup CMake and Ninja
uses: lukka/[email protected]
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/windows_x86_64.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ jobs:
vcpkg install gtest:x64-windows
vcpkg integrate install
- name: Install Benchmark
run: |
vcpkg search benchmark
vcpkg install benchmark:x64-windows
vcpkg integrate install
- name: Setup CMake and Ninja
uses: lukka/[email protected]

Expand Down
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
cmake_minimum_required(VERSION 3.15 FATAL_ERROR)
#------------------------------------------------------------------------------------------
# Determine if ThreadPool is built as a standalone project or included by other projects
set(THREADPOOL_STANDALONE_PROJECT OFF)
Expand All @@ -9,7 +9,7 @@ endif()
# Get the name of the folder and use it as the project name
get_filename_component(PARENT_DIR_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
string(REPLACE " " "_" PARENT_DIR_NAME ${PARENT_DIR_NAME})
project(${PARENT_DIR_NAME} VERSION 1.0 DESCRIPTION "A Threadpool function library" LANGUAGES CXX)
project(${PARENT_DIR_NAME} VERSION 1.13.0 DESCRIPTION "A Threadpool function library" LANGUAGES CXX)
#------------------------------------------------------------------------------------------
# Set the C++ standard
set(CMAKE_CXX_STANDARD 20)
Expand Down Expand Up @@ -55,6 +55,7 @@ endif()
option(BUILD_THREADPOOL_SHARED_LIBS "Build using shared libraries" ON)
option(BUILD_THREADPOOL_EXAMPLE "Build ThreadPool example" ${THREADPOOL_STANDALONE_PROJECT})
option(BUILD_THREADPOOL_TESTS "Build ThreadPool tests" ${THREADPOOL_STANDALONE_PROJECT})
option(BUILD_THREADPOOL_BENCHMARK "Build ThreadPool benchmarks" ${THREADPOOL_STANDALONE_PROJECT})
#------------------------------------------------------------------------------------------
# Force colored output
option(FORCE_COLORED_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." OFF)
Expand Down Expand Up @@ -150,6 +151,11 @@ if(BUILD_THREADPOOL_TESTS AND THREADPOOL_STANDALONE_PROJECT)
add_subdirectory(test)
endif()
#------------------------------------------------------------------------------------------
# Enable benchmarks and add the benchmark directory if this is a standalone project
if(BUILD_THREADPOOL_BENCHMARK AND THREADPOOL_STANDALONE_PROJECT)
add_subdirectory(benchmark)
endif()
#------------------------------------------------------------------------------------------
# Doxygen (only if it's a standalone project)
if(DOXYGEN_FOUND AND THREADPOOL_STANDALONE_PROJECT)
set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in)
Expand Down
52 changes: 52 additions & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Try Finding Installed Google Benchmark
find_package(benchmark QUIET)
#------------------------------------------------------------------------------------------
# Use Installed Version or Fetch
if(NOT benchmark_FOUND)
include(FetchContent)
FetchContent_Declare(
googlebenchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG main
GIT_SHALLOW TRUE # Do a shallow clone to speed up the process
)

# This line ensures Google Benchmark uses the same runtime library
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE)

FetchContent_MakeAvailable(googlebenchmark)
add_library(benchmark::benchmark ALIAS benchmark)
endif()
#------------------------------------------------------------------------------------------
# Function to setup a benchmark executable
function(create_benchmark_target TARGET_NAME SOURCE_FILE)
# Correcting the file path according to the directory structure
add_executable(${TARGET_NAME} ${SOURCE_FILE})
target_include_directories(${TARGET_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/include)

# Set the output directories for benchmark executables
set_target_properties(${TARGET_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/benchmarkbin
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/benchmarklib
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/benchmarklib
)

# Link the main project library and Google Benchmark
target_link_libraries(${TARGET_NAME} PRIVATE ${PROJECT_NAME})
if(benchmark_FOUND)
target_link_libraries(${TARGET_NAME} PRIVATE benchmark::benchmark)
else()
target_link_libraries(${TARGET_NAME} PRIVATE benchmark)
endif()

# Enable testing and add the benchmark to CTest
enable_testing()
add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME})
endfunction()
#------------------------------------------------------------------------------------------
# Add the benchmarks without the 'benchmark/' prefix since we are already in the benchmark directory
create_benchmark_target(${PROJECT_NAME}_PriorityQueue_Benchmark PriorityQueueBenchmark.cpp)
create_benchmark_target(${PROJECT_NAME}_ThreadTask_Benchmark ThreadTaskBenchmark.cpp)
create_benchmark_target(${PROJECT_NAME}_ThreadPool_Benchmark ThreadPoolBenchmark.cpp)
#------------------------------------------------------------------------------------------
103 changes: 103 additions & 0 deletions benchmark/PriorityQueueBenchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#include <random>
#include <vector>
#include <algorithm>

#include <benchmark/benchmark.h>
#include "PriorityQueue.hpp"

using namespace ThreadPool;

// Utility function to generate a random vector of integers
std::vector<int> generate_random_vector(size_t size, int min = 0, int max = 1000000) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(min, max);

std::vector<int> vec(size);
std::generate(vec.begin(), vec.end(), [&]() { return dis(gen); });
return vec;
}

static void BM_PriorityQueue_Push(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (auto _ : state) {
for (const auto& value : random_values) {
pq.push(value);
}
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_Push)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

static void BM_PriorityQueue_Emplace(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (auto _ : state) {
for (const auto& value : random_values) {
pq.emplace(value);
}
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_Emplace)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

static void BM_PriorityQueue_Top(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (const auto& value : random_values) {
pq.push(value);
}
for (auto _ : state) {
auto top = pq.top();
benchmark::DoNotOptimize(top);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_Top)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

static void BM_PriorityQueue_Pop(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (const auto& value : random_values) {
pq.push(value);
}
for (auto _ : state) {
if (!pq.empty()) {
pq.pop();
}
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_Pop)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

static void BM_PriorityQueue_Size(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (const auto& value : random_values) {
pq.push(value);
}
for (auto _ : state) {
auto size = pq.size();
benchmark::DoNotOptimize(size);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_Size)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

static void BM_PriorityQueue_RemoveTask(benchmark::State& state) {
PriorityQueue<int> pq;
auto random_values = generate_random_vector(state.range(0));
for (const auto& value : random_values) {
pq.push(value);
}
for (auto _ : state) {
if (!pq.empty()) {
pq.remove(random_values[state.range(0) / 2]);
}
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_PriorityQueue_RemoveTask)->RangeMultiplier(10)->Range(1, 10000)->Complexity(benchmark::oAuto);

BENCHMARK_MAIN();
98 changes: 98 additions & 0 deletions benchmark/ThreadPoolBenchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <thread>
#include <vector>
#include <future>
#include <chrono>
#include <benchmark/benchmark.h>
#include "ThreadPool.hpp"

// Utility function to simulate work
int simulate_work(int value) {
std::this_thread::sleep_for(std::chrono::nanoseconds(10)); // Reduced sleep time to avoid prolonged blocking
return value * 2;
}

// Benchmark for ThreadPool constructor and destructor
static void BM_ThreadPool_Constructor(benchmark::State& state) {
for (auto _ : state) {
ThreadPool::ThreadPool<ThreadPool::ThreadMode::STANDARD> pool(state.range(0));
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ThreadPool_Constructor)->RangeMultiplier(2)->Range(4, 64)->Complexity(benchmark::oAuto);

// Benchmark for queueing tasks
static void BM_ThreadPool_QueueTask(benchmark::State& state) {
ThreadPool::ThreadPool<ThreadPool::ThreadMode::STANDARD> pool(state.range(0));
for (auto _ : state) {
auto future = pool.queue(simulate_work, 42);
benchmark::DoNotOptimize(future);
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ThreadPool_QueueTask)->RangeMultiplier(2)->Range(4, 64)->Complexity(benchmark::oAuto);

// Benchmark for executing tasks
static void BM_ThreadPool_ExecuteTask(benchmark::State& state) {
ThreadPool::ThreadPool<ThreadPool::ThreadMode::STANDARD> pool(state.range(0));
for (auto _ : state) {
auto future = pool.queue(simulate_work, 42);
future.wait();
benchmark::DoNotOptimize(future.get());
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ThreadPool_ExecuteTask)->RangeMultiplier(2)->Range(4, 64)->Complexity(benchmark::oAuto);

// Benchmark for handling a burst of tasks
static void BM_ThreadPool_BurstTasks(benchmark::State& state) {
ThreadPool::ThreadPool<ThreadPool::ThreadMode::STANDARD> pool(state.range(0));
for (auto _ : state) {
std::vector<std::future<int>> futures;
for (int i = 0; i < state.range(1); ++i) {
futures.push_back(pool.queue(simulate_work, i));
}
for (auto& future : futures) {
benchmark::DoNotOptimize(future.get());
}
}
state.SetComplexityN(state.range(0) * state.range(1));
}
BENCHMARK(BM_ThreadPool_BurstTasks)->Ranges({{4, 64}, {10, 1000}})->Complexity(benchmark::oAuto);

// Benchmark for priority-based task execution
static void BM_ThreadPool_PriorityQueueTask(benchmark::State& state) {
ThreadPool::ThreadPool<ThreadPool::ThreadMode::PRIORITY> pool(state.range(0));
for (auto _ : state) {
auto task = pool.queue(true, simulate_work, 42);
task.set_priority(10);
benchmark::DoNotOptimize(task.get_future());
}
state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ThreadPool_PriorityQueueTask)->RangeMultiplier(2)->Range(4, 64)->Complexity(benchmark::oAuto);

BENCHMARK_MAIN();

/*
Time Complexity Analysis:
1. ThreadPool Constructor/Destructor:
- Complexity: O(1) per thread, as the number of threads created is fixed by the input range.
- Reasoning: The constructor creates the worker threads, and the destructor joins them.
2. Queueing a Task:
- Complexity: O(1) per task.
- Reasoning: Adding a task to the queue is a constant time operation.
3. Executing a Task:
- Complexity: O(1) per task.
- Reasoning: Each task execution is independent, so adding and retrieving a task is O(1).
4. Handling a Burst of Tasks:
- Complexity: O(N * M), where N is the number of threads and M is the number of tasks per burst.
- Reasoning: Each thread processes multiple tasks, and each task retrieval and execution takes O(1).
5. Priority Queue Task Execution:
- Complexity: O(log N) for adding a task, where N is the number of tasks in the priority queue.
- Reasoning: PriorityQueue uses a heap, and adding/removing elements takes O(log N) time.
*/
Loading

0 comments on commit a47dd2f

Please sign in to comment.