├── .cmake ├── icmake.cmake ├── icompiler.cmake ├── ideps.cmake ├── ilinker.cmake ├── imacro.cmake ├── ioption.cmake ├── ios.cmake └── template │ ├── catch2_benchmark_main.cpp │ └── catch2_test_main.cpp ├── .developers ├── vscode_example_c_cpp_properties.json ├── vscode_example_launch.json └── vscode_example_settings.json ├── .devops ├── restart.sh └── restart.txt ├── .github └── workflows │ ├── linux-clang-x64-asan-ubsan.yml │ ├── linux-clang-x64-static.yml │ ├── linux-clang-x64-tsan.yml │ ├── linux-clang-x64.yml │ ├── linux-gcc-x64-asan-ubsan.yml │ ├── linux-gcc-x64-static.yml │ ├── linux-gcc-x64-tsan.yml │ ├── linux-gcc-x64.yml │ ├── macos-x64-asan-ubsan.yml │ ├── macos-x64-tsan.yml │ └── macos-x64.yml ├── .gitignore ├── .image ├── channel_full.jpeg ├── channel_initial.jpeg ├── channel_nodata.jpeg ├── channel_one2each2_r0.jpeg ├── channel_one2each2_r1.jpeg ├── channel_one2each3_r0.jpeg ├── channel_one2each3_r1.jpeg ├── channel_one2each3_r2.jpeg ├── channel_one2one.jpeg ├── channel_somedata.jpeg ├── engine_perimeter.png └── logger.png ├── CMakeLists.txt ├── LICENSE ├── PreLoad.cmake ├── README.md ├── analytics ├── .gitignore ├── README.md ├── latency_hist.py └── requirements.txt ├── channel ├── CMakeLists.txt ├── README.md ├── example │ ├── CMakeLists.txt │ ├── echo.cpp │ └── ping_pong.cpp ├── include │ └── channel │ │ ├── channel_concept.h │ │ ├── channel_factory.h │ │ ├── one2each_seqnum_stream_object_queue.h │ │ ├── one2each_seqnum_stream_pod_queue.h │ │ ├── one2one_seqnum_stream_object_queue.h │ │ ├── one2one_seqnum_stream_pod_queue.h │ │ └── private │ │ ├── allocator_holder.h │ │ ├── channel_helper.h │ │ ├── one2each_seqnum_bucket.h │ │ ├── one2each_seqnum_stream_queue_impl.h │ │ ├── one2one_seqnum_bucket.h │ │ ├── one2one_seqnum_stream_queue_impl.h │ │ └── ring_buffer_factory.h ├── measure │ ├── CMakeLists.txt │ ├── data_heap.h │ ├── data_latency.h │ ├── data_plain.h │ ├── main.h │ ├── measure_one2each_stream_object_queue_heap.cpp │ ├── measure_one2each_stream_object_queue_heap_stream_allocator.cpp │ ├── measure_one2each_stream_object_queue_latency.cpp │ ├── measure_one2each_stream_object_queue_plain.cpp │ ├── measure_one2each_stream_pod_queue_latency.cpp │ ├── measure_one2each_stream_pod_queue_plain.cpp │ ├── measure_one2one_stream_object_queue_heap.cpp │ ├── measure_one2one_stream_object_queue_heap_stream_allocator.cpp │ ├── measure_one2one_stream_object_queue_latency.cpp │ ├── measure_one2one_stream_object_queue_plain.cpp │ ├── measure_one2one_stream_pod_queue_latency.cpp │ └── measure_one2one_stream_pod_queue_plain.cpp └── test │ ├── CMakeLists.txt │ └── test_stream_queue.cpp ├── compiler ├── CMakeLists.txt ├── README.md └── include │ └── compiler │ └── compiler.h ├── constant ├── CMakeLists.txt ├── README.md └── include │ └── constant │ └── constant.h ├── engine ├── CMakeLists.txt ├── README.md ├── example │ ├── CMakeLists.txt │ ├── engine_manual_config.cpp │ ├── engine_perimeter.cpp │ └── logical_cpu_demo.cpp ├── include │ └── engine │ │ ├── cpus_config.h │ │ ├── engine_main.h │ │ ├── private │ │ ├── engine.h │ │ └── logical_cpu.h │ │ └── task_storage.h ├── src │ ├── engine.cpp │ └── engine_main.cpp └── test │ ├── CMakeLists.txt │ ├── test_cpus_config.cpp │ ├── test_engine.cpp │ └── test_logical_cpu.cpp ├── logger ├── CMakeLists.txt ├── README.md ├── benchmark │ ├── CMakeLists.txt │ ├── benchmark_logger_async.cpp │ ├── benchmark_logger_construct.cpp │ ├── benchmark_logger_mthreads.cpp │ └── benchmark_logger_synch.cpp ├── example │ ├── CMakeLists.txt │ ├── logger_mthreads.cpp │ └── logger_simple.cpp ├── include │ └── logger │ │ ├── logger.h │ │ ├── logger_adapter.h │ │ ├── logger_client.h │ │ ├── logger_contract.h │ │ ├── logger_event.h │ │ ├── logger_extra_data.h │ │ ├── logger_level.h │ │ ├── logger_listener.h │ │ └── private │ │ ├── default_logger_listener.h │ │ └── logger_impl.h ├── src │ ├── default_logger_listener.cpp │ ├── logger_adapter.cpp │ ├── logger_client.cpp │ ├── logger_level.cpp │ └── logger_listener.cpp └── test │ ├── CMakeLists.txt │ ├── test_compile_time.cpp │ ├── test_logger_client.cpp │ ├── test_logger_event.cpp │ ├── test_logger_queue.cpp │ ├── test_logger_simple.cpp │ ├── test_tuple_format_accurately.cpp │ ├── test_tuple_format_every.cpp │ ├── test_tuple_print.cpp │ └── test_typer.cpp ├── memory ├── CMakeLists.txt ├── README.md ├── benchmark │ ├── CMakeLists.txt │ ├── benchmark_arena_allocator.cpp │ └── benchmark_stream_fixed_pool_allocator.cpp ├── include │ └── memory │ │ ├── arena_allocator.h │ │ ├── huge_page_allocator.h │ │ ├── page_allocator.h │ │ ├── private │ │ ├── constant.h │ │ └── mmap_page_allocator.h │ │ └── stream_fixed_pool_allocator.h └── test │ ├── CMakeLists.txt │ ├── test_arena_allocator.cpp │ ├── test_mmap_page_allocator.cpp │ └── test_stream_fixed_pool_allocator.cpp ├── misc ├── CMakeLists.txt ├── README.md ├── example │ ├── CMakeLists.txt │ ├── config_helper_demo.cpp │ ├── sigaction_demo.cpp │ ├── toml_demo.cpp │ └── toml_doc.h ├── include │ └── misc │ │ ├── config_helper.h │ │ ├── private │ │ └── signal_helper.inl │ │ └── signal_helper.h ├── src │ └── config_helper.cpp └── test │ ├── CMakeLists.txt │ ├── test_config_helper.cpp │ └── test_signal_helper.cpp ├── network ├── CMakeLists.txt ├── README.md └── example │ ├── CMakeLists.txt │ ├── multicast_hello_sender.cpp │ ├── multicast_helper.h │ ├── multicast_rtt_listener.cpp │ ├── multicast_rtt_sender.cpp │ ├── multicast_trace_listener.cpp │ ├── udp_echo_server.cpp │ ├── udp_helper.h │ └── udp_rtt_client.cpp ├── platform ├── CMakeLists.txt ├── README.md ├── example │ ├── CMakeLists.txt │ ├── bogatyr.cpp │ ├── china_cities.cpp │ ├── core_2_core_latancy.cpp │ ├── get_platform_info.cpp │ ├── greek_alphabet.cpp │ ├── set_thread_cpu.cpp │ ├── set_thread_name.cpp │ └── sysjitter.cpp ├── include │ └── platform │ │ ├── platform.h │ │ ├── private │ │ └── cmdline.h │ │ └── process_cpu_list.h ├── src │ ├── cmdline.cpp │ ├── platform.cpp │ └── process_cpu_list.cpp └── test │ ├── CMakeLists.txt │ ├── test_cmdline.cpp │ └── test_platform.cpp ├── timer ├── CMakeLists.txt ├── README.md ├── benchmark │ ├── CMakeLists.txt │ └── benchmark_timer.cpp └── include │ └── timer │ └── timer.h └── types ├── CMakeLists.txt ├── README.md ├── benchmark ├── CMakeLists.txt ├── benchmark_charcmp_vs_strcmp.cpp ├── benchmark_function_ref.cpp └── benchmark_memory_access.cpp ├── include └── types │ ├── box.h │ ├── function_ref.h │ ├── result.h │ ├── scope_exit.h │ └── temp_file.h └── test ├── CMakeLists.txt ├── test_box.cpp ├── test_function_ref.cpp ├── test_result.cpp ├── test_scope_exit.cpp └── test_sso.cpp /.cmake/icmake.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/.cmake) 2 | 3 | include(ios) 4 | 5 | if(NOT ((PROJECT_OS_LINUX OR PROJECT_OS_OSX) AND PROJECT_PROC_64BIT)) 6 | message(FATAL_ERROR "Only Linux/MacOS amd64/arm64 cpu supported.") 7 | endif() 8 | 9 | if(NOT CMAKE_BUILD_TYPE) 10 | #set(CMAKE_BUILD_TYPE Debug) 11 | set(CMAKE_BUILD_TYPE Release) 12 | endif() 13 | 14 | set(BUILD_SHARED_LIBS OFF) 15 | set(LIBRARY_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) 16 | set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}) 17 | 18 | include(ioption) 19 | include(icompiler) 20 | include(ilinker) 21 | 22 | ihft_setup_compiler_flags() 23 | ihft_setup_linker_flags() 24 | ihft_setup_tools_flags() 25 | 26 | include(ideps) 27 | include(imacro) 28 | 29 | if(NOT IHFT_BUILD_TOML) 30 | message(FATAL_ERROR "Building without toml is not supported at the moment.") 31 | endif() 32 | -------------------------------------------------------------------------------- /.cmake/ideps.cmake: -------------------------------------------------------------------------------- 1 | message(STATUS "Downloading dependencies...") 2 | 3 | if(${CMAKE_VERSION} VERSION_GREATER "3.23") 4 | cmake_policy(SET CMP0135 OLD) 5 | endif() 6 | 7 | include(FetchContent) 8 | #set(FETCHCONTENT_QUIET FALSE) 9 | 10 | # catch2 11 | if (IHFT_BUILD_UNITTESTS) 12 | FetchContent_Declare( 13 | Catch2 14 | URL https://codeload.github.com/catchorg/Catch2/tar.gz/refs/tags/v3.6.0 15 | URL_HASH MD5=86a9fec7afecaec687faaa988ac6818e 16 | ) 17 | FetchContent_GetProperties(Catch2) 18 | if(NOT Catch2_POPULATED) 19 | FetchContent_Populate(Catch2) 20 | #message(STATUS "${catch2_SOURCE_DIR} - ${catch2_BINARY_DIR}") 21 | add_subdirectory(${catch2_SOURCE_DIR} ${catch2_BINARY_DIR}) 22 | endif() 23 | 24 | target_compile_options(Catch2 PRIVATE -Wno-sign-conversion) 25 | target_compile_options(Catch2 PRIVATE -Wno-unused-parameter) 26 | target_compile_options(Catch2 PRIVATE -Wno-conversion) 27 | target_compile_options(Catch2 PRIVATE -Wno-implicit-int-float-conversion) 28 | 29 | add_library(catch2_test_main OBJECT ${PROJECT_SOURCE_DIR}/.cmake/template/catch2_test_main.cpp) 30 | target_link_libraries(catch2_test_main PUBLIC Catch2::Catch2WithMain) 31 | 32 | add_library(catch2_benchmark_main OBJECT ${PROJECT_SOURCE_DIR}/.cmake/template/catch2_benchmark_main.cpp) 33 | target_link_libraries(catch2_benchmark_main PUBLIC Catch2::Catch2WithMain) 34 | endif() 35 | 36 | # toml++ 37 | if (IHFT_BUILD_TOML) 38 | FetchContent_Declare( 39 | tomlplusplus 40 | URL https://codeload.github.com/marzer/tomlplusplus/tar.gz/refs/tags/v3.4.0 41 | URL_HASH MD5=c1f32ced14311fe949b9ce7cc3f7a867 42 | ) 43 | FetchContent_GetProperties(tomlplusplus) 44 | if(NOT tomlplusplus_POPULATED) 45 | FetchContent_Populate(tomlplusplus) 46 | #message(STATUS "${tomlplusplus_SOURCE_DIR} - ${tomlplusplus_BINARY_DIR}") 47 | add_subdirectory(${tomlplusplus_SOURCE_DIR} ${tomlplusplus_BINARY_DIR}) 48 | endif() 49 | endif() 50 | -------------------------------------------------------------------------------- /.cmake/imacro.cmake: -------------------------------------------------------------------------------- 1 | function(ihft_build_report) 2 | set(BUILD_INFO_BAR "====================================================================================") 3 | set(NOOP_STRING "") 4 | 5 | set(RUNTIME_MODE "shared") 6 | if(IHFT_LINK_STATIC) 7 | set(RUNTIME_MODE "static") 8 | endif() 9 | 10 | message(STATUS ${BUILD_INFO_BAR}) 11 | message(STATUS "Summary of the build:") 12 | message(STATUS ${BUILD_INFO_BAR}) 13 | message(STATUS "CMAKE: ${CMAKE_COMMAND} [${CMAKE_VERSION}]") 14 | message(STATUS "GENERATOR: ${CMAKE_GENERATOR}") 15 | if(BUILD_TOOL_VERSION) 16 | message(STATUS "BUILD_TOOL: ${CMAKE_BUILD_TOOL} [${BUILD_TOOL_VERSION}]") 17 | else() 18 | message(STATUS "BUILD_TOOL: ${CMAKE_BUILD_TOOL}") 19 | endif() 20 | message(STATUS "C_COMPILER: ${CMAKE_C_COMPILER} [${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}]") 21 | message(STATUS "CXX_COMPILER: ${CMAKE_CXX_COMPILER} [${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}]") 22 | message(STATUS "LINKER: ${CMAKE_LINKER}") 23 | message(STATUS "RUNTIME MODE: ${RUNTIME_MODE}") 24 | message(STATUS "CXX_RUNTIME: ${CXX_RUNTIME_LIBRARY}") 25 | message(STATUS "") 26 | message(STATUS "Build type : ${CMAKE_BUILD_TYPE}") 27 | message(STATUS "") 28 | message(STATUS "CMAKE_C_FLAGS: ${CMAKE_C_FLAGS}") 29 | message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}") 30 | message(STATUS "CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}") 31 | message(STATUS "CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") 32 | message(STATUS "") 33 | message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}") 34 | message(STATUS "CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}") 35 | message(STATUS ${BUILD_INFO_BAR}) 36 | message(STATUS ${NOOP_STRING}) 37 | endfunction() 38 | 39 | ############################################################################### 40 | 41 | function(ihft_add_test NAME) 42 | if (IHFT_BUILD_UNITTESTS) 43 | add_executable(${NAME} ${NAME}.cpp) 44 | add_test(NAME ${NAME} COMMAND ${NAME}) 45 | target_link_libraries(${NAME} PRIVATE catch2_test_main) 46 | else() 47 | # fake target to avoid problems with target_link_libraries 48 | add_executable(${NAME} EXCLUDE_FROM_ALL ${NAME}.cpp) 49 | endif() 50 | endfunction() 51 | 52 | ############################################################################### 53 | 54 | function(ihft_add_benchmark NAME) 55 | if (IHFT_BUILD_UNITTESTS) 56 | add_executable(${NAME} ${NAME}.cpp) 57 | target_link_libraries(${NAME} PRIVATE catch2_benchmark_main) 58 | else() 59 | # fake target to avoid problems with target_link_libraries 60 | add_executable(${NAME} EXCLUDE_FROM_ALL ${NAME}.cpp) 61 | endif() 62 | endfunction() 63 | -------------------------------------------------------------------------------- /.cmake/ioption.cmake: -------------------------------------------------------------------------------- 1 | option(IHFT_LINK_STATIC "Use static runtime for linkage. Compile dependency free executables." OFF) 2 | option(IHFT_LINK_STRIP "Strip binaries in linker stage." OFF) 3 | option(IHFT_MARCH_NATIVE "Using -march=native for code generation." OFF) 4 | option(IHFT_SANITIZER_ADDRESS "Using address sanitizers for project." OFF) 5 | option(IHFT_SANITIZER_THREAD "Using thread sanitizers for project." OFF) 6 | option(IHFT_SANITIZER_UB "Using undefined behavior sanitizer for project." OFF) 7 | option(IHFT_BUILD_UNITTESTS "Build unit tests for project." ON) 8 | option(IHFT_BUILD_TOML "Build tomlplusplus library for parsing configurations." ON) 9 | option(IHFT_PERF_PROFILING "Use -fno-omit-frame-pointer compile flag." ON) 10 | -------------------------------------------------------------------------------- /.cmake/ios.cmake: -------------------------------------------------------------------------------- 1 | # Check the host. 2 | 3 | if(UNIX) 4 | STRING(REGEX MATCH "Linux" PROJECT_OS_LINUX ${CMAKE_SYSTEM_NAME}) 5 | endif() 6 | 7 | if(APPLE) 8 | STRING(REGEX MATCH "Darwin" PROJECT_OS_OSX ${CMAKE_SYSTEM_NAME}) 9 | endif(APPLE) 10 | 11 | message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}") 12 | message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}") 13 | 14 | if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "x86_64") 15 | SET(PROJECT_PROC_64BIT TRUE BOOL INTERNAL) 16 | MESSAGE(STATUS "x86_64 cpu Detected") 17 | elseif(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64") 18 | SET(PROJECT_PROC_64BIT TRUE BOOL INTERNAL) 19 | MESSAGE(STATUS "arm64 cpu Detected") 20 | endif() 21 | -------------------------------------------------------------------------------- /.cmake/template/catch2_benchmark_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #define CATCH_CONFIG_ENABLE_BENCHMARKING 3 | #include 4 | #include 5 | -------------------------------------------------------------------------------- /.cmake/template/catch2_test_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /.developers/vscode_example_c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/lib/llvm-15/include/c++/v1/**", 8 | "/usr/include/c++/11/**" 9 | ], 10 | "defines": [], 11 | "compilerPath": "/usr/bin/gcc", 12 | "cStandard": "c11", 13 | "cppStandard": "c++20", 14 | "intelliSenseMode": "linux-clang-x64", 15 | "configurationProvider": "ms-vscode.cmake-tools" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /.developers/vscode_example_launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Используйте IntelliSense, чтобы узнать о возможных атрибутах. 3 | // Наведите указатель мыши, чтобы просмотреть описания существующих атрибутов. 4 | // Для получения дополнительной информации посетите: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "engine_bogatyr", 9 | "type": "lldb", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/build/engine_bogatyr", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.developers/vscode_example_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/build**": true, 5 | "**/Testing": true 6 | } 7 | } -------------------------------------------------------------------------------- /.devops/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Based on Erik Rigtorp tuning guide 4 | # https://rigtorp.se/low-latency-guide/ 5 | # 6 | # Kernel cmdline example 7 | # $ cat /proc/cmdline 8 | # BOOT_IMAGE=/boot/vmlinuz-5.13.0-28-generic root=UUID=76a2a6bf-b3b0-6fb6-9ab7-25cacb4c5f34 ro default_hugepagesz=1G isolcpus=6-11 nohz_full=6-11 rcu_nocbs=6-11 quiet splash vt.handoff=7 9 | # 10 | # Reserve several hugepages 11 | # $ echo 8 > /proc/sys/vm/nr_hugepages 12 | # 13 | 14 | if grep -q "isolcpus" /proc/cmdline; then 15 | echo isolcpus found 16 | CMDLINE=$(cat /proc/cmdline) 17 | for word in $CMDLINE 18 | do 19 | if [[ $word == *"isolcpus="* ]]; then 20 | cpus="$word" 21 | pattern="" 22 | cpus=${cpus/isolcpus=/$pattern} 23 | echo $cpus 24 | /usr/bin/tuna --cpus=$cpus --isolate 25 | fi 26 | done 27 | 28 | /usr/sbin/irqbalance --foreground --oneshot 29 | /usr/sbin/swapoff -a 30 | 31 | echo never > /sys/kernel/mm/transparent_hugepage/enabled 32 | echo 0 > /proc/sys/kernel/numa_balancing 33 | echo 0 > /sys/kernel/mm/ksm/run 34 | 35 | sysctl vm.stat_interval=60 36 | 37 | find /sys/devices/system/cpu -name scaling_governor -exec sh -c 'echo performance > {}' ';' 38 | 39 | # https://stackoverflow.com/questions/70521621/sending-and-receiving-multicast-on-the-same-linux-machine-from-different-interfa 40 | sysctl -w net.ipv4.conf.all.accept_local=1 41 | else 42 | echo isolcpus not found 43 | fi 44 | -------------------------------------------------------------------------------- /.devops/restart.txt: -------------------------------------------------------------------------------- 1 | # apply all low-latency optimizations 2 | # crontab -e 3 | @reboot /usr/local/bin/restart.sh 4 | -------------------------------------------------------------------------------- /.github/workflows/linux-clang-x64-asan-ubsan.yml: -------------------------------------------------------------------------------- 1 | name: linux-clang-x64-asan-ubsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up clang 13 | uses: egor-tensin/setup-clang@v1 14 | with: 15 | version: 18 16 | platform: x64 17 | - name: install-tools 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install cmake ninja-build clang-18 clang++-18 lld-18 libc++-18-dev libc++abi-18-dev libunwind-18-dev libclang-rt-18-dev 21 | - name: build 22 | env: 23 | CC: clang-18 24 | CXX: clang++-18 25 | TYPE: Release 26 | run: | 27 | $CC --version 28 | $CXX --version 29 | mkdir build && cd build 30 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_ADDRESS=1 -DIHFT_SANITIZER_UB=1 .. 31 | cmake --build . 32 | - name: test 33 | run: cd build && ctest 34 | -------------------------------------------------------------------------------- /.github/workflows/linux-clang-x64-static.yml: -------------------------------------------------------------------------------- 1 | name: linux-clang-x64-static 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up clang 13 | uses: egor-tensin/setup-clang@v1 14 | with: 15 | version: 18 16 | platform: x64 17 | - name: install-tools 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install cmake ninja-build clang-18 clang++-18 lld-18 libc++-18-dev libc++abi-18-dev libunwind-18-dev libclang-rt-18-dev 21 | - name: build 22 | env: 23 | CC: clang-18 24 | CXX: clang++-18 25 | TYPE: Release 26 | run: | 27 | $CC --version 28 | $CXX --version 29 | mkdir build && cd build 30 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_LINK_STATIC=1 .. 31 | cmake --build . 32 | - name: test 33 | run: cd build && ctest 34 | - name: info 35 | run: cd build && ./platform_get_info 36 | -------------------------------------------------------------------------------- /.github/workflows/linux-clang-x64-tsan.yml: -------------------------------------------------------------------------------- 1 | name: linux-clang-x64-tsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up clang 13 | uses: egor-tensin/setup-clang@v1 14 | with: 15 | version: 18 16 | platform: x64 17 | - name: install-tools 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install cmake ninja-build clang-18 clang++-18 lld-18 libc++-18-dev libc++abi-18-dev libunwind-18-dev libclang-rt-18-dev 21 | - name: build 22 | env: 23 | CC: clang-18 24 | CXX: clang++-18 25 | TYPE: Release 26 | run: | 27 | $CC --version 28 | $CXX --version 29 | mkdir build && cd build 30 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_THREAD=1 .. 31 | cmake --build . 32 | - name: test 33 | run: cd build && ctest 34 | -------------------------------------------------------------------------------- /.github/workflows/linux-clang-x64.yml: -------------------------------------------------------------------------------- 1 | name: linux-clang-x64 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up clang 13 | uses: egor-tensin/setup-clang@v1 14 | with: 15 | version: 18 16 | platform: x64 17 | - name: install-tools 18 | run: | 19 | sudo apt-get update 20 | sudo apt-get install cmake ninja-build clang-18 clang++-18 lld-18 libc++-18-dev libc++abi-18-dev libunwind-18-dev libclang-rt-18-dev 21 | - name: build 22 | env: 23 | CC: clang-18 24 | CXX: clang++-18 25 | TYPE: Release 26 | run: | 27 | $CC --version 28 | $CXX --version 29 | mkdir build && cd build 30 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} .. 31 | cmake --build . 32 | - name: test 33 | run: cd build && ctest 34 | - name: info 35 | run: cd build && ./platform_get_info 36 | -------------------------------------------------------------------------------- /.github/workflows/linux-gcc-x64-asan-ubsan.yml: -------------------------------------------------------------------------------- 1 | name: linux-gcc-x64-asan-ubsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: install-tools 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install cmake ninja-build gcc-11 g++-11 16 | - name: build 17 | env: 18 | CC: gcc-11 19 | CXX: g++-11 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_ADDRESS=1 -DIHFT_SANITIZER_UB=1 .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | -------------------------------------------------------------------------------- /.github/workflows/linux-gcc-x64-static.yml: -------------------------------------------------------------------------------- 1 | name: linux-gcc-x64-static 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: install-tools 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install cmake ninja-build gcc-11 g++-11 16 | - name: build 17 | env: 18 | CC: gcc-11 19 | CXX: g++-11 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_LINK_STATIC=1 .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | - name: info 30 | run: cd build && ./platform_get_info 31 | -------------------------------------------------------------------------------- /.github/workflows/linux-gcc-x64-tsan.yml: -------------------------------------------------------------------------------- 1 | name: linux-gcc-x64-tsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: install-tools 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install cmake ninja-build gcc-11 g++-11 16 | - name: build 17 | env: 18 | CC: gcc-11 19 | CXX: g++-11 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_THREAD=1 .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | -------------------------------------------------------------------------------- /.github/workflows/linux-gcc-x64.yml: -------------------------------------------------------------------------------- 1 | name: linux-gcc-x64 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | cloud-ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: install-tools 13 | run: | 14 | sudo apt-get update 15 | sudo apt-get install cmake ninja-build gcc-11 g++-11 16 | - name: build 17 | env: 18 | CC: gcc-11 19 | CXX: g++-11 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | - name: info 30 | run: cd build && ./platform_get_info 31 | -------------------------------------------------------------------------------- /.github/workflows/macos-x64-asan-ubsan.yml: -------------------------------------------------------------------------------- 1 | name: macos-x64-asan-ubsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | env: 8 | DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer 9 | jobs: 10 | cloud-ci: 11 | runs-on: macos-14 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: install-tools 15 | run: brew install cmake ninja 16 | - name: build 17 | env: 18 | CC: clang 19 | CXX: clang++ 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_ADDRESS=1 -DIHFT_SANITIZER_UB=1 .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | -------------------------------------------------------------------------------- /.github/workflows/macos-x64-tsan.yml: -------------------------------------------------------------------------------- 1 | name: macos-x64-tsan 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | env: 8 | DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer 9 | jobs: 10 | cloud-ci: 11 | runs-on: macos-14 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: install-tools 15 | run: brew install cmake ninja 16 | - name: build 17 | env: 18 | CC: clang 19 | CXX: clang++ 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} -DIHFT_SANITIZER_THREAD=1 .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | -------------------------------------------------------------------------------- /.github/workflows/macos-x64.yml: -------------------------------------------------------------------------------- 1 | name: macos-x64 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | env: 8 | DEVELOPER_DIR: /Applications/Xcode_15.3.app/Contents/Developer 9 | jobs: 10 | cloud-ci: 11 | runs-on: macos-14 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: install-tools 15 | run: brew install cmake ninja 16 | - name: build 17 | env: 18 | CC: clang 19 | CXX: clang++ 20 | TYPE: Release 21 | run: | 22 | $CC --version 23 | $CXX --version 24 | mkdir build && cd build 25 | cmake -GNinja -DCMAKE_BUILD_TYPE=${TYPE} .. 26 | cmake --build . 27 | - name: test 28 | run: cd build && ctest 29 | - name: info 30 | run: cd build && ./platform_get_info 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | .vscode 3 | ctags_tag 4 | build*/ 5 | Testing/* 6 | -------------------------------------------------------------------------------- /.image/channel_full.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_full.jpeg -------------------------------------------------------------------------------- /.image/channel_initial.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_initial.jpeg -------------------------------------------------------------------------------- /.image/channel_nodata.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_nodata.jpeg -------------------------------------------------------------------------------- /.image/channel_one2each2_r0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2each2_r0.jpeg -------------------------------------------------------------------------------- /.image/channel_one2each2_r1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2each2_r1.jpeg -------------------------------------------------------------------------------- /.image/channel_one2each3_r0.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2each3_r0.jpeg -------------------------------------------------------------------------------- /.image/channel_one2each3_r1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2each3_r1.jpeg -------------------------------------------------------------------------------- /.image/channel_one2each3_r2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2each3_r2.jpeg -------------------------------------------------------------------------------- /.image/channel_one2one.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_one2one.jpeg -------------------------------------------------------------------------------- /.image/channel_somedata.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/channel_somedata.jpeg -------------------------------------------------------------------------------- /.image/engine_perimeter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/engine_perimeter.png -------------------------------------------------------------------------------- /.image/logger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/proydakov/ihft/cd3cb5e551a06b39cb49dc7bc5436c13a6b545cf/.image/logger.png -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #============================================================================== 2 | # Copyright (c) 2020-2024 Evgeny Proydakov 3 | #============================================================================== 4 | 5 | # Offline mode: 6 | # cmake -DFETCHCONTENT_UPDATES_DISCONNECTED=ON .. 7 | # More details here: 8 | # https://stackoverflow.com/questions/67408357/how-to-avoid-update-checks-with-cmake-fetchcontent 9 | 10 | cmake_minimum_required(VERSION 3.12.0) 11 | project(IHFT) 12 | 13 | message(STATUS "create IHFT") 14 | 15 | include(.cmake/icmake.cmake) 16 | 17 | enable_testing() 18 | 19 | add_subdirectory(compiler) 20 | add_subdirectory(constant) 21 | add_subdirectory(timer) 22 | add_subdirectory(types) 23 | add_subdirectory(misc) 24 | add_subdirectory(memory) 25 | add_subdirectory(channel) 26 | add_subdirectory(platform) 27 | add_subdirectory(logger) 28 | add_subdirectory(network) 29 | add_subdirectory(engine) 30 | 31 | ihft_build_report() 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020-2025, Evgeny Proydakov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /PreLoad.cmake: -------------------------------------------------------------------------------- 1 | # Use Ninja instead of Unix Makefiles by default. 2 | # https://stackoverflow.com/questions/11269833/cmake-selecting-a-generator-within-cmakelists-txt 3 | # 4 | # Reason: it have better startup time than make and it parallelize jobs more uniformly. 5 | # (when comparing to make with Makefiles that was generated by CMake) 6 | # 7 | # How to install Ninja on Ubuntu: 8 | # sudo apt-get install ninja-build 9 | 10 | if(NOT WIN32) 11 | find_program(NINJA_PATH ninja) 12 | if(NINJA_PATH) 13 | execute_process(COMMAND ninja --version OUTPUT_VARIABLE NINJA_VERSION) 14 | string(REGEX REPLACE "\n$" "" NINJA_VERSION "${NINJA_VERSION}") 15 | set(GENERATOR_PATH "${NINJA_PATH}" CACHE INTERNAL "" FORCE) 16 | set(BUILD_TOOL_VERSION "${NINJA_VERSION}" CACHE INTERNAL "" FORCE) 17 | set(CMAKE_GENERATOR "Ninja" CACHE INTERNAL "" FORCE) 18 | endif() 19 | endif() 20 | -------------------------------------------------------------------------------- /analytics/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /analytics/README.md: -------------------------------------------------------------------------------- 1 | # ihft::analytics 2 | 3 | This module contains auxiliary python code for analyzing benchmark results. 4 | -------------------------------------------------------------------------------- /analytics/latency_hist.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | 5 | for x in range(1, len(sys.argv)): 6 | fname = sys.argv[x] 7 | 8 | x = np.loadtxt(fname, delimiter="\n") 9 | 10 | q = [50, 75, 80, 95, 99, 99.9, 100] 11 | p = np.percentile(x, q = q) 12 | 13 | fig, ax = plt.subplots() 14 | fig.canvas.set_window_title('Latency hist for: ' + fname) 15 | 16 | print(f"file: {fname}") 17 | plt.hist(x, density=True, bins=256, range=(0, p[3] * 2)) 18 | plt.xlabel('Latency (nanoseconds)'); 19 | 20 | print(f"samples: {len(x)}") 21 | for i in range(len(q)): 22 | perc = q[i] 23 | cycl = int(p[i]) 24 | print(f"percentile[{perc}]: {cycl}us") 25 | 26 | plt.show() 27 | -------------------------------------------------------------------------------- /analytics/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | -------------------------------------------------------------------------------- /channel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_channel INTERFACE) 2 | target_include_directories(ihft_channel INTERFACE include) 3 | target_link_libraries(ihft_channel INTERFACE ihft_constant) 4 | 5 | add_subdirectory(test) 6 | add_subdirectory(example) 7 | add_subdirectory(measure) 8 | -------------------------------------------------------------------------------- /channel/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | add_executable(channel_echo echo.cpp) 3 | target_link_libraries(channel_echo ihft_channel) 4 | 5 | add_executable(channel_ping_pong ping_pong.cpp) 6 | target_link_libraries(channel_ping_pong ihft_channel ihft_constant) 7 | -------------------------------------------------------------------------------- /channel/example/echo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using namespace ihft::channel; 12 | 13 | int main() 14 | { 15 | using spsc_queue_t = one2one_seqnum_stream_object_queue; 16 | 17 | constexpr size_t queue_size = 1000; // will be rounded up to 1024 18 | constexpr size_t readers_count = 1; // spsc queue 19 | 20 | auto opt = channel_factory::make(queue_size, readers_count); 21 | if (not opt) 22 | { 23 | std::cerr << "[main thread] can't create a queue." << std::endl; 24 | return EXIT_FAILURE; 25 | } 26 | 27 | auto& p = opt->producer; 28 | auto& c = opt->consumers.front(); 29 | 30 | std::cout << "[main thread] spawn threads" << std::endl; 31 | 32 | std::atomic_bool done{false}; 33 | std::vector threads; 34 | threads.emplace_back([p = std::move(p), &done]() mutable 35 | { 36 | std::string buffer; 37 | std::cout << "[writer thread] type string value or to exit..." << std::endl; 38 | while(std::getline(std::cin, buffer) and not buffer.empty()) 39 | { 40 | p.try_write(std::move(buffer)); 41 | } 42 | done.store(true); 43 | }); 44 | 45 | threads.emplace_back([c = std::move(c), &done]() mutable 46 | { 47 | while(not done.load(std::memory_order_relaxed)) 48 | { 49 | auto opt = c.try_read(); 50 | if (opt) 51 | { 52 | auto const& cref = static_cast(*opt); 53 | std::cout << "[read thread] got: " << cref << std::endl; 54 | } 55 | else 56 | { 57 | using namespace std::chrono_literals; 58 | std::this_thread::sleep_for(250ms); 59 | } 60 | } 61 | }); 62 | 63 | for(auto& t : threads) 64 | { 65 | t.join(); 66 | } 67 | 68 | std::cout << "[main thread] done. See you next time)." << std::endl; 69 | 70 | return EXIT_SUCCESS; 71 | } 72 | -------------------------------------------------------------------------------- /channel/include/channel/channel_concept.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::channel 6 | { 7 | template 8 | concept seqnum_counter = std::is_same_v || 9 | std::is_same_v || 10 | std::is_same_v || 11 | std::is_same_v 12 | ; 13 | 14 | template 15 | concept complex_event = std::is_nothrow_move_constructible_v; 16 | 17 | template 18 | concept plain_event = std::is_trivially_copyable_v && 19 | std::is_nothrow_move_constructible_v 20 | ; 21 | } 22 | -------------------------------------------------------------------------------- /channel/include/channel/channel_factory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ihft::channel 8 | { 9 | 10 | class channel_factory final 11 | { 12 | public: 13 | template 14 | struct producer_and_consumers final 15 | { 16 | producer_and_consumers(std::size_t n) 17 | : producer(n) 18 | { 19 | } 20 | 21 | template> 22 | producer_and_consumers(std::size_t n, std::unique_ptr content_allocator) 23 | : producer(n, std::move(content_allocator)) 24 | { 25 | } 26 | 27 | template 28 | auto& get() & 29 | { 30 | static_assert(I < 2); 31 | if constexpr (I == 0) return producer; 32 | else if constexpr (I == 1) return consumers; 33 | } 34 | 35 | queue_t producer; 36 | std::vector consumers; 37 | }; 38 | 39 | template 40 | using pc_t = producer_and_consumers; 41 | 42 | template 43 | using opc_t = std::optional>; 44 | 45 | // factory methods 46 | 47 | template 48 | static opc_t make(std::size_t queue_capacity, std::size_t readers_count) 49 | { 50 | if (0 == readers_count) 51 | { 52 | return std::nullopt; 53 | } 54 | 55 | auto result = std::make_optional>(queue_capacity); 56 | if (not result) 57 | { 58 | return std::nullopt; 59 | } 60 | auto& pair = *result; 61 | for(std::size_t i = 0; i < readers_count; i++) 62 | { 63 | auto reader = pair.producer.create_reader(); 64 | if (not reader) 65 | { 66 | return std::nullopt; 67 | } 68 | pair.consumers.emplace_back(std::move(*reader)); 69 | } 70 | return result; 71 | } 72 | 73 | template> 74 | static opc_t make(size_t queue_capacity, size_t readers_count, std::unique_ptr content_allocator) 75 | { 76 | if (0 == readers_count) 77 | { 78 | return std::nullopt; 79 | } 80 | 81 | auto result = std::make_optional>(queue_capacity, std::move(content_allocator)); 82 | if (not result) 83 | { 84 | return std::nullopt; 85 | } 86 | auto& pair = *result; 87 | for(std::size_t i = 0; i < readers_count; i++) 88 | { 89 | auto reader = pair.producer.create_reader(); 90 | if (not reader) 91 | { 92 | return std::nullopt; 93 | } 94 | pair.consumers.emplace_back(std::move(*reader)); 95 | } 96 | return result; 97 | } 98 | }; 99 | 100 | } 101 | -------------------------------------------------------------------------------- /channel/include/channel/private/allocator_holder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ihft::channel::impl 4 | { 5 | 6 | struct empty_allocator 7 | { 8 | }; 9 | 10 | template 11 | struct allocator_holder 12 | { 13 | public: 14 | allocator_holder(content_allocator_t* ptr) noexcept 15 | : m_content_allocator(ptr) 16 | { 17 | } 18 | 19 | ~allocator_holder() noexcept 20 | { 21 | m_content_allocator = nullptr; 22 | } 23 | 24 | content_allocator_t& get_content_allocator() noexcept 25 | { 26 | return *m_content_allocator; 27 | } 28 | 29 | private: 30 | content_allocator_t* m_content_allocator; 31 | }; 32 | 33 | template<> 34 | struct allocator_holder 35 | { 36 | }; 37 | 38 | } // ihft::channel::impl 39 | -------------------------------------------------------------------------------- /channel/include/channel/private/channel_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace ihft::channel::impl 9 | { 10 | 11 | struct channel_helper final 12 | { 13 | template 14 | constexpr static std::size_t to2pow(std::size_t n) noexcept 15 | { 16 | static_assert(std::is_unsigned_v, "Counter type must be unsigned."); 17 | 18 | constexpr std::size_t min_valid_pow = 4; 19 | if (n <= min_valid_pow) 20 | { 21 | return min_valid_pow; 22 | } 23 | 24 | constexpr auto max_valid_pow = std::size_t(1) << (sizeof(T) * 8 - 2); 25 | if (n >= max_valid_pow) 26 | { 27 | return max_valid_pow; 28 | } 29 | 30 | std::size_t power = min_valid_pow; 31 | while(power < n) 32 | { 33 | power *= 2; 34 | } 35 | return std::min(max_valid_pow, power); 36 | } 37 | }; 38 | 39 | // For seqnum-based queues maximum seqnum should be bigger than queue size 40 | static_assert(channel_helper::to2pow(0) == 4ul); 41 | static_assert(channel_helper::to2pow(0) == 4ul); 42 | static_assert(channel_helper::to2pow(0) == 4ul); 43 | static_assert(channel_helper::to2pow(0) == 4ul); 44 | 45 | static_assert(channel_helper::to2pow(std::numeric_limits::max()) == 64ul); 46 | static_assert(channel_helper::to2pow(std::numeric_limits::max()) == 16'384ul); 47 | static_assert(channel_helper::to2pow(std::numeric_limits::max()) == 1'073'741'824ul); 48 | static_assert(channel_helper::to2pow(std::numeric_limits::max()) == 4'611'686'018'427'387'904ul); 49 | 50 | } // ihft::channel 51 | -------------------------------------------------------------------------------- /channel/include/channel/private/one2each_seqnum_bucket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "channel_helper.h" 8 | 9 | namespace ihft::channel::impl 10 | { 11 | 12 | template 13 | struct one2each_seqnum_queue_constant 14 | { 15 | static_assert(std::is_unsigned_v, "Counter type must be unsigned."); 16 | 17 | enum : T { MIN_EVENT_SEQ_NUM = 0 }; 18 | enum : T { DUMMY_EVENT_SEQ_NUM = std::numeric_limits::max() }; 19 | enum : T { MIN_READER_ID = 0 }; 20 | enum : T { DUMMY_READER_ID = 255 }; 21 | enum : T { SEQNUM_MASK = std::numeric_limits::max() >> T(1) }; 22 | enum : T { EMPTY_DATA_MARK = 0 }; 23 | enum : T { CONSTRUCTED_DATA_MARK = 1 }; 24 | }; 25 | 26 | static_assert(one2each_seqnum_queue_constant ::SEQNUM_MASK == 127ul); 27 | static_assert(one2each_seqnum_queue_constant::SEQNUM_MASK == 32'767ul); 28 | static_assert(one2each_seqnum_queue_constant::SEQNUM_MASK == 2'147'483'647ul); 29 | static_assert(one2each_seqnum_queue_constant::SEQNUM_MASK == 9'223'372'036'854'775'807ul); 30 | 31 | // bucket 32 | template 33 | struct alignas(constant::CPU_CACHE_LINE_SIZE) one2each_seqnum_bucket final 34 | { 35 | using storage_type = typename std::aligned_storage::type; 36 | using counter_type = one2each_seqnum_queue_constant; 37 | 38 | one2each_seqnum_bucket() noexcept 39 | : m_seqn(counter_type::DUMMY_EVENT_SEQ_NUM) 40 | , m_counter(counter_type::EMPTY_DATA_MARK) 41 | { 42 | } 43 | 44 | one2each_seqnum_bucket(const one2each_seqnum_bucket&) = delete; 45 | one2each_seqnum_bucket& operator=(const one2each_seqnum_bucket&) = delete; 46 | one2each_seqnum_bucket(one2each_seqnum_bucket&&) = delete; 47 | one2each_seqnum_bucket& operator=(one2each_seqnum_bucket&&) = delete; 48 | 49 | ~one2each_seqnum_bucket() noexcept 50 | { 51 | if (m_counter != counter_type::EMPTY_DATA_MARK) 52 | { 53 | std::destroy_at(&get_event()); 54 | m_seqn.store(counter_type::DUMMY_EVENT_SEQ_NUM, std::memory_order_relaxed); 55 | m_counter.store(counter_type::EMPTY_DATA_MARK, std::memory_order_release); 56 | } 57 | } 58 | 59 | event_t& get_event() noexcept 60 | { 61 | // Note: std::launder is needed after the change of object model in P0137R1 62 | return *std::launder(reinterpret_cast(&m_storage)); 63 | } 64 | 65 | std::atomic m_seqn; 66 | std::atomic m_counter; 67 | storage_type m_storage; 68 | }; 69 | 70 | } 71 | -------------------------------------------------------------------------------- /channel/include/channel/private/one2one_seqnum_bucket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "channel_helper.h" 8 | 9 | namespace ihft::channel::impl 10 | { 11 | 12 | template 13 | struct one2one_seqnum_queue_constant 14 | { 15 | static_assert(std::is_unsigned_v, "Counter type must be unsigned."); 16 | 17 | enum : T { MIN_EVENT_SEQ_NUM = 0 }; 18 | enum : T { DUMMY_EVENT_SEQ_NUM = std::numeric_limits::max() }; 19 | enum : T { MIN_READER_ID = 0 }; 20 | enum : T { DUMMY_READER_ID = 1 }; 21 | enum : T { SEQNUM_MASK = std::numeric_limits::max() >> T(1) }; 22 | }; 23 | 24 | static_assert(one2one_seqnum_queue_constant ::SEQNUM_MASK == 127ul); 25 | static_assert(one2one_seqnum_queue_constant::SEQNUM_MASK == 32'767ul); 26 | static_assert(one2one_seqnum_queue_constant::SEQNUM_MASK == 2'147'483'647ul); 27 | static_assert(one2one_seqnum_queue_constant::SEQNUM_MASK == 9'223'372'036'854'775'807ul); 28 | 29 | // bucket 30 | template 31 | struct alignas(constant::CPU_CACHE_LINE_SIZE) one2one_seqnum_bucket final 32 | { 33 | using storage_type = typename std::aligned_storage::type; 34 | using counter_type = one2one_seqnum_queue_constant; 35 | 36 | one2one_seqnum_bucket() noexcept 37 | : m_seqn(counter_type::DUMMY_EVENT_SEQ_NUM) 38 | { 39 | } 40 | 41 | one2one_seqnum_bucket(const one2one_seqnum_bucket&) = delete; 42 | one2one_seqnum_bucket& operator=(const one2one_seqnum_bucket&) = delete; 43 | one2one_seqnum_bucket(one2one_seqnum_bucket&&) = delete; 44 | one2one_seqnum_bucket& operator=(one2one_seqnum_bucket&&) = delete; 45 | 46 | ~one2one_seqnum_bucket() noexcept 47 | { 48 | if (m_seqn != counter_type::DUMMY_EVENT_SEQ_NUM) 49 | { 50 | std::destroy_at(&get_event()); 51 | m_seqn.store(counter_type::DUMMY_EVENT_SEQ_NUM, std::memory_order_release); 52 | } 53 | } 54 | 55 | event_t& get_event() noexcept 56 | { 57 | // Note: std::launder is needed after the change of object model in P0137R1 58 | return *std::launder(reinterpret_cast(&m_storage)); 59 | } 60 | 61 | std::atomic m_seqn; 62 | storage_type m_storage; 63 | }; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /channel/include/channel/private/ring_buffer_factory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::channel::impl 6 | { 7 | 8 | class ring_buffer_factory final 9 | { 10 | public: 11 | // RA - region allocator type. Used for ring buffer allocation 12 | template 13 | static std::shared_ptr make(std::size_t n, RA region_allocator) 14 | { 15 | using bucket_type = typename RA::value_type; 16 | 17 | auto reg_allocator(std::move(region_allocator)); 18 | auto ptr = reg_allocator.allocate(n); 19 | std::uninitialized_default_construct_n(ptr, n); 20 | 21 | std::shared_ptr buffer(ptr, [ 22 | r_allocator = std::move(reg_allocator), 23 | size = n 24 | ](bucket_type* ptr) mutable { 25 | std::destroy_n(ptr, size); 26 | r_allocator.deallocate(ptr, size); 27 | }); 28 | 29 | return buffer; 30 | } 31 | 32 | template 33 | static std::shared_ptr make(std::size_t n, std::unique_ptr content_allocator, RA region_allocator = RA()) 34 | { 35 | using bucket_type = typename RA::value_type; 36 | 37 | auto reg_allocator(std::move(region_allocator)); 38 | auto ptr = reg_allocator.allocate(n); 39 | std::uninitialized_default_construct_n(ptr, n); 40 | 41 | std::shared_ptr buffer(ptr, [ 42 | r_allocator = std::move(reg_allocator), 43 | size = n, 44 | c_allocator = content_allocator.release(), 45 | deleter = content_allocator.get_deleter() 46 | ](bucket_type* ptr) mutable { 47 | std::destroy_n(ptr, size); 48 | r_allocator.deallocate(ptr, size); 49 | // data removed. now we ready to cleanup allocator memory 50 | deleter(c_allocator); 51 | }); 52 | 53 | return buffer; 54 | } 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /channel/measure/data_heap.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct alignas(ihft::constant::CPU_CACHE_LINE_SIZE) stat_local_t 12 | { 13 | std::int64_t counter{0}; 14 | }; 15 | 16 | struct alignas(ihft::constant::CPU_CACHE_LINE_SIZE) stat_global_t 17 | { 18 | std::atomic counter{0}; 19 | }; 20 | 21 | thread_local stat_local_t g_local_allocated; 22 | thread_local stat_local_t g_local_released; 23 | 24 | template 25 | struct data_t 26 | { 27 | using value_type = typename allocator_t::value_type; 28 | 29 | data_t(std::uint64_t val, allocator_t& allocator) noexcept 30 | : m_ptr(std::construct_at(allocator.allocate(1), val)) 31 | , m_allocator(allocator) 32 | { 33 | g_local_allocated.counter++; 34 | } 35 | 36 | data_t(data_t&& data) noexcept 37 | : m_ptr(data.m_ptr) 38 | , m_allocator(data.m_allocator) 39 | { 40 | data.m_ptr = nullptr; 41 | } 42 | 43 | data_t& operator=(data_t&& data) = delete; 44 | data_t(const data_t&) = delete; 45 | data_t& operator=(const data_t&) = delete; 46 | 47 | ~data_t() noexcept 48 | { 49 | if (m_ptr != nullptr) 50 | { 51 | std::destroy_at(m_ptr); 52 | m_allocator.deallocate(m_ptr, 1); 53 | m_ptr = nullptr; 54 | g_local_released.counter++; 55 | } 56 | } 57 | 58 | typename std::allocator_traits::pointer m_ptr; 59 | allocator_t& m_allocator; 60 | }; 61 | 62 | template 63 | struct perf_allocated_test 64 | { 65 | static constexpr bool flush = false; 66 | 67 | perf_allocated_test(std::uint64_t, std::uint64_t, allocator_t& allocator) noexcept 68 | : m_allocator(allocator) 69 | { 70 | std::cout << "g_counter before: " << (m_global_allocated.counter - m_global_released.counter) << " (must be zero)" << std::endl; 71 | } 72 | 73 | ~perf_allocated_test() noexcept 74 | { 75 | std::cout << "g_counter after: " << (m_global_allocated.counter - m_global_released.counter) << " (must be zero)" << std::endl; 76 | } 77 | 78 | auto create_data(std::uint64_t i) noexcept 79 | { 80 | return data_t(i, m_allocator); 81 | } 82 | 83 | void check_data(std::uint64_t, std::uint64_t i, data_t const& cref) noexcept 84 | { 85 | if (i != *cref.m_ptr) 86 | { 87 | abort(); 88 | } 89 | } 90 | 91 | void reader_done() noexcept 92 | { 93 | m_global_released.counter += g_local_released.counter; 94 | } 95 | 96 | void writer_done() noexcept 97 | { 98 | m_global_allocated.counter += g_local_allocated.counter; 99 | } 100 | 101 | allocator_t& m_allocator; 102 | stat_global_t m_global_allocated; 103 | stat_global_t m_global_released; 104 | }; 105 | -------------------------------------------------------------------------------- /channel/measure/data_latency.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using clock_type = std::chrono::steady_clock; 14 | using time_point_t = std::chrono::time_point; 15 | 16 | struct data_t 17 | { 18 | data_t(time_point_t start) noexcept 19 | : m_start(std::move(start)) 20 | { 21 | } 22 | 23 | time_point_t m_start; 24 | }; 25 | 26 | struct latency_test 27 | { 28 | static constexpr bool flush = true; 29 | 30 | static constexpr unsigned default_delta = std::numeric_limits::min(); 31 | static constexpr unsigned max_pow_2 = 32; 32 | static constexpr unsigned mask = max_pow_2 - 1; 33 | 34 | latency_test(std::size_t NUM_READERS, std::size_t TOTAL_EVENTS) noexcept 35 | { 36 | m_lines.resize(NUM_READERS); 37 | for(auto & line : m_lines) 38 | { 39 | line.m_delta.reserve(TOTAL_EVENTS / max_pow_2); 40 | } 41 | } 42 | 43 | ~latency_test() noexcept 44 | { 45 | for(std::size_t i = 0; i < m_lines.size(); i++) 46 | { 47 | auto const& line = m_lines[i]; 48 | std::ofstream output("reader_" + std::to_string(i)); 49 | for(auto const delta : line.m_delta) 50 | { 51 | output << delta << "\n"; 52 | } 53 | } 54 | } 55 | 56 | data_t create_data(std::uint64_t) noexcept 57 | { 58 | return data_t(clock_type::now()); 59 | } 60 | 61 | void check_data(std::uint64_t reader_id, std::uint64_t, data_t const& data) 62 | { 63 | auto const end = clock_type::now(); 64 | auto const delta = static_cast((end - data.m_start).count()); 65 | 66 | auto& result = m_lines[reader_id]; 67 | result.val = std::max(result.val, delta); 68 | if (result.tick == mask) 69 | { 70 | result.m_delta.emplace_back( result.val ); 71 | result.val = default_delta; 72 | } 73 | result.tick++; 74 | result.tick &= mask; 75 | } 76 | 77 | void reader_done() noexcept 78 | { 79 | } 80 | 81 | void writer_done() noexcept 82 | { 83 | } 84 | 85 | private: 86 | struct alignas(ihft::constant::CPU_CACHE_LINE_SIZE) line_t 87 | { 88 | unsigned tick{}; 89 | unsigned val{default_delta}; 90 | std::vector m_delta; 91 | }; 92 | 93 | std::vector m_lines; 94 | }; 95 | -------------------------------------------------------------------------------- /channel/measure/data_plain.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct data_t 6 | { 7 | data_t() noexcept 8 | : m_value(0) 9 | { 10 | } 11 | 12 | data_t(std::uint64_t value) noexcept 13 | : m_value(value) 14 | { 15 | } 16 | 17 | std::uint64_t m_value; 18 | }; 19 | 20 | struct perf_plain_test 21 | { 22 | static constexpr bool flush = false; 23 | 24 | perf_plain_test(std::uint64_t, std::uint64_t) noexcept 25 | { 26 | } 27 | 28 | ~perf_plain_test() noexcept 29 | { 30 | } 31 | 32 | data_t create_data(std::uint64_t i) noexcept 33 | { 34 | return data_t(i); 35 | } 36 | 37 | void check_data(std::uint64_t, std::uint64_t i, data_t const& cref) noexcept 38 | { 39 | if (i != cref.m_value) 40 | { 41 | abort(); 42 | } 43 | } 44 | 45 | void reader_done() noexcept 46 | { 47 | } 48 | 49 | void writer_done() noexcept 50 | { 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_object_queue_heap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | using allocator_t = std::allocator; 10 | using queue_t = one2each_seqnum_stream_object_queue, allocator_t>; 11 | return test_main>(argc, argv); 12 | } 13 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_object_queue_heap_stream_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace ihft::channel; 7 | 8 | int main(int const argc, char const* argv[]) 9 | { 10 | using allocator_t = ihft::memory::stream_fixed_pool_allocator; 11 | using queue_t = one2each_seqnum_stream_object_queue, allocator_t>; 12 | return test_main>(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_object_queue_latency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, latency_test>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_object_queue_plain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, perf_plain_test>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_pod_queue_latency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, latency_test>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2each_stream_pod_queue_plain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, perf_plain_test>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_object_queue_heap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | using allocator_t = std::allocator; 10 | using queue_t = one2one_seqnum_stream_object_queue, allocator_t>; 11 | return test_main, true>(argc, argv); 12 | } 13 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_object_queue_heap_stream_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace ihft::channel; 7 | 8 | int main(int const argc, char const* argv[]) 9 | { 10 | using allocator_t = ihft::memory::stream_fixed_pool_allocator; 11 | using queue_t = one2one_seqnum_stream_object_queue, allocator_t>; 12 | return test_main, true>(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_object_queue_latency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, latency_test, true>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_object_queue_plain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, perf_plain_test, true>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_pod_queue_latency.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, latency_test, true>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/measure/measure_one2one_stream_pod_queue_plain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace ihft::channel; 6 | 7 | int main(int const argc, char const* argv[]) 8 | { 9 | return test_main, perf_plain_test, true>(argc, argv); 10 | } 11 | -------------------------------------------------------------------------------- /channel/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # auto test 2 | 3 | ihft_add_test(test_stream_queue) 4 | target_link_libraries(test_stream_queue PRIVATE ihft_channel) 5 | -------------------------------------------------------------------------------- /compiler/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_compiler INTERFACE) 2 | target_include_directories(ihft_compiler INTERFACE include) 3 | -------------------------------------------------------------------------------- /compiler/README.md: -------------------------------------------------------------------------------- 1 | # ihft::compiler 2 | 3 | This module contains various macros for detailed control of the compilation process. 4 | 5 | `COLD` marco is useful for lambda expressions or functions containing error handling code. This allows you to remove code that is rare in execution from the hot processing path and exclude it from the decoding process. 6 | 7 | ```cpp 8 | IHFT_COLD 9 | ``` 10 | 11 | `NOINLINE` macro is useful for benchmarks and other code in which we want to keep the structure of the code breakdown by methods written by the programmer. 12 | 13 | ```cpp 14 | IHFT_NOINLINE 15 | ``` 16 | -------------------------------------------------------------------------------- /compiler/include/compiler/compiler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // https://rigtorp.se/iife/ 4 | #define IHFT_COLD __attribute__((noinline, cold)) 5 | 6 | // https://clang.llvm.org/docs/AttributeReference.html#noinline 7 | #define IHFT_NOINLINE __attribute__((noinline)) 8 | -------------------------------------------------------------------------------- /constant/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_constant INTERFACE) 2 | target_include_directories(ihft_constant INTERFACE include) 3 | -------------------------------------------------------------------------------- /constant/README.md: -------------------------------------------------------------------------------- 1 | # ihft::compiler 2 | 3 | This module contains various useful compile-time constants. 4 | 5 | The real cpu loads the data into cache in chunks. `CPU_CACHE_LINE_SIZE` reflects this feature of the processor implementation. Using this constant is useful when writing data structures that are used simultaneously in a multithreaded environment. (See more about a false sharing). 6 | -------------------------------------------------------------------------------- /constant/include/constant/constant.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 4 | // @todo: 5 | // 6 | // use https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size 7 | // 8 | 9 | // 10 | // sysctl -a | grep cachelinesize 11 | // hw.cachelinesize: 64 12 | // 13 | 14 | namespace ihft::constant 15 | { 16 | constexpr unsigned CPU_CACHE_LINE_SIZE = 64; 17 | } 18 | -------------------------------------------------------------------------------- /engine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_engine src/engine.cpp src/engine_main.cpp) 2 | target_include_directories(ihft_engine PUBLIC include) 3 | target_link_libraries(ihft_engine PUBLIC ihft_types PRIVATE ihft_platform ihft_misc ihft_logger) 4 | 5 | add_subdirectory(test) 6 | add_subdirectory(example) 7 | -------------------------------------------------------------------------------- /engine/README.md: -------------------------------------------------------------------------------- 1 | # ihft::engine 2 | 3 | This module contains a main function for *ihft* applications. 4 | 5 | ```cpp 6 | namespace ihft::engine 7 | { 8 | class cpus_config; 9 | class task_storage; 10 | 11 | using register_tasks_callback_t = types::function_ref; 12 | using invalid_config_callback_t = types::function_ref; 13 | 14 | int engine_main(int const argc, char const * const argv[], register_tasks_callback_t, invalid_config_callback_t); 15 | } 16 | ``` 17 | 18 | It provides a comprehensive functionality for: 19 | 20 | - parse and validate an application configuration 21 | - validation of the application launch platform 22 | - complex signal handler initialization 23 | - thread initialization and processor bindings 24 | - binding user tasks to execution threads 25 | - waiting for the end of the application 26 | 27 | Demonstration in Linux (htop): 28 | 29 | ![perimeter](/.image/engine_perimeter.png) 30 | 31 | ## Examples 32 | 33 | [engine_perimeter example](example/engine_perimeter.cpp) 34 | 35 | [engine_manual_config example](example/engine_manual_config.cpp) 36 | -------------------------------------------------------------------------------- /engine/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | add_executable(engine_logical_cpu_demo logical_cpu_demo.cpp) 3 | target_link_libraries(engine_logical_cpu_demo PRIVATE ihft_engine ihft_platform) 4 | 5 | add_executable(engine_perimeter engine_perimeter.cpp) 6 | target_link_libraries(engine_perimeter PRIVATE ihft_engine ihft_platform) 7 | 8 | add_executable(engine_manual_config engine_manual_config.cpp) 9 | target_link_libraries(engine_manual_config PRIVATE ihft_engine) 10 | -------------------------------------------------------------------------------- /engine/example/engine_manual_config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | static const char * const DEMO_CFG = R"( 9 | $ cat engine.toml # example 10 | [engine] 11 | cpu.netio = 6 12 | cpu.algo = 7 13 | cpu.logger = 8 14 | )"; 15 | 16 | using namespace ihft::engine; 17 | 18 | namespace 19 | { 20 | bool register_items(cpus_config const& cfg, task_storage& storage, std::atomic_bool const& until) 21 | { 22 | for(auto const& [name, _] : cfg.get_name_2_cpu()) 23 | { 24 | storage.add_task(name, [](){ 25 | return true; 26 | }); 27 | } 28 | 29 | auto res = std::chrono::nanoseconds::max(); 30 | 31 | auto t1 = std::chrono::steady_clock::now(); 32 | for(int i = 0; i < 1'000'000 and until; i++) 33 | { 34 | auto const t2 = std::chrono::steady_clock::now(); 35 | auto const delta = t2 - t1; 36 | if (delta < res) 37 | { 38 | res = delta; 39 | } 40 | } 41 | 42 | std::cout << "cpu threshold_ns: " << res.count() << std::endl; 43 | 44 | return true; 45 | } 46 | 47 | void invalid_config() 48 | { 49 | std::cerr << DEMO_CFG; 50 | std::cerr.flush(); 51 | } 52 | } 53 | 54 | int main(int const argc, const char * const argv[]) 55 | { 56 | std::cout << "Hello, %Username%.\n"; 57 | std::cout << "This demo loading on any isolated CPU from config.\n"; 58 | std::cout << "Generates several threads from configuration file.\n"; 59 | std::cout << "And then waits for the SIGINT signal from the user.\n"; 60 | std::cout.flush(); 61 | 62 | return engine_main(argc, argv, register_items, invalid_config); 63 | } 64 | -------------------------------------------------------------------------------- /engine/example/engine_perimeter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace ihft::engine; 15 | using namespace ihft::platform; 16 | 17 | namespace 18 | { 19 | bool register_items(cpus_config const& cfg, task_storage& storage, std::atomic_bool const&) 20 | { 21 | for(auto const& [name, _] : cfg.get_name_2_cpu()) 22 | { 23 | storage.add_task(name, [](){ 24 | return true; 25 | }); 26 | } 27 | 28 | return true; 29 | } 30 | 31 | void invalid_config() 32 | { 33 | } 34 | 35 | bool generate_valid_config() 36 | { 37 | std::vector frames = 38 | { 39 | "Зодиак", 40 | "Роутер", 41 | "Арк-9", 42 | "Пионер", 43 | "Cтранник", 44 | "Орган", 45 | "Банч", 46 | "Zкзистор", 47 | "Кластер" 48 | }; 49 | 50 | std::random_device rd; 51 | std::mt19937 rg(rd()); 52 | std::shuffle(frames.begin(), frames.end(), rg); 53 | 54 | std::ofstream output("engine.toml"); 55 | output << "[engine]\n"; 56 | 57 | auto it = frames.begin(); 58 | 59 | unsigned const cpus = std::thread::hardware_concurrency(); 60 | for(unsigned c = 0; c < cpus and it != frames.end(); c++) 61 | { 62 | if (trait::get_cpu_isolation_status(c)) 63 | { 64 | output << "cpu." << '"' << *it << '"' << " = " << c << '\n'; 65 | it++; 66 | } 67 | } 68 | 69 | if (it == frames.begin()) 70 | { 71 | std::cerr << "I can't find suitable isolated cores to generate the correct configuration.\n"; 72 | std::cerr.flush(); 73 | return false; 74 | } 75 | else 76 | { 77 | output << "\n"; 78 | return true; 79 | } 80 | } 81 | } 82 | 83 | int main(int const, const char * const argv[]) 84 | { 85 | std::cout << "Hello, %Username%.\n"; 86 | std::cout << "This demo loading on any isolated CPU from config.\n"; 87 | std::cout << "Generates several threads from configuration file.\n"; 88 | std::cout << "And then waits for the SIGINT signal from the user.\n"; 89 | std::cout << "\n"; 90 | std::cout << "The thread names were inspired by:\n"; 91 | std::cout << "https://github.com/KD-lab-Open-Source/Perimeter\n"; 92 | std::cout << "https://store.steampowered.com/app/289440/Perimeter\n"; 93 | 94 | std::cout.flush(); 95 | 96 | if (not generate_valid_config()) 97 | { 98 | return EXIT_FAILURE; 99 | } 100 | 101 | std::vector params; 102 | params.push_back(argv[0]); 103 | params.push_back("engine.toml"); 104 | 105 | return engine_main(static_cast(params.size()), params.data(), register_items, invalid_config); 106 | } 107 | -------------------------------------------------------------------------------- /engine/example/logical_cpu_demo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using logical_cpu = ihft::engine::impl::logical_cpu_impl; 10 | 11 | std::chrono::nanoseconds calc_cpu_cycle() 12 | { 13 | auto res = std::chrono::nanoseconds::max(); 14 | 15 | auto t1 = std::chrono::steady_clock::now(); 16 | for(int i = 0; i < 1'000'000'000; i++) 17 | { 18 | auto const t2 = std::chrono::steady_clock::now(); 19 | auto const delta = t2 - t1; 20 | if (delta < res) 21 | { 22 | res = delta; 23 | } 24 | } 25 | 26 | return res; 27 | } 28 | 29 | int main(int const argc, char const * const argv[]) 30 | { 31 | if (argc != 3) 32 | { 33 | std::cout << "Usage. engine_logical_cpu_demo " << std::endl; 34 | return EXIT_FAILURE; 35 | } 36 | 37 | auto const cpu_id = static_cast(std::stoul(argv[1])); 38 | auto const cpu_name = argv[2]; 39 | 40 | std::chrono::nanoseconds delta1; 41 | std::chrono::nanoseconds delta2; 42 | 43 | { 44 | std::cout << "step1" << std::endl; 45 | 46 | logical_cpu cpu(cpu_id, cpu_name); 47 | if (not cpu.bind()) 48 | { 49 | std::cout << "Can't bind " << cpu_id << " " << cpu_name << std::endl; 50 | return EXIT_FAILURE; 51 | } 52 | 53 | delta1 = calc_cpu_cycle(); 54 | } 55 | 56 | { 57 | std::cout << "step2" << std::endl; 58 | 59 | delta2 = calc_cpu_cycle(); 60 | } 61 | 62 | auto const min_delta = std::min(delta1, delta2); 63 | 64 | std::cout << "min_delta: " << min_delta.count() << std::endl; 65 | 66 | return EXIT_SUCCESS; 67 | } 68 | -------------------------------------------------------------------------------- /engine/include/engine/engine_main.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace ihft::engine 8 | { 9 | class cpus_config; 10 | class task_storage; 11 | 12 | using register_tasks_callback_t = types::function_ref; 13 | using invalid_config_callback_t = types::function_ref; 14 | 15 | int engine_main(int const argc, char const * const argv[], register_tasks_callback_t, invalid_config_callback_t); 16 | } 17 | -------------------------------------------------------------------------------- /engine/include/engine/private/engine.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace ihft::engine::impl 13 | { 14 | 15 | class engine 16 | { 17 | public: 18 | using engine_result_t = ihft::types::result; 19 | 20 | static engine_result_t create(cpus_config, task_storage, std::atomic_bool const&); 21 | ~engine(); 22 | 23 | engine(engine const&) = delete; 24 | engine(engine&&) noexcept = default; 25 | 26 | engine& operator=(engine const&) = delete; 27 | engine& operator=(engine&&) noexcept = delete; 28 | 29 | void join(); 30 | 31 | private: 32 | engine(cpus_config, task_storage, std::atomic_bool const&); 33 | 34 | private: 35 | std::vector m_threads; 36 | bool m_joined; 37 | }; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /engine/include/engine/private/logical_cpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace ihft::engine::impl 7 | { 8 | 9 | template 10 | class logical_cpu_impl final 11 | { 12 | public: 13 | logical_cpu_impl(unsigned cpu_id, std::string cpu_name) noexcept 14 | : m_binded(false) 15 | , m_cpu_id(cpu_id) 16 | , m_cpu_name(std::move(cpu_name)) 17 | { 18 | } 19 | 20 | ~logical_cpu_impl() 21 | { 22 | if (m_binded) 23 | { 24 | m_binded = !platform::reset_current_thread_cpu(); 25 | } 26 | } 27 | 28 | logical_cpu_impl(logical_cpu_impl&& other) noexcept = delete; 29 | logical_cpu_impl& operator=(logical_cpu_impl&& other) noexcept = delete; 30 | 31 | logical_cpu_impl(logical_cpu_impl const&) noexcept = delete; 32 | logical_cpu_impl& operator=(logical_cpu_impl const&) noexcept = delete; 33 | 34 | unsigned get_id() const noexcept 35 | { 36 | return m_cpu_id; 37 | } 38 | 39 | std::string_view get_name() const noexcept 40 | { 41 | return m_cpu_name; 42 | } 43 | 44 | // bind logical cpu to system cpu, also change system thread name 45 | bool bind() noexcept 46 | { 47 | return m_binded = platform::set_current_thread_cpu(m_cpu_id) 48 | && platform::set_current_thread_name(m_cpu_name.c_str()); 49 | } 50 | 51 | private: 52 | bool m_binded; 53 | unsigned m_cpu_id; 54 | std::string m_cpu_name; 55 | }; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /engine/include/engine/task_storage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ihft::engine 8 | { 9 | 10 | namespace impl 11 | { 12 | class engine; 13 | class engine_main_helper; 14 | } 15 | 16 | class task_storage final 17 | { 18 | public: 19 | using task_t = std::function; 20 | 21 | void add_task(std::string name, task_t task) 22 | { 23 | m_tasks.emplace(std::move(name), std::move(task)); 24 | } 25 | 26 | auto const& get_tasks() const noexcept 27 | { 28 | return m_tasks; 29 | } 30 | 31 | private: 32 | void replace_task(std::string name, task_t task) 33 | { 34 | std::erase_if(m_tasks, [&name](const auto& item) { 35 | return item.first == name; 36 | }); 37 | 38 | m_tasks.emplace(std::move(name), std::move(task)); 39 | } 40 | 41 | private: 42 | friend class impl::engine; 43 | friend class impl::engine_main_helper; 44 | 45 | std::multimap m_tasks; 46 | }; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /engine/src/engine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace ihft::engine::impl 11 | { 12 | engine::engine_result_t engine::create(cpus_config cfg, task_storage storage, std::atomic_bool const& until) 13 | { 14 | std::string const error("Invalid configuration. "); 15 | 16 | auto const cpus = cfg.get_name_2_cpu().size(); 17 | auto const tasks = storage.get_tasks().size(); 18 | if (cpus != tasks) 19 | { 20 | return error + "cpus: [" + std::to_string(cpus) + "] != tasks[" + std::to_string(tasks) + "]"; 21 | } 22 | 23 | std::set task; 24 | 25 | for(auto const& [name, _] : storage.get_tasks()) 26 | { 27 | auto const [_it, res] = task.insert(name); 28 | if (!res) 29 | { 30 | return error + "duplicated tasks: " + name; 31 | } 32 | 33 | if (cfg.get_name_2_cpu().find(name) == cfg.get_name_2_cpu().end()) 34 | { 35 | return error + "task: [" + name + "] doesn't have logical cpu."; 36 | } 37 | } 38 | return engine(std::move(cfg), std::move(storage), until); 39 | } 40 | 41 | engine::engine(cpus_config cfg, task_storage storage, std::atomic_bool const& until) 42 | : m_joined(false) 43 | { 44 | for(auto const& [name, cpu] : cfg.get_name_2_cpu()) 45 | { 46 | auto it = storage.m_tasks.find(name); 47 | if (it == storage.m_tasks.end()) 48 | { 49 | ::abort(); 50 | } 51 | 52 | m_threads.emplace_back([&until, task = std::move(it->second), name = name, cpu = cpu]() 53 | { 54 | logical_cpu_impl lcpu(cpu, name); 55 | logger::logger_adapter::logger_client_thread_guard guard; 56 | 57 | logger::logger_adapter::set_thread_name(name.c_str()); 58 | 59 | if (not lcpu.bind()) 60 | { 61 | return; 62 | } 63 | 64 | while(until.load(std::memory_order_relaxed) and task()); 65 | }); 66 | } 67 | } 68 | 69 | engine::~engine() 70 | { 71 | join(); 72 | } 73 | 74 | void engine::join() 75 | { 76 | if (!m_joined) 77 | { 78 | for(auto& t : m_threads) 79 | { 80 | t.join(); 81 | } 82 | 83 | m_joined = true; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /engine/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_test(test_logical_cpu) 2 | target_link_libraries(test_logical_cpu PRIVATE ihft_engine) 3 | 4 | ihft_add_test(test_engine) 5 | target_link_libraries(test_engine PRIVATE ihft_engine) 6 | 7 | ihft_add_test(test_cpus_config) 8 | target_link_libraries(test_cpus_config PRIVATE ihft_engine ihft_misc) 9 | -------------------------------------------------------------------------------- /engine/test/test_engine.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace ihft::engine; 8 | using namespace ihft::engine::impl; 9 | 10 | namespace 11 | { 12 | std::atomic_bool g_until{}; 13 | 14 | struct test_platform 15 | { 16 | static unsigned get_total_cpus() noexcept 17 | { 18 | return 16; 19 | } 20 | 21 | static bool get_cpu_isolation_status(unsigned) noexcept 22 | { 23 | return true; 24 | } 25 | 26 | static bool get_cpu_nohz_full_status(unsigned) noexcept 27 | { 28 | return true; 29 | } 30 | 31 | static bool get_cpu_rcu_nocbs_status(unsigned) noexcept 32 | { 33 | return true; 34 | } 35 | }; 36 | } 37 | 38 | TEST_CASE("engine - not enough CPU") 39 | { 40 | auto const cpus_res = cpus_config::parse({{"alpha", 7}, {"omega", 9}}); 41 | REQUIRE(cpus_res); 42 | auto const& cpus = cpus_res.value(); 43 | 44 | auto func = std::function([](){ 45 | return false; 46 | }); 47 | 48 | task_storage storage; 49 | storage.add_task("alpha", func); 50 | storage.add_task("omega", func); 51 | storage.add_task("omicron", func); 52 | 53 | auto const res = engine::create(cpus, std::move(storage), g_until); 54 | REQUIRE(!res); 55 | 56 | REQUIRE(res.error() == "Invalid configuration. cpus: [2] != tasks[3]"); 57 | } 58 | 59 | TEST_CASE("engine - duplicated tasks") 60 | { 61 | auto const cpus_res = cpus_config::parse({{"alpha", 7}, {"omega", 9}}); 62 | REQUIRE(cpus_res); 63 | auto const& cpus = cpus_res.value(); 64 | 65 | auto func = std::function([](){ 66 | return false; 67 | }); 68 | 69 | task_storage storage; 70 | storage.add_task("alpha", func); 71 | storage.add_task("alpha", func); 72 | 73 | auto const res = engine::create(cpus, std::move(storage), g_until); 74 | REQUIRE(!res); 75 | 76 | REQUIRE(res.error() == "Invalid configuration. duplicated tasks: alpha"); 77 | } 78 | 79 | TEST_CASE("engine - invalid assignment") 80 | { 81 | auto const cpus_res = cpus_config::parse({{"alpha", 7}, {"omega", 9}}); 82 | REQUIRE(cpus_res); 83 | auto const& cpus = cpus_res.value(); 84 | 85 | auto func = std::function([](){ 86 | return false; 87 | }); 88 | 89 | task_storage storage; 90 | storage.add_task("alpha", func); 91 | storage.add_task("omicron", func); 92 | 93 | auto const res = engine::create(cpus, std::move(storage), g_until); 94 | REQUIRE(!res); 95 | 96 | REQUIRE(res.error() == "Invalid configuration. task: [omicron] doesn't have logical cpu."); 97 | } 98 | -------------------------------------------------------------------------------- /engine/test/test_logical_cpu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace 6 | { 7 | static unsigned g_cpu{}; 8 | static std::string g_cpu_name{}; 9 | 10 | struct test_platform 11 | { 12 | static bool set_current_thread_cpu(unsigned cpu) 13 | { 14 | g_cpu = cpu; 15 | 16 | return true; 17 | } 18 | 19 | static bool set_current_thread_name(const char * const name) 20 | { 21 | g_cpu_name = name; 22 | 23 | return true; 24 | } 25 | 26 | static bool reset_current_thread_cpu() 27 | { 28 | g_cpu = 0; 29 | 30 | return true; 31 | } 32 | }; 33 | } 34 | 35 | TEST_CASE("logical_cpu") 36 | { 37 | { 38 | using test_logical_cpu = ihft::engine::impl::logical_cpu_impl; 39 | 40 | test_logical_cpu cpu(7, "omega"); 41 | REQUIRE(cpu.get_id() == 7); 42 | REQUIRE(cpu.get_name() == "omega"); 43 | 44 | REQUIRE(g_cpu == 0); 45 | REQUIRE(g_cpu_name == ""); 46 | 47 | REQUIRE(cpu.bind()); 48 | 49 | REQUIRE(g_cpu == 7); 50 | REQUIRE(g_cpu_name == "omega"); 51 | } 52 | 53 | REQUIRE(g_cpu == 0); 54 | } 55 | -------------------------------------------------------------------------------- /logger/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_logger 2 | src/logger_adapter.cpp 3 | src/logger_client.cpp 4 | src/logger_level.cpp 5 | src/logger_listener.cpp 6 | src/default_logger_listener.cpp 7 | ) 8 | 9 | target_include_directories(ihft_logger PUBLIC include) 10 | target_link_libraries(ihft_logger PUBLIC ihft_constant) 11 | target_link_libraries(ihft_logger PRIVATE ihft_channel) 12 | target_link_libraries(ihft_logger PRIVATE ihft_types) 13 | target_link_libraries(ihft_logger PRIVATE ihft_memory) 14 | target_link_libraries(ihft_logger PRIVATE ihft_platform) 15 | 16 | add_subdirectory(test) 17 | add_subdirectory(example) 18 | add_subdirectory(benchmark) 19 | -------------------------------------------------------------------------------- /logger/README.md: -------------------------------------------------------------------------------- 1 | # ihft::logger 2 | 3 | ## Concept 4 | 5 | Logging helps us to monitor the behavior of the code with minimal overhead. The following steps occur during data logging: 6 | 7 | - The current thread copies input arguments into logger_event object with a fixed internal storage for dynamic data 8 | - The executor thread is trying to quickly push a pointer to the logger_event event to the logging queue of this thread 9 | - A separate processing thread pops the event and performs its formatting, the finalized event is dumped to disk or to the network, the logger_event destructor is called. 10 | 11 | Briefly, this scheme can be described by the sequence: 12 | 13 | - Fast copy input. 14 | - Push in queue. 15 | - Format & notify. 16 | 17 | The design is based on the following requirements: 18 | 19 | - No system calls are made during the process of creating and pushing events. 20 | - Writing to disk or network is performed on a separate thread. 21 | - All threads log independently and do not compete for memory. 22 | 23 | The following picture illustrates the flow: 24 | 25 | ![initial](/.image/logger.png) 26 | 27 | ## Limitations 28 | 29 | An important limitation of the system design is that if the logging queue overflows on a particular thread, events are discarded. Thus the overloaded logging system does not guarantee that all events will be delivered. 30 | If compile-time estimated event size is above logger_event::ITEM_SIZE threshold a compilation error is thrown. 31 | Events containing dynamic data could be trimmed to fit into the internal fixed storage. 32 | The trimming policy is set via logger_contract<> template class specialization. 33 | 34 | ## Quick start 35 | 36 | One can use the logging system in a fairly simple way: 37 | 38 | ``` 39 | IHFT_LOG_INFO("Hello {} {}!!!", "world", 1024); 40 | ``` 41 | 42 | As a result of executing this code, the following line will appear in the log: 43 | 44 | ``` 45 | UTC 2024-07-06 08:58:05.518553 INFO [main:1818552] logger_simple.cpp(15:42):'int main()' Hello world 1024 !!! 46 | ``` 47 | 48 | The formatted message provides the following information: 49 | 50 | - The time of sending the event. 51 | - Event logging level (Debug|Info|Warning|Error). 52 | - Name and identifier of the sender's thread. 53 | - The file, line and position that performed the logging. 54 | - The name of the function that performed the logging. 55 | - The text of the event transmitted by the user. 56 | 57 | ## Custom types support 58 | 59 | Template class logger_contract<> specialization should be provided to log data of a custom type. 60 | 61 | See a full source of [logger_contract](include/logger/logger_contract.h). 62 | 63 | ## Examples 64 | 65 | [logger_simple example](example/logger_simple.cpp) 66 | 67 | [logger_mthreads example](example/logger_mthreads.cpp) 68 | 69 | ## Benchmarks 70 | 71 | [logger_construct benchmark](benchmark/benchmark_logger_construct.cpp) 72 | -------------------------------------------------------------------------------- /logger/benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_benchmark(benchmark_logger_async) 2 | target_link_libraries(benchmark_logger_async PRIVATE ihft_logger) 3 | 4 | ihft_add_benchmark(benchmark_logger_synch) 5 | target_link_libraries(benchmark_logger_synch PRIVATE ihft_logger) 6 | 7 | add_executable(benchmark_logger_mthreads benchmark_logger_mthreads.cpp) 8 | target_link_libraries(benchmark_logger_mthreads PRIVATE ihft_timer) 9 | target_link_libraries(benchmark_logger_mthreads PRIVATE ihft_logger) 10 | target_link_libraries(benchmark_logger_mthreads PRIVATE ihft_platform) 11 | 12 | ihft_add_benchmark(benchmark_logger_construct) 13 | target_link_libraries(benchmark_logger_construct PRIVATE ihft_logger) 14 | target_link_libraries(benchmark_logger_construct PRIVATE ihft_platform) 15 | -------------------------------------------------------------------------------- /logger/benchmark/benchmark_logger_async.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using namespace ihft::logger; 13 | 14 | namespace 15 | { 16 | class benchmark_logger_listener final : public logger_listener 17 | { 18 | public: 19 | void notify(std::string_view) override 20 | { 21 | } 22 | 23 | void flush() override 24 | { 25 | } 26 | }; 27 | } 28 | 29 | TEST_CASE("log event benchmark") 30 | { 31 | std::atomic_bool started{false}; 32 | std::atomic_bool working{true}; 33 | 34 | std::thread thread([&started, &working]() 35 | { 36 | started = true; 37 | 38 | while(working.load(std::memory_order_relaxed)) 39 | { 40 | logger_adapter::dispatch(); 41 | } 42 | 43 | }); 44 | 45 | { 46 | auto uniq = std::make_unique(); 47 | 48 | REQUIRE( uniq ); 49 | 50 | logger_adapter::replace_listener(std::move(uniq)); 51 | 52 | logger_adapter::change_mode(logger_adapter::mode_t::async); 53 | 54 | while(not started); 55 | } 56 | 57 | auto client = logger_client::get_this_thread_client(); 58 | 59 | REQUIRE( client ); 60 | 61 | BENCHMARK("log_event(C++, IHFT, 1024ul)") 62 | { 63 | auto event_slab = client->active_event_slab(); 64 | 65 | auto event = std::construct_at(event_slab, 66 | "benchmark constexpr args: {} {} {}", 67 | "C++", 68 | "IHFT", 69 | 1024ul 70 | ); 71 | 72 | return client->try_log_event(event); 73 | }; 74 | 75 | working = false; 76 | 77 | thread.join(); 78 | } 79 | -------------------------------------------------------------------------------- /logger/benchmark/benchmark_logger_construct.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ------------------------------------------------------------------------------- 4 | log event serrialize 5 | ------------------------------------------------------------------------------- 6 | ../logger/benchmark/benchmark_logger_construct.cpp:12 7 | ............................................................................... 8 | 9 | benchmark name samples iterations estimated 10 | mean low mean high mean 11 | std dev low std dev high std dev 12 | ------------------------------------------------------------------------------- 13 | log_event(C++, IHFT, 1024ul) 100 866 2.0784 ms 14 | 24.4293 ns 24.4215 ns 24.4601 ns 15 | 0.0729872 ns 0.00705451 ns 0.173306 ns 16 | 17 | =============================================================================== 18 | All tests passed (1 assertion in 1 test case) 19 | 20 | */ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | using namespace ihft::logger; 32 | 33 | TEST_CASE("log event serrialize") 34 | { 35 | auto client = logger_client::get_this_thread_client(); 36 | 37 | REQUIRE( client ); 38 | 39 | auto event_slab = client->active_event_slab(); 40 | 41 | const long tid = ihft::platform::trait::get_thread_id(); 42 | 43 | const char * const tname = "main"; 44 | char array[16] = {'\0'}; 45 | strncpy(array, tname, strnlen(tname, sizeof(array))); 46 | 47 | BENCHMARK("log_event(C++, IHFT, 1024ul)") 48 | { 49 | auto event = std::construct_at(event_slab, 50 | "benchmark constexpr args: {} {} {}", 51 | "C++", 52 | "IHFT", 53 | 1024ul 54 | ); 55 | event->set_log_point_source_info(log_level::INFO, logger_event::clock_t::now(), std::source_location::current()); 56 | event->set_log_point_thread_info(tid, array); 57 | 58 | std::destroy_at(event); 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /logger/benchmark/benchmark_logger_mthreads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace ihft::timer; 14 | using namespace ihft::logger; 15 | using namespace ihft::platform; 16 | 17 | constexpr size_t ITERATIONS = 1 * 1024 * 1024; 18 | 19 | int main() 20 | { 21 | logger_adapter::change_mode(logger_adapter::mode_t::async); 22 | 23 | unsigned const cpus = std::max(1u, trait::get_total_cpus() - 1); 24 | trait::set_current_thread_cpu(cpus); 25 | 26 | std::atomic alive{}; 27 | 28 | std::vector threads; 29 | for(unsigned i = 0; i < cpus; i++) 30 | { 31 | alive++; 32 | 33 | threads.emplace_back([i, &alive](){ 34 | logger_adapter::logger_client_thread_guard guard; 35 | 36 | std::string const tname("thread_" + std::to_string(i)); 37 | trait::set_current_thread_name(tname.c_str()); 38 | trait::set_current_thread_cpu(i); 39 | 40 | auto const tid = trait::get_thread_id(); 41 | auto client = logger_client::get_this_thread_client(); 42 | 43 | for(size_t j = 0; j < ITERATIONS; j++) 44 | { 45 | auto event_slab = client->active_event_slab(); 46 | 47 | auto event = std::construct_at(event_slab, 48 | "from id: {}, thread_id: {}, thread_name: {}, event id: {}", 49 | i, tid, tname, j 50 | ); 51 | 52 | while(not client->try_log_event(event)) 53 | { 54 | cpu_pause(); 55 | } 56 | } 57 | 58 | alive--; 59 | }); 60 | } 61 | 62 | std::uint64_t total{}; 63 | 64 | while(alive.load(std::memory_order_relaxed) > 0) 65 | { 66 | if (logger_adapter::dispatch()) 67 | { 68 | total++; 69 | } 70 | } 71 | 72 | for(auto& t : threads) 73 | { 74 | t.join(); 75 | } 76 | 77 | while(logger_adapter::dispatch()) 78 | { 79 | total++; 80 | } 81 | 82 | std::cout << "total: " << total << std::endl; 83 | 84 | return 0; 85 | } 86 | -------------------------------------------------------------------------------- /logger/benchmark/benchmark_logger_synch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace ihft::logger; 10 | 11 | namespace 12 | { 13 | class benchmark_logger_listener final : public logger_listener 14 | { 15 | public: 16 | void notify(std::string_view) override 17 | { 18 | } 19 | 20 | void flush() override 21 | { 22 | } 23 | }; 24 | } 25 | 26 | TEST_CASE("log event benchmark") 27 | { 28 | { 29 | auto uniq = std::make_unique(); 30 | 31 | REQUIRE( uniq ); 32 | 33 | logger_adapter::replace_listener(std::move(uniq)); 34 | } 35 | 36 | auto client = logger_client::get_this_thread_client(); 37 | 38 | REQUIRE( client ); 39 | 40 | BENCHMARK("log_event(C++, IHFT, 1024ul)") 41 | { 42 | auto event_slab = client->active_event_slab(); 43 | 44 | auto event = std::construct_at(event_slab, 45 | "benchmark constexpr args: {} {} {}", 46 | "C++", 47 | "IHFT", 48 | 1024ul 49 | ); 50 | 51 | return client->try_log_event(event); 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /logger/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | add_executable(logger_mthreads logger_mthreads.cpp) 3 | target_link_libraries(logger_mthreads ihft_logger ihft_platform ihft_timer) 4 | 5 | add_executable(logger_simple logger_simple.cpp) 6 | target_link_libraries(logger_simple ihft_logger) 7 | -------------------------------------------------------------------------------- /logger/example/logger_mthreads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* 11 | 12 | UTC 2023-06-22 20:56:09.668061 INFO [thread_5:1581221] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 5, thread_id: 1581221, tname: thread_5, event id: 1021 13 | UTC 2023-06-22 20:56:09.668119 INFO [thread_0:1581216] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 0, thread_id: 1581216, tname: thread_0, event id: 1020 14 | UTC 2023-06-22 20:56:09.664770 INFO [thread_1:1581217] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 1, thread_id: 1581217, tname: thread_1, event id: 1024 15 | UTC 2023-06-22 20:56:09.668944 INFO [thread_2:1581218] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 2, thread_id: 1581218, tname: thread_2, event id: 1022 16 | UTC 2023-06-22 20:56:09.669717 INFO [thread_6:1581222] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 6, thread_id: 1581222, tname: thread_6, event id: 1022 17 | UTC 2023-06-22 20:56:09.669239 INFO [thread_3:1581219] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 3, thread_id: 1581219, tname: thread_3, event id: 1022 18 | UTC 2023-06-22 20:56:09.667791 INFO [thread_4:1581220] logger_mthreads.cpp(56):'auto main()::(anonymous class)::operator()() const' from id: 4, thread_id: 1581220, tname: thread_4, event id: 1022 19 | 20 | */ 21 | 22 | using namespace ihft::logger; 23 | using namespace ihft::platform; 24 | 25 | constexpr size_t ITERATIONS = 1 * 1024 * 1024; 26 | 27 | int main() 28 | { 29 | logger_adapter::change_mode(logger_adapter::mode_t::async); 30 | 31 | unsigned const cpus = std::max(1u, trait::get_total_cpus() - 1); 32 | trait::set_current_thread_cpu(cpus); 33 | 34 | std::atomic alive{}; 35 | std::vector threads; 36 | 37 | for(unsigned i = 0; i < cpus; i++) 38 | { 39 | alive++; 40 | 41 | threads.emplace_back([i, &alive](){ 42 | logger_adapter::logger_client_thread_guard guard; 43 | 44 | std::string const tname("thread_" + std::to_string(i)); 45 | logger_adapter::set_thread_name(tname.c_str()); 46 | trait::set_current_thread_name(tname.c_str()); 47 | trait::set_current_thread_cpu(i); 48 | 49 | auto const tid = trait::get_thread_id(); 50 | 51 | for(size_t j = 0; j < ITERATIONS; j++) 52 | { 53 | IHFT_LOG_INFO( 54 | "from id: {}, thread_id: {}, tname: {}, event id: {}", 55 | i, tid, tname, j 56 | ); 57 | } 58 | 59 | alive--; 60 | }); 61 | } 62 | 63 | while(alive.load(std::memory_order_relaxed) > 0) 64 | { 65 | logger_adapter::dispatch(); 66 | } 67 | 68 | for(auto& t : threads) 69 | { 70 | t.join(); 71 | } 72 | 73 | while(logger_adapter::dispatch()); 74 | 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /logger/example/logger_simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | 5 | UTC 2023-07-06 08:58:05.515885 DEBUG [main:1818552] logger_simple.cpp(14):'int main()' Hello world !!! 6 | UTC 2023-07-06 08:58:05.518553 INFO [main:1818552] logger_simple.cpp(15):'int main()' Hello world !!! 7 | UTC 2023-07-06 08:58:05.518573 WARN [main:1818552] logger_simple.cpp(16):'int main()' Hello world !!! 8 | UTC 2023-07-06 08:58:05.518587 ERROR [main:1818552] logger_simple.cpp(17):'int main()' Hello world !!! 9 | 10 | */ 11 | 12 | int main() 13 | { 14 | IHFT_LOG_DEBUG("Hello {} !!!", "world"); 15 | IHFT_LOG_INFO("Hello {} !!!", "world"); 16 | IHFT_LOG_WARN("Hello {} !!!", "world"); 17 | IHFT_LOG_ERROR("Hello {} !!!", "world"); 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /logger/include/logger/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | consteval size_t compiletime_placeholders_count(std::string_view view, std::string_view pattern) 7 | { 8 | size_t res{}, pos{}; 9 | while(pos = view.find(pattern, pos), pos != std::string_view::npos) 10 | { 11 | res += 1; 12 | pos += pattern.size(); 13 | } 14 | return res; 15 | } 16 | 17 | template 18 | consteval size_t compiletime_args_count(Args&& ... args) 19 | { 20 | return sizeof...(args); 21 | } 22 | 23 | #define IHFT_LOG_IMPL(level, pattern, ...) \ 24 | do \ 25 | { \ 26 | if (auto client = ::ihft::logger::logger_client::get_this_thread_client(); client) { \ 27 | static_assert(compiletime_placeholders_count(pattern, "{}") == compiletime_args_count(__VA_ARGS__)); \ 28 | auto slab = client->active_event_slab(); \ 29 | auto event = std::construct_at(slab, pattern, __VA_ARGS__); \ 30 | event->set_log_point_source_info(level, std::chrono::system_clock::now(), std::source_location::current()); \ 31 | client->try_log_event(event); \ 32 | } \ 33 | } \ 34 | while(0); 35 | 36 | #define IHFT_LOG_DEBUG(pattern, ...) IHFT_LOG_IMPL(::ihft::logger::log_level::DEBUG, pattern, __VA_ARGS__) 37 | #define IHFT_LOG_INFO(pattern, ...) IHFT_LOG_IMPL(::ihft::logger::log_level::INFO, pattern, __VA_ARGS__) 38 | #define IHFT_LOG_WARN(pattern, ...) IHFT_LOG_IMPL(::ihft::logger::log_level::WARN, pattern, __VA_ARGS__) 39 | #define IHFT_LOG_ERROR(pattern, ...) IHFT_LOG_IMPL(::ihft::logger::log_level::ERROR, pattern, __VA_ARGS__) 40 | -------------------------------------------------------------------------------- /logger/include/logger/logger_adapter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::logger 6 | { 7 | 8 | /// 9 | /// The logger_adapter is main class of logging system. 10 | /// It has two modes: synch and async. Default mode is synch. 11 | /// 12 | /// 13 | /// The end user interacts with the logging system via thread_local logger_client*. 14 | /// By default the logger_adapter initializes only one logger_client* in main thread. 15 | /// 16 | /// 17 | /// Let's explore how the logging system works in synch mode on a particular example. 18 | /// 19 | /// The initial state: 20 | /// main thread: [] 21 | /// LOG_EVENT(...) will put log_event into a this thread queue: 22 | /// main thread: [log_event] 23 | /// Immediately after that, the `logger_adapter_instanse.dispatch()` will be called. 24 | /// main thread: [] 25 | /// 26 | /// 27 | /// Let's explore how the logging system works in async mode on a particular example. 28 | /// 29 | /// The user should create a `logger_client_thread_guard` on the stack of each user thread 30 | /// to initialize a dedicated lock-free queue and setup thread_local logger_client pointer. 31 | /// 32 | /// main_thread: [log_event, ....] ----| 33 | /// net_thread: [log_event, ....] ----|---- logger_thread::dispatch() 34 | /// algo_thread: [log_event, ....] ----| 35 | /// 36 | /// In Asynchronous mode, it calls the `dispatch()` method in a separate thread. 37 | /// 38 | 39 | class logger_client; 40 | class logger_listener; 41 | 42 | class logger_adapter final 43 | { 44 | public: 45 | // 46 | // The user should create a guard on the stack of each user thread instead of main. 47 | // 48 | class logger_client_thread_guard final 49 | { 50 | public: 51 | logger_client_thread_guard(); 52 | ~logger_client_thread_guard() noexcept; 53 | 54 | logger_client_thread_guard(const logger_client_thread_guard&) = delete; 55 | logger_client_thread_guard(logger_client_thread_guard&&) noexcept = delete; 56 | 57 | private: 58 | std::shared_ptr m_client; 59 | }; 60 | 61 | public: 62 | ~logger_adapter(); 63 | 64 | // 65 | // Consume logger_events from readers and process it. 66 | // 67 | static bool dispatch() noexcept; 68 | 69 | // 70 | // Changing the operating mode between synchronous and asynchronous. 71 | // 72 | enum class mode_t { synch, async }; 73 | 74 | static void change_mode(mode_t) noexcept; 75 | 76 | // 77 | // Replace global logger_listener instanse. 78 | // 79 | static void replace_listener(std::shared_ptr) noexcept; 80 | 81 | // 82 | // Change thread name for this thread 83 | // 84 | static void set_thread_name(const char * const tname) noexcept; 85 | 86 | private: 87 | logger_adapter(); 88 | 89 | static logger_adapter* create_instance(); 90 | 91 | static std::shared_ptr register_logger_client(logger_adapter*); 92 | 93 | struct aimpl; 94 | std::unique_ptr m_impl; 95 | 96 | private: 97 | inline static logger_adapter* global_instance = create_instance(); 98 | }; 99 | 100 | } 101 | -------------------------------------------------------------------------------- /logger/include/logger/logger_client.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ihft::logger 8 | { 9 | 10 | struct logger_event; 11 | class logger_adapter; 12 | 13 | class logger_client final 14 | { 15 | static constexpr size_t content_s = 64; 16 | 17 | public: 18 | static logger_client* get_this_thread_client() noexcept; 19 | ~logger_client(); 20 | 21 | logger_client(const logger_client&) = delete; 22 | logger_client(logger_client&&) noexcept = delete; 23 | 24 | logger_event* active_event_slab() noexcept; 25 | bool try_log_event(logger_event*) noexcept; 26 | 27 | private: 28 | friend class logger_adapter; 29 | 30 | template 31 | logger_client(T producer, bool synch) noexcept 32 | : m_synch(synch) 33 | , m_lost{0} 34 | , m_thread_id{0} 35 | { 36 | static_assert(logger_client::content_s == sizeof(T)); 37 | static_assert(logger_client::content_s == alignof(T)); 38 | 39 | m_impl = std::construct_at(reinterpret_cast(&m_storage), std::move(producer)); 40 | m_thread_name[0] = 'u'; 41 | m_thread_name[1] = 'n'; 42 | m_thread_name[2] = 'k'; 43 | m_thread_name[3] = 'n'; 44 | m_thread_name[4] = 'o'; 45 | m_thread_name[5] = 'w'; 46 | m_thread_name[6] = 'n'; 47 | m_thread_name[7] = '\0'; 48 | set_this_thread_client(this); 49 | } 50 | 51 | void set_mode(bool synch, std::memory_order order = std::memory_order_relaxed) 52 | { 53 | m_synch.store(synch, order); 54 | } 55 | 56 | void set_thread_id(long id) 57 | { 58 | m_thread_id = id; 59 | } 60 | 61 | void set_thread_name(const char (&tname)[16]) 62 | { 63 | static_assert(sizeof(tname) == sizeof(m_thread_name)); 64 | for(size_t i = 0; i < sizeof(tname); i++) 65 | { 66 | m_thread_name[i] = tname[i]; 67 | } 68 | } 69 | 70 | static void set_this_thread_client(logger_client*) noexcept; 71 | 72 | private: 73 | std::atomic_bool m_synch; 74 | std::uint64_t m_lost; 75 | long m_thread_id; 76 | char m_thread_name[16]; 77 | void* m_impl; 78 | std::aligned_storage_t m_storage; 79 | }; 80 | 81 | } 82 | -------------------------------------------------------------------------------- /logger/include/logger/logger_extra_data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ihft::logger 8 | { 9 | 10 | // 11 | // This is helper class. It works together with logger_contract. 12 | // It represents a lightweight wrapper on extra memory in logger slab. 13 | // Also it provides several helpers to place origin types in slab memory region. 14 | // 15 | // @todo : support other string_view types 16 | // 17 | // std::wstring_view (C++17) std::basic_string_view 18 | // std::u16string_view (C++17) std::basic_string_view 19 | // std::u32string_view (C++17) std::basic_string_view 20 | // std::u8string_view (C++20) std::basic_string_view 21 | // 22 | 23 | struct logger_extra_data final 24 | { 25 | logger_extra_data(char* buffer, size_t size) 26 | : m_buffer(buffer) 27 | , m_size(size) 28 | { 29 | } 30 | 31 | std::string_view place(std::string_view origin) 32 | { 33 | if (is_enought(origin.size())) 34 | { 35 | std::string_view data(m_buffer, origin.size()); 36 | std::memcpy(m_buffer, origin.data(), origin.size()); 37 | seek_buff(origin.size()); 38 | return data; 39 | } 40 | else 41 | { 42 | return ""; 43 | } 44 | } 45 | 46 | private: 47 | bool is_enought(size_t size) const 48 | { 49 | return size <= m_size; 50 | } 51 | 52 | void seek_buff(size_t size) 53 | { 54 | m_buffer += size; 55 | m_size -= size; 56 | } 57 | 58 | private: 59 | char* m_buffer; 60 | size_t m_size; 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /logger/include/logger/logger_level.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::logger 6 | { 7 | enum class log_level 8 | { 9 | _NONE_, 10 | DEBUG, 11 | INFO, 12 | WARN, 13 | ERROR 14 | }; 15 | 16 | std::ostream& operator<<(std::ostream&, log_level); 17 | } 18 | -------------------------------------------------------------------------------- /logger/include/logger/logger_listener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::logger 6 | { 7 | 8 | class logger_listener 9 | { 10 | public: 11 | virtual ~logger_listener() noexcept; 12 | 13 | virtual void notify(std::string_view) = 0; 14 | virtual void flush() = 0; 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /logger/include/logger/private/default_logger_listener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::logger::impl 6 | { 7 | class default_logger_listener final : public logger_listener 8 | { 9 | public: 10 | void notify(std::string_view) override; 11 | 12 | void flush() override; 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /logger/include/logger/private/logger_impl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | using event_t = ihft::types::box; 13 | using alloc_t = ihft::memory::stream_fixed_pool_allocator; 14 | using queue_t = ihft::channel::one2one_seqnum_stream_object_queue; 15 | -------------------------------------------------------------------------------- /logger/src/default_logger_listener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace ihft::logger::impl 6 | { 7 | void default_logger_listener::notify(std::string_view view) 8 | { 9 | std::cout << view << "\n"; 10 | } 11 | 12 | void default_logger_listener::flush() 13 | { 14 | std::cout.flush(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /logger/src/logger_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace 9 | { 10 | thread_local ihft::logger::logger_client* tls_client = nullptr; 11 | } 12 | 13 | namespace ihft::logger 14 | { 15 | 16 | logger_client* logger_client::get_this_thread_client() noexcept 17 | { 18 | return tls_client; 19 | } 20 | 21 | void logger_client::set_this_thread_client(logger_client* ctx) noexcept 22 | { 23 | tls_client = ctx; 24 | } 25 | 26 | logger_client::~logger_client() 27 | { 28 | set_this_thread_client(nullptr); 29 | 30 | auto ptr = reinterpret_cast(m_impl); 31 | std::destroy_at(ptr); 32 | } 33 | 34 | logger_event* logger_client::active_event_slab() noexcept 35 | { 36 | auto ptr = reinterpret_cast(m_impl); 37 | auto& allocator = ptr->get_content_allocator(); 38 | return allocator.active_slab(); 39 | } 40 | 41 | bool logger_client::try_log_event(logger_event* event) noexcept 42 | { 43 | auto ptr = reinterpret_cast(m_impl); 44 | auto& allocator = ptr->get_content_allocator(); 45 | 46 | assert(event == allocator.active_slab()); 47 | event->set_log_point_thread_info(m_thread_id, m_thread_name); 48 | 49 | bool const write = ptr->try_write(types::box(event)); 50 | if (write) 51 | { 52 | allocator.seek_to_next_slab(); 53 | if (m_synch.load(std::memory_order_relaxed)) 54 | { 55 | logger_adapter::dispatch(); 56 | } 57 | } 58 | else 59 | { 60 | m_lost++; 61 | } 62 | 63 | return write; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /logger/src/logger_level.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace ihft::logger 6 | { 7 | std::ostream& operator<<(std::ostream& os, log_level level) 8 | { 9 | switch(level) 10 | { 11 | case log_level::_NONE_: 12 | os << "NONE"; 13 | break; 14 | 15 | case log_level::DEBUG: 16 | os << "DEBUG"; 17 | break; 18 | 19 | case log_level::INFO: 20 | os << "INFO"; 21 | break; 22 | 23 | case log_level::WARN: 24 | os << "WARN"; 25 | break; 26 | 27 | case log_level::ERROR: 28 | os << "ERROR"; 29 | break; 30 | } 31 | 32 | return os; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /logger/src/logger_listener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace ihft::logger 4 | { 5 | 6 | logger_listener::~logger_listener() noexcept = default; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /logger/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_test(test_compile_time) 2 | 3 | ihft_add_test(test_typer) 4 | 5 | ihft_add_test(test_tuple_print) 6 | 7 | ihft_add_test(test_tuple_format_accurately) 8 | 9 | ihft_add_test(test_tuple_format_every) 10 | 11 | ihft_add_test(test_logger_simple) 12 | target_link_libraries(test_logger_simple PRIVATE ihft_logger) 13 | 14 | ihft_add_test(test_logger_event) 15 | target_link_libraries(test_logger_event PRIVATE ihft_logger) 16 | 17 | ihft_add_test(test_logger_client) 18 | target_link_libraries(test_logger_client PRIVATE ihft_logger) 19 | 20 | ihft_add_test(test_logger_queue) 21 | target_link_libraries(test_logger_queue PRIVATE ihft_types) 22 | target_link_libraries(test_logger_queue PRIVATE ihft_channel) 23 | target_link_libraries(test_logger_queue PRIVATE ihft_memory) 24 | target_link_libraries(test_logger_queue PRIVATE ihft_logger) 25 | -------------------------------------------------------------------------------- /logger/test/test_compile_time.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | consteval size_t compiletime_count(std::string_view view, std::string_view pattern) 6 | { 7 | size_t res{}; 8 | size_t pos{}; 9 | while(pos = view.find(pattern, pos), pos != std::string_view::npos) 10 | { 11 | res += 1; 12 | pos += pattern.size(); 13 | } 14 | return res; 15 | } 16 | 17 | template 18 | consteval size_t compiletime_size(Args&& ... args) 19 | { 20 | return sizeof...(args); 21 | } 22 | 23 | #define CHECK_ME(pattern, ...) \ 24 | do { \ 25 | static_assert(compiletime_count(pattern, "{}") == compiletime_size(__VA_ARGS__)); \ 26 | /* this should be the second function call */ \ 27 | } \ 28 | while(0) 29 | 30 | TEST_CASE("compile_time") 31 | { 32 | using namespace std::literals; 33 | 34 | static_assert(compiletime_count("hello: {} world: {}"sv, "{}") == 2); 35 | static_assert(compiletime_count("{}"sv, "{}") == 1); 36 | static_assert(compiletime_count("simple str"sv, "{}") == 0); 37 | static_assert(compiletime_count("{} {} {} {}"sv, "{}") == 4); 38 | 39 | static_assert(compiletime_size() == 0); 40 | static_assert(compiletime_size(1) == 1); 41 | static_assert(compiletime_size(1, 2) == 2); 42 | static_assert(compiletime_size(1, 2.0, 'a') == 3); 43 | static_assert(compiletime_size(1, 2.0, 'a', "hello"sv) == 4); 44 | 45 | CHECK_ME("no items here", ); 46 | CHECK_ME("hello: {}, world: {}", 'a', 1); 47 | CHECK_ME("{} - {} - {}", "this", "world", "is huge"); 48 | 49 | // This is doesn't work because it is not consteval expression 50 | //std::string str("{}"); 51 | //CHECK_ME(str, 1); 52 | } 53 | -------------------------------------------------------------------------------- /logger/test/test_logger_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace ihft::logger; 9 | 10 | namespace 11 | { 12 | class test_logger_listener final : public logger_listener 13 | { 14 | public: 15 | test_logger_listener() 16 | : m_cnt() 17 | { 18 | } 19 | 20 | void notify(std::string_view view) override 21 | { 22 | m_cnt++; 23 | m_last = view; 24 | } 25 | 26 | void flush() override 27 | { 28 | } 29 | 30 | std::size_t m_cnt; 31 | std::string m_last; 32 | }; 33 | } 34 | 35 | TEST_CASE("log simple event") 36 | { 37 | auto uniq = std::make_shared(); 38 | 39 | REQUIRE( uniq ); 40 | 41 | auto& listener = *uniq.get(); 42 | 43 | logger_adapter::replace_listener(std::move(uniq)); 44 | 45 | REQUIRE( logger_client::get_this_thread_client() ); 46 | 47 | auto client = logger_client::get_this_thread_client(); 48 | 49 | REQUIRE( client ); 50 | 51 | for(size_t i = 1; i <= 3; i++) 52 | { 53 | auto event_slab = client->active_event_slab(); 54 | 55 | auto event = std::construct_at(event_slab, "args constexpr: {} {} iter: {}", "C++", "IHFT", i); 56 | 57 | REQUIRE( listener.m_cnt == i - 1 ); 58 | 59 | REQUIRE( client->try_log_event(event) ); 60 | 61 | REQUIRE( listener.m_cnt == i ); 62 | REQUIRE( listener.m_last == "args constexpr: C++ IHFT iter: " + std::to_string(i)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /logger/test/test_logger_queue.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | using namespace ihft; 15 | 16 | TEST_CASE("try to queue 5 events") 17 | { 18 | constexpr size_t QUEUE_SIZE = 4; 19 | 20 | using event_t = types::box; 21 | using alloc_t = memory::stream_fixed_pool_allocator; 22 | using queue_t = channel::one2one_seqnum_stream_object_queue; 23 | 24 | auto opt = channel::channel_factory::make(QUEUE_SIZE, 1, std::make_unique(QUEUE_SIZE)); 25 | 26 | REQUIRE(opt); 27 | 28 | auto& producer = opt->producer; 29 | auto& consumer = opt->consumers.front(); 30 | auto& allocator = producer.get_content_allocator(); 31 | 32 | for(size_t i = 0; i < QUEUE_SIZE; i++) 33 | { 34 | REQUIRE(allocator.position() == i); 35 | auto event = std::construct_at(allocator.active_slab(), "args constexpr: {} {}", "C++", "IHFT"); 36 | REQUIRE(producer.try_write(types::box(event))); 37 | allocator.seek_to_next_slab(); 38 | REQUIRE(allocator.position() == (i + 1)); 39 | } 40 | 41 | { 42 | REQUIRE(allocator.position() == 4); 43 | auto event = std::construct_at(allocator.active_slab(), "invalid args constexpr"); 44 | REQUIRE(not producer.try_write(types::box(event))); 45 | REQUIRE(allocator.position() == 4); 46 | } 47 | 48 | { 49 | auto opt = consumer.try_read(); 50 | REQUIRE(opt); 51 | 52 | logger::logger_event const& event_ref = opt->get_event(); 53 | 54 | std::ostringstream sstream; 55 | event_ref.print_args_to(sstream); 56 | 57 | REQUIRE(sstream.str() == "args constexpr: C++ IHFT"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /logger/test/test_logger_simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | // 6 | // This test should output: 7 | // 8 | // UTC 2023-06-19 14:54:07.460302 INFO [main:79325] test_logger.cpp(16):void C_A_T_C_H_T_E_S_T_0() Hello world !!! 9 | // 10 | 11 | TEST_CASE("simple_logging") 12 | { 13 | IHFT_LOG_INFO("Hello {} !!!", "world"); 14 | } 15 | -------------------------------------------------------------------------------- /logger/test/test_tuple_format_every.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Formating code 9 | 10 | template 11 | void format_impl(std::ostream& os, Array const& a, Tuple const& t, std::index_sequence) 12 | { 13 | ((os << a[Is] << std::get(t)), ...); 14 | os << a.back(); 15 | } 16 | 17 | template 18 | void format(std::ostream& os, std::string_view expr, Tuple const& t) 19 | { 20 | constexpr auto tuple_size = std::tuple_size_v; 21 | constexpr auto array_size = tuple_size + 1; // need extra place for tail 22 | 23 | std::array array; 24 | size_t from{}, count{}, pos{}; 25 | 26 | while(pos = expr.find("{}", from), pos != std::string_view::npos and count < tuple_size) 27 | { 28 | array[count] = expr.substr(from, pos - from); 29 | 30 | count += 1; 31 | pos += 2; 32 | from = pos; 33 | } 34 | 35 | array[count] = expr.substr(from); 36 | 37 | using indices = std::make_index_sequence; 38 | format_impl(os, array, t, indices{}); 39 | } 40 | 41 | // Unit tests 42 | 43 | TEST_CASE("format success 1") 44 | { 45 | auto const t = std::tuple('a', 1024, 3.14); 46 | std::string_view const expr = "arg1: '{}', arg2: {}, arg3: {}f"; 47 | 48 | std::ostringstream stream; 49 | format(stream, expr, t); 50 | 51 | REQUIRE(stream.str() == "arg1: 'a', arg2: 1024, arg3: 3.14f"); 52 | } 53 | 54 | TEST_CASE("format success 2") 55 | { 56 | auto const t = std::tuple('a', 1024, 3.14); 57 | std::string_view const expr = "arg1: '{}', arg2: {}, arg3: "; 58 | 59 | std::ostringstream stream; 60 | format(stream, expr, t); 61 | 62 | REQUIRE(stream.str() == "arg1: 'a', arg2: 1024, arg3: 3.14"); 63 | } 64 | 65 | TEST_CASE("format success 3") 66 | { 67 | auto const t = std::tuple('a', 1024, 3.14, 'e'); 68 | std::string_view const expr = "arg1: '{}', arg2: {}, arg3: "; 69 | 70 | std::ostringstream stream; 71 | format(stream, expr, t); 72 | 73 | REQUIRE(stream.str() == "arg1: 'a', arg2: 1024, arg3: 3.14e"); 74 | } 75 | 76 | TEST_CASE("format success 4") 77 | { 78 | auto const t = std::tuple('a', 3.14, 'e'); 79 | std::string_view const expr = "arg1: '{}', arg2: {}, arg3: '{}', arg4: {}, arg5: '{}'"; 80 | 81 | std::ostringstream stream; 82 | format(stream, expr, t); 83 | 84 | REQUIRE(stream.str() == "arg1: 'a', arg2: 3.14, arg3: 'e', arg4: {}, arg5: '{}'"); 85 | } 86 | -------------------------------------------------------------------------------- /logger/test/test_tuple_print.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | template 7 | void trace_impl(std::ostream& os, Tuple const& t, std::index_sequence) 8 | { 9 | os << "("; 10 | ((os << (Is == 0 ? "" : ", ") << std::get(t)), ...); 11 | os << ")"; 12 | } 13 | 14 | // see std::apply next time 15 | 16 | template 17 | void trace(std::ostream& os, Tuple const& t) 18 | { 19 | constexpr auto tuple_size = std::tuple_size_v; 20 | using indices = std::make_index_sequence; 21 | trace_impl(os, t, indices{}); 22 | } 23 | 24 | TEST_CASE("print") 25 | { 26 | auto const t = std::tuple('a', 1024, 3.14); 27 | std::ostringstream stream; 28 | trace(stream, t); 29 | 30 | REQUIRE(stream.str() == "(a, 1024, 3.14)"); 31 | } 32 | -------------------------------------------------------------------------------- /logger/test/test_typer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | struct typer 10 | { 11 | using type = void; 12 | }; 13 | 14 | template 15 | struct typer 16 | { 17 | using type = void*; 18 | }; 19 | 20 | template 21 | struct typer 22 | { 23 | using type = void*; 24 | }; 25 | 26 | template<> 27 | struct typer 28 | { 29 | using type = void*; 30 | }; 31 | 32 | template<> 33 | struct typer 34 | { 35 | using type = long; 36 | }; 37 | 38 | template<> 39 | struct typer 40 | { 41 | using type = double; 42 | }; 43 | 44 | template<> 45 | struct typer 46 | { 47 | using type = std::string_view; 48 | }; 49 | 50 | template 51 | decltype(auto) to_tuple(Args&& ...) 52 | { 53 | return std::tuple::type ...>(); 54 | } 55 | 56 | TEST_CASE("typer") 57 | { 58 | char* ptr0 = nullptr; 59 | const char* ptr1 = nullptr; 60 | char* const ptr2 = nullptr; 61 | const char* const ptr3 = nullptr; 62 | 63 | static_assert(std::is_same_v::type, void*>); 64 | static_assert(std::is_same_v::type, void*>); 65 | static_assert(std::is_same_v::type, void*>); 66 | static_assert(std::is_same_v::type, void*>); 67 | static_assert(std::is_same_v::type, void*>); 68 | static_assert(std::is_same_v, decltype(to_tuple(int{}, float{}, std::string{}))>); 69 | } 70 | -------------------------------------------------------------------------------- /memory/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_memory INTERFACE) 2 | target_include_directories(ihft_memory INTERFACE include) 3 | target_link_libraries(ihft_memory INTERFACE ihft_constant) 4 | 5 | add_subdirectory(test) 6 | add_subdirectory(benchmark) 7 | -------------------------------------------------------------------------------- /memory/README.md: -------------------------------------------------------------------------------- 1 | # ihft::memory 2 | 3 | This module contains the code of domain-specific allocators. 4 | 5 | [stream_fixed_pool_allocator](include/memory/stream_fixed_pool_allocator.h) is your best friend in working with fixed-size queues for non-POD objects. It contains one element more than the queue size, which allows you to fill the channel with data entirely and safely prepare the next batch of data. 6 | 7 | [page_allocator](include/memory/page_allocator.h) and [huge_page_allocator](include/memory/huge_page_allocator.h) are simple unix page allocators. They can be useful for experiments with TLB. 8 | 9 | [arena_allocator](include/memory/arena_allocator.h) is simple arena allocator. It allows you to pack arbitrary data of different types in a specific location. 10 | 11 | ```cpp 12 | using alloc_t = arena_allocator::typed_arena_allocator; 13 | using astring = std::basic_string, alloc_t>; 14 | 15 | struct network_data final 16 | { 17 | network_data(std::string_view iname, std::string_view ilocation) 18 | : arena(extra_data) 19 | , name(iname, arena.typed_allocator()) 20 | , location(ilocation, arena.typed_allocator()) 21 | { 22 | } 23 | 24 | arena_allocator arena; 25 | 26 | astring name; 27 | astring location; 28 | 29 | char extra_data[192]; 30 | }; 31 | 32 | network_data data("Proydakov Evgeny Alexandrovich", "Moscow"); 33 | REQUIRE(data.name == "Proydakov Evgeny Alexandrovich"); 34 | REQUIRE(data.location == "Moscow"); 35 | 36 | REQUIRE(std::string_view(data.extra_data, 30) == "Proydakov Evgeny Alexandrovich"); 37 | ``` 38 | 39 | ## Benchmarks 40 | 41 | [arena_allocator benchmark](benchmark/benchmark_arena_allocator.cpp) 42 | 43 | [stream_fixed_pool_allocator benchmark](benchmark/benchmark_stream_fixed_pool_allocator.cpp) 44 | -------------------------------------------------------------------------------- /memory/benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_benchmark(benchmark_stream_fixed_pool_allocator) 2 | target_link_libraries(benchmark_stream_fixed_pool_allocator PRIVATE ihft_memory) 3 | 4 | ihft_add_benchmark(benchmark_arena_allocator) 5 | target_link_libraries(benchmark_arena_allocator PRIVATE ihft_memory) 6 | -------------------------------------------------------------------------------- /memory/benchmark/benchmark_arena_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | /* 10 | 11 | ------------------------------------------------------------------------------- 12 | arena_allocator benchmark 13 | ------------------------------------------------------------------------------- 14 | ../memory/benchmark/benchmark_arena_allocator.cpp:38 15 | ............................................................................... 16 | 17 | benchmark name samples iterations estimated 18 | mean low mean high mean 19 | std dev low std dev high std dev 20 | ------------------------------------------------------------------------------- 21 | allocate(1) 100 41473 0 ns 22 | 0.928058 ns 0.901819 ns 1.00861 ns 23 | 0.198474 ns 0.0112234 ns 0.42777 ns 24 | ------------------------------------------------------------------------------- 25 | 26 | */ 27 | 28 | using namespace ihft::memory; 29 | 30 | struct A 31 | { 32 | std::uint64_t type{}; 33 | std::uint64_t length{}; 34 | }; 35 | 36 | static_assert(std::is_same_v::value_type, A>, "value_type should works correctly"); 37 | 38 | std::vector g_memory_slab(1024ul * 1024ul * 1024ul); 39 | 40 | TEST_CASE("arena_allocator benchmark") 41 | { 42 | arena_allocator arena(g_memory_slab.data(), g_memory_slab.size()); 43 | auto allocator = arena.typed_allocator(); 44 | 45 | BENCHMARK("allocate(1)") 46 | { 47 | auto ptr = allocator.allocate(1); 48 | if (ptr == nullptr) [[unlikely]] 49 | { 50 | arena.reset(); 51 | ptr = allocator.allocate(1); 52 | } 53 | return ptr; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /memory/benchmark/benchmark_stream_fixed_pool_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | /* 9 | 10 | ------------------------------------------------------------------------------- 11 | stream_fixed_pool_allocator benchmark 12 | ------------------------------------------------------------------------------- 13 | ../memory/benchmark/benchmark_stream_fixed_pool_allocator.cpp:18 14 | ............................................................................... 15 | 16 | benchmark name samples iterations estimated 17 | mean low mean high mean 18 | std dev low std dev high std dev 19 | ------------------------------------------------------------------------------- 20 | allocate(1) 100 54541 0 ns 21 | 0.747524 ns 0.744961 ns 0.75362 ns 22 | 0.0203072 ns 0.0116361 ns 0.0323833 ns 23 | ------------------------------------------------------------------------------- 24 | 25 | */ 26 | 27 | using namespace ihft::memory; 28 | 29 | struct A 30 | { 31 | std::uint64_t type{}; 32 | std::uint64_t length{}; 33 | }; 34 | 35 | static_assert(std::is_same_v::value_type, A>, "value_type should works correctly"); 36 | 37 | TEST_CASE("stream_fixed_pool_allocator benchmark") 38 | { 39 | stream_fixed_pool_allocator allocator(1024); 40 | 41 | BENCHMARK("allocate(1)") 42 | { 43 | return allocator.allocate(1); 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /memory/include/memory/huge_page_allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "private/mmap_page_allocator.h" 4 | 5 | namespace ihft::memory 6 | { 7 | 8 | template 9 | using one_gb_huge_page_allocator = impl::mmap_page_allocator; 10 | 11 | template 12 | using two_mb_huge_page_allocator = impl::mmap_page_allocator; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /memory/include/memory/page_allocator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "private/mmap_page_allocator.h" 4 | 5 | namespace ihft::memory 6 | { 7 | 8 | template 9 | using four_4b_page_allocator = impl::mmap_page_allocator; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /memory/include/memory/private/constant.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ihft::memory::impl 4 | { 5 | constexpr unsigned _1gb_ = 1u << 30u; 6 | constexpr unsigned _2mb_ = 1u << 21u; 7 | constexpr unsigned _4kb_ = 1u << 12u; 8 | } 9 | -------------------------------------------------------------------------------- /memory/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_test(test_stream_fixed_pool_allocator) 2 | target_link_libraries(test_stream_fixed_pool_allocator PRIVATE ihft_memory) 3 | 4 | ihft_add_test(test_arena_allocator) 5 | target_link_libraries(test_arena_allocator PRIVATE ihft_memory) 6 | 7 | ihft_add_test(test_mmap_page_allocator) 8 | target_link_libraries(test_mmap_page_allocator PRIVATE ihft_memory) 9 | target_link_libraries(test_mmap_page_allocator PRIVATE ihft_platform) 10 | -------------------------------------------------------------------------------- /memory/test/test_mmap_page_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | using namespace ihft::memory; 11 | 12 | namespace 13 | { 14 | template 15 | class memory_cleaner 16 | { 17 | public: 18 | memory_cleaner(allocator_t& allocator, std::byte* ptr, size_t count) noexcept 19 | : m_allocator(allocator), m_ptr(ptr), m_count(count) 20 | { 21 | } 22 | 23 | ~memory_cleaner() noexcept 24 | { 25 | m_allocator.deallocate_pages(m_ptr, m_count); 26 | } 27 | 28 | private: 29 | allocator_t& m_allocator; 30 | std::byte* m_ptr; 31 | size_t m_count; 32 | }; 33 | 34 | template 35 | void test_impl(unsigned total_pages, const char * const comment) 36 | { 37 | if (total_pages < 2) 38 | { 39 | std::cerr << comment << " are unavailable\n"; 40 | return; 41 | } 42 | 43 | constexpr size_t count = 2; 44 | allocator_t allocator; 45 | 46 | auto page = allocator.allocate_pages(count); 47 | memory_cleaner cleaner(allocator, page, count); 48 | 49 | std::cout << comment << " ptr: " << page << std::endl; 50 | 51 | REQUIRE( page != nullptr ); 52 | 53 | static_assert(sizeof(size_t) == sizeof(void*), "Please cleanup tests below"); 54 | REQUIRE( ((size_t)(page) & (allocator_t::page_size - 1)) == 0 ); 55 | } 56 | } 57 | 58 | TEST_CASE("check 4kb pages") 59 | { 60 | test_impl>(std::numeric_limits::max(), "pages 4kb"); 61 | } 62 | 63 | #ifdef __linux__ 64 | 65 | TEST_CASE("check 1gb hugepages") 66 | { 67 | test_impl>(ihft::platform::trait::total_1gb_hugepages(), "hugepages 1GB"); 68 | } 69 | 70 | TEST_CASE("check 2mb hugepages") 71 | { 72 | test_impl>(ihft::platform::trait::total_2mb_hugepages(), "hugepages 2mb"); 73 | } 74 | 75 | #endif 76 | -------------------------------------------------------------------------------- /memory/test/test_stream_fixed_pool_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | using namespace ihft::memory; 9 | 10 | struct A 11 | { 12 | std::uint64_t type{}; 13 | std::uint64_t length{}; 14 | }; 15 | 16 | static_assert(std::is_same_v::value_type, A>, "value_type should works correctly"); 17 | 18 | TEST_CASE("test_stream_fixed_pool_allocator STL-api") 19 | { 20 | constexpr std::size_t size = 16; 21 | 22 | stream_fixed_pool_allocator allocator(size); 23 | 24 | REQUIRE(allocator.capacity() == 17); 25 | 26 | std::set set; 27 | 28 | // size + 1 extra slab 29 | for(std::size_t i = 0; i < size + 1; i++) 30 | { 31 | REQUIRE(allocator.position() == i); 32 | 33 | set.insert(allocator.allocate(1)); 34 | 35 | REQUIRE( set.size() == i + 1 ); 36 | } 37 | 38 | set.insert(allocator.allocate(1)); 39 | 40 | REQUIRE( set.size() == size + 1 ); 41 | } 42 | 43 | TEST_CASE("test_stream_fixed_pool_allocator IHFT-api") 44 | { 45 | constexpr std::size_t size = 16; 46 | 47 | stream_fixed_pool_allocator allocator(size); 48 | 49 | std::set set; 50 | 51 | for(std::size_t i = 0; i < 8; i++) 52 | { 53 | set.insert(allocator.active_slab()); 54 | 55 | REQUIRE( set.size() == 1 ); 56 | } 57 | 58 | allocator.seek_to_next_slab(); 59 | set.insert(allocator.active_slab()); 60 | 61 | REQUIRE( set.size() == 2 ); 62 | } 63 | 64 | TEST_CASE("stream_fixed_pool_allocator element allocation") 65 | { 66 | stream_fixed_pool_allocator allocator(16); 67 | 68 | REQUIRE( allocator.allocate(1) != nullptr ); 69 | } 70 | 71 | TEST_CASE("stream_fixed_pool_allocator array allocation") 72 | { 73 | stream_fixed_pool_allocator allocator(16); 74 | 75 | REQUIRE( allocator.allocate(2) == nullptr ); 76 | } 77 | -------------------------------------------------------------------------------- /misc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_misc src/config_helper.cpp) 2 | target_include_directories(ihft_misc PUBLIC include) 3 | target_link_libraries(ihft_misc PUBLIC ihft_types PRIVATE ihft_constant) 4 | if(IHFT_BUILD_TOML) 5 | target_link_libraries(ihft_misc PRIVATE tomlplusplus::tomlplusplus) 6 | endif() 7 | 8 | add_subdirectory(test) 9 | add_subdirectory(example) 10 | -------------------------------------------------------------------------------- /misc/README.md: -------------------------------------------------------------------------------- 1 | # ihft::misc 2 | 3 | This module contains an utility code for *ihft* applications. 4 | 5 | TOML-based configuration class [config_helper](include/misc/config_helper.h) 6 | 7 | ```cpp 8 | namespace ihft::misc 9 | { 10 | 11 | // This class provides a simple config assist 12 | // Configuration data is immutable after loading 13 | class config_helper final 14 | ``` 15 | 16 | Unix singnal helper with several methods [signal_helper](include/misc/signal_helper.h) 17 | 18 | ```cpp 19 | namespace ihft::misc 20 | { 21 | 22 | using sa_sigaction_t = void (*)(int, siginfo_t*, void*); 23 | 24 | bool setup_sigaction_handler(sa_sigaction_t, std::initializer_list signals, std::optional flags = std::nullopt); 25 | 26 | bool block_application_signals(std::initializer_list signals); 27 | bool block_thread_signals(std::initializer_list signals); 28 | ``` 29 | 30 | ## Examples 31 | 32 | [sigaction_demo example](example/sigaction_demo.cpp) 33 | 34 | [config_helper example](example/config_helper_demo.cpp) 35 | -------------------------------------------------------------------------------- /misc/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | 3 | if(IHFT_BUILD_TOML) 4 | add_executable(toml_demo toml_demo.cpp) 5 | target_link_libraries(toml_demo tomlplusplus::tomlplusplus) 6 | endif() 7 | 8 | add_executable(misc_config_helper_demo config_helper_demo.cpp) 9 | target_link_libraries(misc_config_helper_demo ihft_misc) 10 | 11 | add_executable(misc_sigaction_demo sigaction_demo.cpp) 12 | -------------------------------------------------------------------------------- /misc/example/config_helper_demo.cpp: -------------------------------------------------------------------------------- 1 | #include "toml_doc.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace ihft:: misc; 10 | 11 | const char * const FNAME = "table.toml"; 12 | 13 | int main() 14 | { 15 | { 16 | std::ofstream output( FNAME ); 17 | output << VALID_TOML_DOC; 18 | } 19 | 20 | std::cout << "parsing\n"; 21 | 22 | auto config = config_helper::parse( FNAME ); 23 | 24 | std::cout << "parsed\n"; 25 | 26 | if (config.failed()) 27 | { 28 | std::cerr << config.error() << "\n"; 29 | return 1; 30 | } 31 | 32 | std::cout << "\nTOML parsing looks good:\n\n"; 33 | 34 | // re-serialize as TOML 35 | std::cout << config << "\n"; 36 | 37 | return 1; 38 | } 39 | -------------------------------------------------------------------------------- /misc/example/sigaction_demo.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // https://www.ibm.com/docs/en/zos/2.5.0?topic=functions-sigaction-examine-change-signal-action 3 | // https://habr.com/ru/post/141206/ 4 | // 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | constexpr int SLEEP_TIME = 9; 12 | 13 | void mysignal(int signal) 14 | { 15 | printf("In mysignal: %s.\n", strsignal(signal)); 16 | sleep(SLEEP_TIME); 17 | } 18 | 19 | void mysa_sigaction(int signal, siginfo_t* info, void* context) 20 | { 21 | printf("In mysa_sigaction: %s info: %p context: %p.\n", strsignal(signal), (void*)info, (void*)context); 22 | sleep(SLEEP_TIME); 23 | } 24 | 25 | void set_new_signal(int signal, struct sigaction& newhandler, const char * const name) 26 | { 27 | if (sigaction(signal, &newhandler, nullptr) != -1) 28 | printf("New handler set for %s.\n", name); 29 | } 30 | 31 | int main() 32 | { 33 | printf("PID: %d\n", getpid()); 34 | 35 | printf("SIGINT: %d\n", SIGINT); 36 | printf("SIGUSR1: %d\n", SIGUSR1); 37 | printf("SIGUSR2: %d\n", SIGUSR2); 38 | 39 | struct sigaction newhandler; 40 | memset(&newhandler, 0, sizeof(newhandler)); 41 | newhandler.sa_handler = mysignal; 42 | newhandler.sa_sigaction = &mysa_sigaction; 43 | newhandler.sa_flags = 0; 44 | 45 | sigemptyset(&newhandler.sa_mask); 46 | //sigaddset(&newhandler.sa_mask, SIGINT); 47 | sigaddset(&newhandler.sa_mask, SIGUSR1); 48 | sigaddset(&newhandler.sa_mask, SIGUSR2); 49 | 50 | //set_new_signal(SIGINT, newhandler, "SIGINT"); 51 | set_new_signal(SIGUSR1, newhandler, "SIGUSR1"); 52 | set_new_signal(SIGUSR2, newhandler, "SIGUSR2"); 53 | 54 | sigset_t mask; 55 | sigemptyset(&mask); 56 | sigaddset(&mask, SIGINT); 57 | 58 | if (sigprocmask(SIG_BLOCK, &mask, nullptr) != -1) 59 | printf("Block several signals {SIGINT}.\n"); 60 | 61 | int val; 62 | if (1 == scanf("%d", &val)) 63 | { 64 | printf("scaned: %d\n", val); 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /misc/example/toml_demo.cpp: -------------------------------------------------------------------------------- 1 | #include "toml_doc.h" 2 | 3 | #define TOML_EXCEPTIONS 0 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | const char * const FNAME = "configuration.toml"; 12 | 13 | int main() 14 | { 15 | { 16 | std::ofstream output( FNAME ); 17 | output << VALID_TOML_DOC; 18 | } 19 | 20 | auto result = toml::parse_file( FNAME ); 21 | 22 | if (result.failed()) 23 | { 24 | std::cerr << result.error() << "\n"; 25 | return 1; 26 | } 27 | 28 | auto const& config = result.table(); 29 | 30 | std::cout << "source: " << config.source() << "\n"; 31 | 32 | std::cout << "sizeof(toml::parse_result): " << sizeof(config) << "\n"; 33 | std::cout << "alignof(toml::parse_result): " << alignof(decltype(config)) << "\n"; 34 | 35 | std::cout << "sizeof(toml::table): " << sizeof(toml::table) << "\n"; 36 | std::cout << "alignof(toml::table): " << alignof(toml::table) << "\n"; 37 | 38 | std::cout << "\nINFO:\n\n"; 39 | std::cout << "size: " << config.size() << "\n"; 40 | std::cout << "empty: " << std::boolalpha << config.empty() << "\n"; 41 | 42 | std::cout << "\nDATA:\n\n"; 43 | 44 | // get key-value pairs 45 | std::string_view library_name = config["library"]["name"].value_or(std::string_view("")); 46 | if (config["library"]["authors"].is_array()) 47 | { 48 | std::cout << "len: " << config["library"]["authors"].as_array()->size() << "\n"; 49 | } 50 | std::string_view library_author = config["library"]["authors"][0].value_or(std::string_view("")); 51 | int64_t depends_on_cpp_version = config["dependencies"]["version"].value_or(0); 52 | 53 | std::cout << "library_name: " << library_name << "\n"; 54 | std::cout << "library_author: " << library_author << "\n"; 55 | std::cout << "depends_on_cpp_version: " << depends_on_cpp_version << "\n"; 56 | 57 | std::cout << "\nRANGLE LOOP:\n\n"; 58 | 59 | // iterate & visit over the data 60 | for(auto const& [k, v] : config) 61 | { 62 | std::cout << "key: '" << k << "'\n"; 63 | v.visit([](auto& node) noexcept 64 | { 65 | std::cout << node << "\n"; 66 | }); 67 | std::cout << '\n'; 68 | } 69 | 70 | std::cout << "\nTOML:\n\n"; 71 | 72 | // re-serialize as TOML 73 | std::cout << config << "\n"; 74 | 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /misc/example/toml_doc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const char * const VALID_TOML_DOC = 4 | R"(title = "my toml configuration" 5 | 6 | [library] 7 | name = "toml++" 8 | authors = ["Mark Gillard "] 9 | cities = ["上海", "北京", "深圳"] 10 | bogatyrs = ["Добрыня Никитич", "Илья Муромец", "Алёша Попович"] 11 | 12 | [dependencies] 13 | lang = "C++" 14 | cpp_version = 20 15 | cpp_compiler = "clang" 16 | operation_system = "linux" 17 | )"; 18 | -------------------------------------------------------------------------------- /misc/include/misc/config_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace ihft::misc 14 | { 15 | 16 | // This class provides a simple config assist 17 | // Configuration data is immutable after loading 18 | class config_helper final 19 | { 20 | public: 21 | using config_result = types::result; 22 | 23 | config_helper(config_helper const&) = delete; 24 | config_helper(config_helper&&) noexcept; 25 | 26 | config_helper& operator=(config_helper const&) = delete; 27 | config_helper& operator=(config_helper&&) noexcept; 28 | 29 | static config_result parse(std::string_view file_path); 30 | 31 | ~config_helper(); 32 | 33 | friend std::ostream& operator<<(std::ostream&, const config_helper&); 34 | 35 | std::string_view source() const noexcept; 36 | 37 | std::optional get_boolean(std::string_view section, std::string_view key) const noexcept; 38 | std::optional get_integer(std::string_view section, std::string_view key) const noexcept; 39 | std::optional get_string(std::string_view section, std::string_view key) const noexcept; 40 | 41 | void enumerate_boolean(std::string_view section, types::function_ref) const noexcept; 42 | void enumerate_integer(std::string_view section, types::function_ref) const noexcept; 43 | void enumerate_string (std::string_view section, types::function_ref) const noexcept; 44 | 45 | bool exists(std::string_view path) const noexcept; 46 | 47 | private: 48 | template 49 | std::optional get_value(std::string_view section, std::string_view key) const noexcept; 50 | 51 | template 52 | void enumerate(std::string_view section, ihft::types::function_ref) const noexcept; 53 | 54 | template 55 | config_helper(T) noexcept; 56 | 57 | template 58 | static config_result parse_impl(T file_path); 59 | 60 | private: 61 | struct impl; 62 | std::unique_ptr m_impl; 63 | }; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /misc/include/misc/private/signal_helper.inl: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::misc 6 | { 7 | 8 | [[nodiscard("Please check change signal handler result")]] 9 | bool setup_sigaction_handler(sa_sigaction_t action, std::initializer_list signals, std::optional flags) 10 | { 11 | struct sigaction newhandler; 12 | memset(&newhandler, 0, sizeof(newhandler)); 13 | 14 | newhandler.sa_sigaction = action; 15 | 16 | if (flags) 17 | { 18 | newhandler.sa_flags = *flags; 19 | } 20 | 21 | sigemptyset(&newhandler.sa_mask); 22 | for(auto const signal : signals) 23 | { 24 | sigaddset(&newhandler.sa_mask, signal); 25 | } 26 | 27 | bool succeeded = true; 28 | for(auto const signal : signals) 29 | { 30 | // https://man7.org/linux/man-pages/man2/sigaction.2.html 31 | succeeded &= sigaction(signal, &newhandler, nullptr) == 0; 32 | } 33 | 34 | return succeeded; 35 | } 36 | 37 | [[nodiscard("Please check change signal mask for application result")]] 38 | bool block_application_signals(std::initializer_list signals) 39 | { 40 | sigset_t mask; 41 | sigemptyset(&mask); 42 | 43 | for(auto const signal : signals) 44 | { 45 | sigaddset(&mask, signal); 46 | } 47 | 48 | // https://man7.org/linux/man-pages/man2/sigprocmask.2.html 49 | return sigprocmask(SIG_BLOCK, &mask, nullptr) == 0; 50 | } 51 | 52 | [[nodiscard("Please check change signal mask for thread result")]] 53 | bool block_thread_signals(std::initializer_list signals) 54 | { 55 | sigset_t mask; 56 | sigemptyset(&mask); 57 | 58 | for(auto const signal : signals) 59 | { 60 | sigaddset(&mask, signal); 61 | } 62 | 63 | // https://man7.org/linux/man-pages/man3/pthread_sigmask.3.html 64 | return pthread_sigmask(SIG_BLOCK, &mask, nullptr) == 0; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /misc/include/misc/signal_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 4 | // This code is based on some other works: 5 | // 6 | // https://habr.com/ru/post/141206/ 7 | // https://www.ibm.com/docs/en/zos/2.5.0?topic=functions-sigaction-examine-change-signal-action 8 | // 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace ihft::misc 15 | { 16 | 17 | using sa_sigaction_t = void (*)(int, siginfo_t*, void*); 18 | 19 | bool setup_sigaction_handler(sa_sigaction_t, std::initializer_list signals, std::optional flags = std::nullopt); 20 | 21 | bool block_application_signals(std::initializer_list signals); 22 | bool block_thread_signals(std::initializer_list signals); 23 | 24 | } 25 | 26 | #include "private/signal_helper.inl" 27 | -------------------------------------------------------------------------------- /misc/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_test(test_config_helper) 2 | target_link_libraries(test_config_helper PRIVATE ihft_misc) 3 | 4 | ihft_add_test(test_signal_helper) 5 | target_link_libraries(test_signal_helper PRIVATE ihft_misc) 6 | -------------------------------------------------------------------------------- /misc/test/test_signal_helper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ihft::misc; 6 | 7 | void mysa_sigaction(int signal, siginfo_t* info, void* context) 8 | { 9 | printf("In mysa_sigaction: %s info: %p context: %p.\n", strsignal(signal), (void*)info, (void*)context); 10 | } 11 | 12 | TEST_CASE("setup_sigaction_handler") 13 | { 14 | REQUIRE(setup_sigaction_handler(&mysa_sigaction, {SIGUSR1, SIGUSR2})); 15 | } 16 | 17 | TEST_CASE("block_application_signals") 18 | { 19 | REQUIRE(block_application_signals({SIGINT})); 20 | } 21 | 22 | TEST_CASE("block_thread_signals") 23 | { 24 | REQUIRE(block_thread_signals({SIGINT})); 25 | } 26 | -------------------------------------------------------------------------------- /network/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_subdirectory(example) 3 | -------------------------------------------------------------------------------- /network/README.md: -------------------------------------------------------------------------------- 1 | # ihft::network 2 | 3 | This module contains network related code. 4 | -------------------------------------------------------------------------------- /network/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | add_executable(network_multicast_trace_listener multicast_trace_listener.cpp) 3 | add_executable(network_multicast_hello_sender multicast_hello_sender.cpp) 4 | 5 | add_executable(network_multicast_rtt_listener multicast_rtt_listener.cpp) 6 | add_executable(network_multicast_rtt_sender multicast_rtt_sender.cpp) 7 | 8 | add_executable(network_udp_echo_server udp_echo_server.cpp) 9 | add_executable(network_udp_rtt_client udp_rtt_client.cpp) 10 | -------------------------------------------------------------------------------- /network/example/multicast_hello_sender.cpp: -------------------------------------------------------------------------------- 1 | // https://tldp.org/HOWTO/Multicast-HOWTO-6.html 2 | // 3 | // Example: 4 | // ./network_multicast_sender 239.255.255.251 27335 192.168.88.50 5 | // 6 | 7 | #include "multicast_helper.h" 8 | 9 | #include 10 | #include 11 | 12 | int main(int argc, char* argv[]) 13 | { 14 | if (argc < 3) 15 | { 16 | printf("Command line args should be multicast group and port\n"); 17 | printf("(e.g. for SSDP, `sender 239.255.255.250 1900 [interface_ip]`)\n"); 18 | return 1; 19 | } 20 | 21 | const char* const group = argv[1]; // e.g. 239.255.255.250 for SSDP 22 | const unsigned short port = (unsigned short) atoi(argv[2]); // 0 if error, which is an invalid port 23 | const char* const source_iface = (argc == 4 ? argv[3] : nullptr); 24 | 25 | auto const opt = create_multicast_sender(source_iface); 26 | if (!opt) 27 | { 28 | return 1; 29 | } 30 | 31 | const int fd = opt.value(); 32 | 33 | // 34 | // set up destination address 35 | // 36 | struct sockaddr_in addr; 37 | memset(&addr, 0, sizeof(addr)); 38 | addr.sin_family = AF_INET; 39 | addr.sin_addr.s_addr = inet_addr(group); 40 | addr.sin_port = htons(port); 41 | 42 | // 43 | // now just sendto() our destination 44 | // 45 | for(unsigned i = 0; ; i++) 46 | { 47 | char buffer[64]; 48 | memset(buffer, '\0', sizeof(buffer)); 49 | snprintf(buffer, sizeof(buffer), "Hello, World! Sequence: %u", i & 0xFF); 50 | 51 | ssize_t const nbytes = sendto( 52 | fd, 53 | buffer, 54 | sizeof(buffer), 55 | 0, 56 | (struct sockaddr*) &addr, 57 | sizeof(addr) 58 | ); 59 | if (nbytes < 0) 60 | { 61 | perror("sendto"); 62 | return 1; 63 | } 64 | 65 | constexpr int delay_secs = 1; 66 | 67 | sleep(delay_secs); // Unix sleep is seconds 68 | } 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /network/example/multicast_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | std::optional create_multicast_sender(const char* const source_iface) 14 | { 15 | // 16 | // create what looks like an ordinary UDP socket 17 | // 18 | const int fd = socket(AF_INET, SOCK_DGRAM, 0); 19 | if (fd < 0) 20 | { 21 | perror("socket"); 22 | return std::nullopt; 23 | } 24 | 25 | // 26 | // setup sender interface 27 | // 28 | struct ip_mreq mreq; 29 | memset(&mreq, 0, sizeof(mreq)); 30 | mreq.imr_interface.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY); 31 | 32 | if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, (void*) &mreq, sizeof(mreq)) < 0) 33 | { 34 | perror("setsockopt"); 35 | return std::nullopt; 36 | } 37 | 38 | return fd; 39 | } 40 | 41 | std::optional create_multicast_listener(const char* const group, const unsigned short port, const char* const source_iface) 42 | { 43 | // 44 | // create what looks like an ordinary UDP socket 45 | // 46 | const int fd = socket(AF_INET, SOCK_DGRAM, 0); 47 | if (fd < 0) 48 | { 49 | perror("socket"); 50 | return std::nullopt; 51 | } 52 | 53 | // 54 | // allow multiple sockets to use the same PORT number 55 | // 56 | const u_int yes = 1; 57 | if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes)) < 0) 58 | { 59 | perror("Reusing ADDR failed"); 60 | return std::nullopt; 61 | } 62 | 63 | // 64 | // set up destination address 65 | // 66 | struct sockaddr_in addr; 67 | memset(&addr, 0, sizeof(addr)); 68 | addr.sin_family = AF_INET; 69 | addr.sin_addr.s_addr = inet_addr(group); 70 | addr.sin_port = htons((unsigned short)port); 71 | 72 | // 73 | // bind to receive address 74 | // 75 | if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) 76 | { 77 | perror("bind"); 78 | return std::nullopt; 79 | } 80 | 81 | // 82 | // use setsockopt() to request that the kernel join a multicast group 83 | // 84 | struct ip_mreq mreq; 85 | memset(&mreq, 0, sizeof(mreq)); 86 | mreq.imr_multiaddr.s_addr = inet_addr(group); 87 | mreq.imr_interface.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY); 88 | if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)) < 0) 89 | { 90 | perror("setsockopt"); 91 | return std::nullopt; 92 | } 93 | 94 | return fd; 95 | } 96 | -------------------------------------------------------------------------------- /network/example/multicast_rtt_listener.cpp: -------------------------------------------------------------------------------- 1 | // https://tldp.org/HOWTO/Multicast-HOWTO-6.html 2 | // 3 | // Example: 4 | // ./network_multicast_listener 239.255.255.251 27335 192.168.88.50 5 | // 6 | 7 | #include "multicast_helper.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | if (argc < 3) 16 | { 17 | printf("Command line args should be multicast group and port and [interface] optional\n"); 18 | printf("(e.g. for SSDP, `listener 239.255.255.250 1900 [192.168.1.1]`)\n"); 19 | return 1; 20 | } 21 | 22 | const char* const group = argv[1]; // e.g. 239.255.255.250 for SSDP 23 | const unsigned short port = (unsigned short) atoi(argv[2]); // 0 if error, which is an invalid port 24 | const char* const source_iface = (argc == 4) ? argv[3] : NULL; 25 | 26 | auto const opt = create_multicast_listener(group, port, source_iface); 27 | if (!opt) 28 | { 29 | return 1; 30 | } 31 | 32 | const int fd = opt.value(); 33 | 34 | // 35 | // now just enter a read-print loop 36 | // 37 | struct sockaddr_in addr{}; 38 | char msgbuf[4096]; 39 | while(true) 40 | { 41 | unsigned addrlen = sizeof(addr); 42 | ssize_t const nbytes = recvfrom( 43 | fd, 44 | msgbuf, 45 | sizeof(msgbuf), 46 | 0, 47 | (struct sockaddr*) &addr, 48 | &addrlen 49 | ); 50 | if (nbytes < 0) 51 | { 52 | perror("recvfrom"); 53 | return 1; 54 | } 55 | struct timespec tp; 56 | clock_gettime(CLOCK_REALTIME, &tp); 57 | long const end_micro = tp.tv_sec * 1000 * 1000 * 1000 + tp.tv_nsec; 58 | 59 | long start_micro = 0; 60 | if (nbytes == sizeof(start_micro)) 61 | { 62 | long const* start_micro_ptr = (long const*) msgbuf; 63 | start_micro = *start_micro_ptr; 64 | long const delta = end_micro - start_micro; 65 | 66 | printf("from: %s time delta: %ld nanoseconds\n", inet_ntoa(addr.sin_addr), delta); 67 | } 68 | } 69 | 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /network/example/multicast_rtt_sender.cpp: -------------------------------------------------------------------------------- 1 | // https://tldp.org/HOWTO/Multicast-HOWTO-6.html 2 | // 3 | // Example: 4 | // ./network_multicast_sender 239.255.255.251 27335 192.168.88.50 5 | // 6 | 7 | #include "multicast_helper.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | if (argc < 3) 16 | { 17 | printf("Command line args should be multicast group and port\n"); 18 | printf("(e.g. for SSDP, `sender 239.255.255.250 1900 [interface_ip]`)\n"); 19 | return 1; 20 | } 21 | 22 | const char* const group = argv[1]; // e.g. 239.255.255.250 for SSDP 23 | const unsigned short port = (unsigned short) atoi(argv[2]); // 0 if error, which is an invalid port 24 | const char* const source_iface = (argc == 4 ? argv[3] : NULL); 25 | 26 | auto const opt = create_multicast_sender(source_iface); 27 | if (!opt) 28 | { 29 | return 1; 30 | } 31 | 32 | const int fd = opt.value(); 33 | 34 | // 35 | // set up destination address 36 | // 37 | struct sockaddr_in addr; 38 | memset(&addr, 0, sizeof(addr)); 39 | addr.sin_family = AF_INET; 40 | addr.sin_addr.s_addr = inet_addr(group); 41 | addr.sin_port = htons(port); 42 | 43 | // 44 | // now just sendto() our destination 45 | // 46 | for(unsigned i = 0; i < 1'000'000'000; i++) 47 | { 48 | struct timespec tp; 49 | clock_gettime(CLOCK_REALTIME, &tp); 50 | long const start_micro = tp.tv_sec * 1000 * 1000 * 1000 + tp.tv_nsec; 51 | 52 | ssize_t const nbytes = sendto( 53 | fd, 54 | &start_micro, 55 | sizeof(start_micro), 56 | 0, 57 | (struct sockaddr*) &addr, 58 | sizeof(addr) 59 | ); 60 | if (nbytes < 0) 61 | { 62 | perror("sendto"); 63 | return 1; 64 | } 65 | 66 | constexpr int delay_secs = 1; 67 | 68 | sleep(delay_secs); // Unix sleep is seconds 69 | } 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /network/example/multicast_trace_listener.cpp: -------------------------------------------------------------------------------- 1 | // https://tldp.org/HOWTO/Multicast-HOWTO-6.html 2 | // 3 | // Example: 4 | // ./network_multicast_listener 239.255.255.251 27335 192.168.88.50 5 | // 6 | 7 | #include "multicast_helper.h" 8 | 9 | #include 10 | #include 11 | 12 | int main(int argc, char* argv[]) 13 | { 14 | if (argc < 3) 15 | { 16 | printf("Command line args should be multicast group and port and [interface] optional\n"); 17 | printf("(e.g. for SSDP, `listener 239.255.255.250 1900 [192.168.1.1]`)\n"); 18 | return 1; 19 | } 20 | 21 | const char* const group = argv[1]; // e.g. 239.255.255.250 for SSDP 22 | const unsigned short port = (unsigned short) atoi(argv[2]); // 0 if error, which is an invalid port 23 | const char* const source_iface = (argc == 4) ? argv[3] : NULL; 24 | 25 | auto const opt = create_multicast_listener(group, port, source_iface); 26 | if (!opt) 27 | { 28 | return 1; 29 | } 30 | 31 | const int fd = opt.value(); 32 | 33 | // 34 | // now just enter a read-print loop 35 | // 36 | struct sockaddr_in addr{}; 37 | char msgbuf[4096]; 38 | while(true) 39 | { 40 | unsigned addrlen = sizeof(addr); 41 | ssize_t const nbytes = recvfrom( 42 | fd, 43 | msgbuf, 44 | sizeof(msgbuf), 45 | 0, 46 | (struct sockaddr*) &addr, 47 | &addrlen 48 | ); 49 | if (nbytes < 0) { 50 | perror("recvfrom"); 51 | return 1; 52 | } 53 | msgbuf[nbytes] = '\0'; 54 | printf("from: %s message: %s\n", inet_ntoa(addr.sin_addr), msgbuf); 55 | } 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /network/example/udp_echo_server.cpp: -------------------------------------------------------------------------------- 1 | #include "udp_helper.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char* argv[]) 7 | { 8 | if (argc < 2) 9 | { 10 | printf("Usage: %s 'port' '[interface]' optional\n", argv[0]); 11 | return 1; 12 | } 13 | 14 | const unsigned short server_port = (unsigned short) atoi(argv[1]); 15 | const char* const source_iface = (argc == 3) ? argv[2] : nullptr; 16 | 17 | auto const opt = create_udp_server(server_port, source_iface); 18 | if (!opt) 19 | { 20 | return 1; 21 | } 22 | 23 | auto const sock = opt.value(); 24 | 25 | // socket address used to store client address 26 | struct sockaddr_in client_address; 27 | memset(&client_address, 0, sizeof(client_address)); 28 | socklen_t client_address_len = sizeof(client_address); 29 | 30 | // run indefinitely 31 | while(true) 32 | { 33 | char buffer[4096]; 34 | 35 | // read content into buffer from an incoming client 36 | ssize_t const len = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_address, &client_address_len); 37 | 38 | if (len < 0) 39 | { 40 | continue; 41 | } 42 | 43 | size_t const clen = (size_t)(len); 44 | 45 | // send same content back to the client ("echo") 46 | sendto(sock, buffer, clen, 0, (struct sockaddr*)&client_address, sizeof(client_address)); 47 | 48 | buffer[len] = '\0'; 49 | // inet_ntoa prints user friendly representation of the ip address 50 | printf("received: '%s' from client %s:%d\n", buffer, inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port)); 51 | } 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /network/example/udp_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | std::optional create_udp_server(const unsigned short server_port, const char* const source_iface) 12 | { 13 | // open socket 14 | int const sock = socket(PF_INET, SOCK_DGRAM, 0); 15 | if (sock < 0) 16 | { 17 | printf("could not create socket\n"); 18 | return std::nullopt; 19 | } 20 | 21 | // socket address used for the server 22 | struct sockaddr_in server_address; 23 | memset(&server_address, 0, sizeof(server_address)); 24 | server_address.sin_family = AF_INET; 25 | server_address.sin_port = htons((unsigned short)server_port); 26 | server_address.sin_addr.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY); 27 | 28 | // bind it to listen to the incoming connections on the created server address 29 | if ((bind(sock, (struct sockaddr*) &server_address, sizeof(server_address))) < 0) 30 | { 31 | printf("could not bind socket\n"); 32 | return std::nullopt; 33 | } 34 | 35 | return sock; 36 | } 37 | 38 | std::optional create_udp_client(const char* const source_iface) 39 | { 40 | // open socket 41 | int const sock = socket(PF_INET, SOCK_DGRAM, 0); 42 | if (sock < 0) 43 | { 44 | printf("could not create socket\n"); 45 | return std::nullopt; 46 | } 47 | 48 | struct sockaddr_in addr; 49 | memset(&addr, 0, sizeof(addr)); 50 | addr.sin_family = AF_INET; 51 | addr.sin_addr.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY); 52 | 53 | // bind to send address 54 | if (bind(sock, (struct sockaddr*) &addr, sizeof(addr)) < 0) 55 | { 56 | perror("bind"); 57 | return std::nullopt; 58 | } 59 | 60 | return sock; 61 | } 62 | -------------------------------------------------------------------------------- /network/example/udp_rtt_client.cpp: -------------------------------------------------------------------------------- 1 | #include "udp_helper.h" 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char* argv[]) 7 | { 8 | if (argc < 4) 9 | { 10 | printf("Usage: %s '' '' 'message' '[interface]' optional\n", argv[0]); 11 | return EXIT_FAILURE; 12 | } 13 | 14 | const char* const server_name = argv[1]; 15 | const unsigned short server_port = (unsigned short) atoi(argv[2]); 16 | const char* const data_to_send = argv[3]; 17 | const char* const source_iface = (argc == 5) ? argv[4] : nullptr; 18 | 19 | auto const opt = create_udp_client(source_iface); 20 | if (!opt) 21 | { 22 | return 1; 23 | } 24 | 25 | auto const sock = opt.value(); 26 | 27 | struct sockaddr_in server_address; 28 | memset(&server_address, 0, sizeof(server_address)); 29 | server_address.sin_family = AF_INET; 30 | server_address.sin_addr.s_addr = inet_addr(server_name); 31 | server_address.sin_port = htons(server_port); 32 | 33 | struct timespec tp; 34 | clock_gettime(CLOCK_REALTIME, &tp); 35 | long const start_micro = tp.tv_sec * 1000 * 1000 * 1000 + tp.tv_nsec; 36 | 37 | // send data 38 | ssize_t const len = sendto(sock, data_to_send, strlen(data_to_send), 0, 39 | (struct sockaddr*) &server_address, sizeof(server_address)); 40 | 41 | if (len < 0) 42 | { 43 | printf("sendto return zero"); 44 | return 1; 45 | } 46 | 47 | size_t const clen = (size_t)(len); 48 | 49 | // received echoed data back 50 | char buffer[4096]; 51 | recvfrom(sock, buffer, clen, 0, NULL, NULL); 52 | 53 | clock_gettime(CLOCK_REALTIME, &tp); 54 | long const end_micro = tp.tv_sec * 1000 * 1000 * 1000 + tp.tv_nsec; 55 | long const delta = end_micro - start_micro; 56 | 57 | buffer[len] = '\0'; 58 | printf("time delta: %ld nanoseconds, recieved: '%s'\n", delta, buffer); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /platform/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_platform src/platform.cpp src/cmdline.cpp src/process_cpu_list.cpp) 2 | target_include_directories(ihft_platform PUBLIC include) 3 | target_link_libraries(ihft_platform PUBLIC ihft_types PRIVATE ihft_constant) 4 | 5 | add_subdirectory(test) 6 | add_subdirectory(example) 7 | -------------------------------------------------------------------------------- /platform/README.md: -------------------------------------------------------------------------------- 1 | # ihft::platform 2 | 3 | This module provides a bridge for working with the application startup system. 4 | 5 | The main class is [ihft::platform::trait](include/platform/platform.h). 6 | 7 | ```cpp 8 | namespace ihft::platform 9 | { 10 | 11 | struct trait 12 | ``` 13 | 14 | It contains a lot of methods for detecting system configuration or change some runtime parameters. 15 | 16 | ## Examples 17 | 18 | [set_thread_cpu example](example/set_thread_cpu.cpp) 19 | 20 | [set_thread_name example](example/set_thread_name.cpp) 21 | 22 | [get_platform_info example](example/get_platform_info.cpp) 23 | 24 | [core_2_core_latancy example](example/core_2_core_latancy.cpp) 25 | 26 | [sysjitter example](example/sysjitter.cpp) 27 | 28 | [bogatyr example](example/bogatyr.cpp) 29 | 30 | [greek_alphabet example](example/greek_alphabet.cpp) 31 | 32 | [china_cities example](example/china_cities.cpp) 33 | -------------------------------------------------------------------------------- /platform/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # manual example apps 2 | add_executable(platform_set_thread_name set_thread_name.cpp) 3 | target_link_libraries(platform_set_thread_name ihft_platform) 4 | 5 | add_executable(platform_set_thread_cpu set_thread_cpu.cpp) 6 | target_link_libraries(platform_set_thread_cpu ihft_platform) 7 | 8 | add_executable(platform_get_info get_platform_info.cpp) 9 | target_link_libraries(platform_get_info ihft_platform) 10 | 11 | add_executable(platform_core_2_core_latancy core_2_core_latancy.cpp) 12 | target_link_libraries(platform_core_2_core_latancy ihft_platform) 13 | target_link_libraries(platform_core_2_core_latancy ihft_constant) 14 | 15 | add_executable(platform_sysjitter sysjitter.cpp) 16 | target_link_libraries(platform_sysjitter ihft_platform) 17 | target_link_libraries(platform_sysjitter ihft_compiler) 18 | target_link_libraries(platform_sysjitter ihft_constant) 19 | 20 | add_executable(platform_greek_alphabet greek_alphabet.cpp) 21 | target_link_libraries(platform_greek_alphabet ihft_platform) 22 | 23 | add_executable(platform_china_cities china_cities.cpp) 24 | target_link_libraries(platform_china_cities ihft_platform) 25 | 26 | add_executable(platform_bogatyr bogatyr.cpp) 27 | target_link_libraries(platform_bogatyr ihft_platform) 28 | -------------------------------------------------------------------------------- /platform/example/bogatyr.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | [[maybe_unused]] inline constexpr const char * const russian_bogatyrs[] = { 11 | "Добрыня Никитич", 12 | "Илья Муромец", 13 | "Алёша Попович" 14 | }; 15 | 16 | int main() 17 | { 18 | std::vector threads; 19 | 20 | for(size_t i = 0; i < std::size(russian_bogatyrs); i++) 21 | { 22 | threads.emplace_back([i](){ 23 | auto const ptr = russian_bogatyrs[i]; 24 | ihft::platform::trait::set_current_thread_name(ptr); 25 | 26 | using namespace std::chrono_literals; 27 | std::this_thread::sleep_for(60s); 28 | }); 29 | } 30 | 31 | for(auto& t : threads) 32 | { 33 | t.join(); 34 | } 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /platform/example/china_cities.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | [[maybe_unused]] inline constexpr const char * const cities_in_china[] = { 11 | "上海", 12 | "北京", 13 | "深圳", 14 | "香港", 15 | "广州", 16 | "重庆", 17 | "天津", 18 | "苏州", 19 | "成都", 20 | "澳门", 21 | "台北", 22 | "武汉" 23 | }; 24 | 25 | int main() 26 | { 27 | std::vector threads; 28 | 29 | for(size_t i = 0; i < std::size(cities_in_china); i++) 30 | { 31 | threads.emplace_back([i](){ 32 | auto const ptr = cities_in_china[i]; 33 | ihft::platform::trait::set_current_thread_name(ptr); 34 | 35 | using namespace std::chrono_literals; 36 | std::this_thread::sleep_for(60s); 37 | }); 38 | } 39 | 40 | for(auto& t : threads) 41 | { 42 | t.join(); 43 | } 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /platform/example/core_2_core_latancy.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // This program was inspired by https://github.com/rigtorp/c2clat 3 | // 4 | // A tool to measure CPU core to core latency (inter-core latency). 5 | // 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | using plf = ihft::platform::trait; 20 | 21 | int main() 22 | { 23 | auto const nthreads = std::thread::hardware_concurrency(); 24 | 25 | std::vector cpus(nthreads); 26 | std::iota(cpus.begin(), cpus.end(), 0); 27 | 28 | std::map, std::chrono::nanoseconds> results; 29 | 30 | constexpr int repeats = 1024; 31 | constexpr int iters = 256; 32 | 33 | for(unsigned i = 0; i < cpus.size(); i++) 34 | { 35 | for(unsigned j = i + 1; j < cpus.size(); j++) 36 | { 37 | alignas(ihft::constant::CPU_CACHE_LINE_SIZE) std::atomic seq1 = {-1}; 38 | alignas(ihft::constant::CPU_CACHE_LINE_SIZE) std::atomic seq2 = {-1}; 39 | 40 | auto t = std::thread([&](){ 41 | plf::set_current_thread_cpu(cpus[i]); 42 | for(int k = 0; k < repeats; k++) 43 | { 44 | for(int h = 0; h < iters; h++) 45 | { 46 | while(seq1.load(std::memory_order_acquire) != h); 47 | seq2.store(h, std::memory_order_release); 48 | } 49 | } 50 | }); 51 | 52 | std::chrono::nanoseconds rtt = std::chrono::nanoseconds::max(); 53 | 54 | plf::set_current_thread_cpu(cpus[j]); 55 | for(int k = 0; k < repeats; k++) 56 | { 57 | seq1 = seq2 = -1; 58 | auto const ts1 = std::chrono::steady_clock::now(); 59 | for(int h = 0; h < iters; h++) 60 | { 61 | seq1.store(h, std::memory_order_release); 62 | while(seq2.load(std::memory_order_acquire) != h); 63 | } 64 | auto const ts2 = std::chrono::steady_clock::now(); 65 | rtt = std::min(rtt, ts2 - ts1); 66 | } 67 | 68 | t.join(); 69 | 70 | auto const real_rtt = rtt / 2 / iters; 71 | results[{i, j}] = real_rtt; 72 | results[{j, i}] = real_rtt; 73 | } 74 | } 75 | 76 | std::cout << "Core <-> Core latency report. Units: (nanoseconds)\n"; 77 | std::cout << std::setw(4) << "CPU"; 78 | for(size_t i = 0; i < cpus.size(); ++i) 79 | { 80 | std::cout << " " << std::setw(4) << cpus[i]; 81 | } 82 | std::cout << "\n"; 83 | for(size_t i = 0; i < cpus.size(); ++i) 84 | { 85 | std::cout << std::setw(4) << cpus[i]; 86 | for(size_t j = 0; j < cpus.size(); ++j) 87 | { 88 | std::cout << " " << std::setw(4) << results[{i, j}].count(); 89 | } 90 | std::cout << "\n"; 91 | } 92 | std::cout.flush(); 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /platform/example/get_platform_info.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // A tool to get linux system configuration details for low latency applications. 3 | // 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using plf = ihft::platform::trait; 13 | 14 | int main() 15 | { 16 | std::cout << "IHFT platform information\n" << std::endl; 17 | 18 | std::cout << "Hugepage allocator info (how many pages are available):" << std::endl; 19 | std::cout << "Total 1GB hugepages: " << plf::total_1gb_hugepages() << std::endl; 20 | std::cout << "Total 2MB hugepages: " << plf::total_2mb_hugepages() << std::endl; 21 | 22 | std::cout << "\n"; 23 | 24 | std::cout << "Positive factors (should be true):" << std::endl; 25 | std::cout << "Is CPU scaling governor use performance: " << std::boolalpha << plf::is_scaling_governor_use_performance_mode() << std::endl; 26 | 27 | std::cout << "\n"; 28 | 29 | std::cout << "Negative factors (should be false):" << std::endl; 30 | std::cout << "Is hyper threading active: " << std::boolalpha << plf::is_hyper_threading_active() << std::endl; 31 | std::cout << "Is transparent_hugepage active: " << std::boolalpha << plf::is_transparent_hugepages_active() << std::endl; 32 | std::cout << "Is swap active: " << std::boolalpha << plf::is_swap_active() << std::endl; 33 | 34 | std::cout << "\n"; 35 | 36 | std::cout << "| cpu | isolation | nohz_full | rcu_nocbs |" << std::endl; 37 | for(unsigned int i = 0; i < std::thread::hardware_concurrency(); i++) 38 | { 39 | std::cout << std::noboolalpha 40 | << std::setw(5) << i 41 | << std::setw(12) << (plf::get_cpu_isolation_status(i) ? '*' : ' ') 42 | << std::setw(12) << (plf::get_cpu_nohz_full_status(i) ? '*' : ' ') 43 | << std::setw(12) << (plf::get_cpu_rcu_nocbs_status(i) ? '*' : ' ') 44 | << std::endl; 45 | } 46 | 47 | return EXIT_SUCCESS; 48 | } 49 | -------------------------------------------------------------------------------- /platform/example/greek_alphabet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | [[maybe_unused]] inline constexpr const char * const greek_alphabet_ascii[] = { 11 | "alpha", 12 | "beta", 13 | "gamma", 14 | "delta", 15 | "epsilon", 16 | "zeta", 17 | "eta", 18 | "theta", 19 | "iota", 20 | "kappa", 21 | "lamda", 22 | "mu", 23 | "nu", 24 | "xi", 25 | "omicron", 26 | "pi", 27 | "rho", 28 | "sigma", 29 | "tau", 30 | "upsilon", 31 | "phi", 32 | "chi", 33 | "psi", 34 | "omega" 35 | }; 36 | 37 | [[maybe_unused]] inline constexpr const char * const greek_alphabet_utf8[] = { 38 | "\u03B1", 39 | "\u03B2", 40 | "\u03B3", 41 | "\u03B4", 42 | "\u03B5", 43 | "\u03B6", 44 | "\u03B7", 45 | "\u03B8", 46 | "\u03B9", 47 | "\u03BA", 48 | "\u03BB", 49 | "\u03BC", 50 | "\u03BD", 51 | "\u03BE", 52 | "\u03BF", 53 | "\u03C0", 54 | "\u03C1", 55 | "\u03C3", 56 | "\u03C4", 57 | "\u03C5", 58 | "\u03C6", 59 | "\u03C7", 60 | "\u03C8", 61 | "\u03C9" 62 | }; 63 | 64 | static_assert(std::size(greek_alphabet_ascii) == std::size(greek_alphabet_utf8)); 65 | 66 | int main() 67 | { 68 | std::vector threads; 69 | 70 | for(size_t i = 0; i < std::size(greek_alphabet_utf8); i++) 71 | { 72 | threads.emplace_back([i](){ 73 | auto const ptr = greek_alphabet_utf8[i]; 74 | ihft::platform::trait::set_current_thread_name(ptr); 75 | 76 | using namespace std::chrono_literals; 77 | std::this_thread::sleep_for(60s); 78 | }); 79 | } 80 | 81 | for(auto& t : threads) 82 | { 83 | t.join(); 84 | } 85 | 86 | return 0; 87 | } 88 | -------------------------------------------------------------------------------- /platform/example/set_thread_cpu.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char* argv[]) 8 | { 9 | if (argc != 2) 10 | { 11 | std::cout << "usage: " << argv[0] << " " << std::endl; 12 | return EXIT_FAILURE; 13 | } 14 | 15 | std::thread thread([argv](){ 16 | auto const cpu = static_cast(std::stoul(argv[1])); 17 | ihft::platform::trait::set_current_thread_cpu(cpu); 18 | std::cout << "Enter any symbol for exit... "; 19 | char c; 20 | std::cin >> c; 21 | }); 22 | 23 | thread.join(); 24 | 25 | return EXIT_SUCCESS; 26 | } 27 | -------------------------------------------------------------------------------- /platform/example/set_thread_name.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char* argv[]) 8 | { 9 | if (argc != 2) 10 | { 11 | std::cout << "usage: " << argv[0] << " " << std::endl; 12 | return EXIT_FAILURE; 13 | } 14 | 15 | std::thread thread([argv](){ 16 | ihft::platform::trait::set_current_thread_name(argv[1]); 17 | std::cout << "Enter any symbol for exit... "; 18 | char c; 19 | std::cin >> c; 20 | }); 21 | 22 | thread.join(); 23 | 24 | return EXIT_SUCCESS; 25 | } 26 | -------------------------------------------------------------------------------- /platform/include/platform/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // 4 | // This code contains a lot of common methods for detecting system 5 | // configuration or change some runtime parameters 6 | // 7 | // See examples: get_platform_info & set_thread_cpu & set_thread_name 8 | // 9 | namespace ihft::platform 10 | { 11 | 12 | struct trait 13 | { 14 | // Change current thread name. Could be useful with htop 15 | // The name can be up to 16 bytes long, including the terminating null byte. 16 | // (If the length of the string, including the terminating null byte, 17 | // exceeds 16 bytes, the string is silently truncated.) 18 | static bool set_current_thread_name(const char* name) noexcept; 19 | 20 | // What CPU isolation is? 21 | // https://lwn.net/Articles/816298/ 22 | // https://www.suse.com/support/kb/doc/?id=000017747 23 | // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux_for_real_time/7/html/tuning_guide/isolating_cpus_using_tuned-profiles-realtime 24 | 25 | // Change current thread cpu 26 | static bool set_current_thread_cpu(unsigned cpu) noexcept; 27 | 28 | // Reset current thread cpu mask 29 | static bool reset_current_thread_cpu() noexcept; 30 | 31 | // Get total cpus count on current machine 32 | static unsigned get_total_cpus() noexcept; 33 | 34 | // Get current thread native id 35 | static long get_thread_id() noexcept; 36 | 37 | // This call locks all pages mapped into the address space of the calling process. 38 | // This includes the pages of the code, data, and stack segment, 39 | // as well as shared libraries, user space kernel data, shared memory, and memory-mapped files. 40 | // All mapped pages are guaranteed to be resident in RAM when the call returns successfully. 41 | // The pages are guaranteed to stay in RAM until later unlocked. 42 | // https://man7.org/linux/man-pages/man2/mlock.2.html 43 | static bool lock_memory_pages(bool current, bool future) noexcept; 44 | 45 | // Check cpu isolation 46 | static bool get_cpu_isolation_status(unsigned cpu) noexcept; 47 | 48 | // Check cpu nohz_full 49 | static bool get_cpu_nohz_full_status(unsigned cpu) noexcept; 50 | 51 | // Check cpu rcu_nocbs 52 | static bool get_cpu_rcu_nocbs_status(unsigned cpu) noexcept; 53 | 54 | // Hugepages detector 55 | // https://rigtorp.se/hugepages/ 56 | // Get total 1gb hugepages 57 | static unsigned total_1gb_hugepages() noexcept; 58 | 59 | // Get total 2mb hugepages 60 | static unsigned total_2mb_hugepages() noexcept; 61 | 62 | // 63 | // System cpu & memory features 64 | // https://rigtorp.se/low-latency-guide/ 65 | // 66 | 67 | // All parameters below should be true for low latency 68 | 69 | // Check CPU frequency scaling governor mode 70 | static bool is_scaling_governor_use_performance_mode() noexcept; 71 | 72 | // All parameters below should be false for low latency 73 | 74 | // Check hyper-threading 75 | static bool is_hyper_threading_active() noexcept; 76 | 77 | // Check swaps 78 | static bool is_swap_active() noexcept; 79 | 80 | // Check transparent hugepages is [always] or [madvise] 81 | static bool is_transparent_hugepages_active() noexcept; 82 | }; 83 | 84 | } 85 | -------------------------------------------------------------------------------- /platform/include/platform/private/cmdline.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::impl 6 | { 7 | 8 | class cmdline 9 | { 10 | public: 11 | explicit cmdline(const char* file); 12 | 13 | bool is_isolated(unsigned cpu) const noexcept 14 | { 15 | return m_isolated.test(cpu); 16 | } 17 | 18 | bool is_nohz_fulled(unsigned cpu) const noexcept 19 | { 20 | return m_nohz_fulled.test(cpu); 21 | } 22 | 23 | bool is_rcu_nocbsed(unsigned cpu) const noexcept 24 | { 25 | return m_rcu_nocbsed.test(cpu); 26 | } 27 | 28 | private: 29 | static constexpr size_t MAX_CPUS = 512; 30 | 31 | std::bitset m_isolated; 32 | std::bitset m_nohz_fulled; 33 | std::bitset m_rcu_nocbsed; 34 | }; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /platform/include/platform/process_cpu_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // 7 | // This code process cpu list from /proc/cmdline & other linux files 8 | // and execute functor: Functor for each cpu in list or range cpus 9 | // 10 | // Example: 11 | // 1,2,3 -> cpus: [1, 2, 3] 12 | // 1-3,5-7 -> cpus: [1, 2, 3, 5, 6, 7] 13 | // 14 | namespace ihft::platform 15 | { 16 | 17 | void process_cpu_list(std::string_view cpus, types::function_ref functor); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /platform/src/cmdline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ihft::impl 11 | { 12 | 13 | static_assert(sizeof(cmdline) == 3 * constant::CPU_CACHE_LINE_SIZE); 14 | 15 | cmdline::cmdline(const char* file) 16 | { 17 | std::ifstream input(file); 18 | 19 | auto process = [](auto const& text, auto const pattern, auto& list) 20 | { 21 | auto const index = text.find(pattern); 22 | if (index != std::string::npos) 23 | { 24 | auto const cpus = std::string_view(text).substr(index + strlen(pattern)); 25 | ihft::platform::process_cpu_list(cpus, [&](unsigned cpu) mutable 26 | { 27 | list.set(cpu); 28 | }); 29 | } 30 | }; 31 | 32 | const char * const isolcpus_pattern = "isolcpus="; 33 | const char * const nohz_full_pattern = "nohz_full="; 34 | const char * const rcu_nocbs_pattern = "rcu_nocbs="; 35 | 36 | std::string text; 37 | while(input >> text) 38 | { 39 | process(text, isolcpus_pattern, m_isolated); 40 | process(text, nohz_full_pattern, m_nohz_fulled); 41 | process(text, rcu_nocbs_pattern, m_rcu_nocbsed); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /platform/src/process_cpu_list.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace ihft::platform 6 | { 7 | 8 | void process_cpu_list(std::string_view cpus, types::function_ref functor) 9 | { 10 | size_t from{}; 11 | size_t next{}; 12 | 13 | do 14 | { 15 | next = cpus.find(',', from); 16 | auto const cpu = cpus.substr(from, (next != std::string_view::npos) ? (next - from) : std::string_view::npos); 17 | from = next + 1; 18 | 19 | unsigned result{}; 20 | if (auto [p, ec] = std::from_chars(cpu.begin(), cpu.end(), result); ec == std::errc()) 21 | { 22 | // only 1 cpu in section 23 | if (p == cpu.end()) 24 | { 25 | functor(result); 26 | } 27 | // range cpu in section 28 | else 29 | { 30 | if ('-' != *p) 31 | { 32 | return; 33 | } 34 | 35 | unsigned to_result{}; 36 | if (auto [to_p, to_ec] = std::from_chars(p + 1, cpu.end(), to_result); to_ec == std::errc()) 37 | { 38 | for(auto i = result; i <= to_result; i++) 39 | { 40 | functor(i); 41 | } 42 | } 43 | } 44 | } 45 | } 46 | while(next != std::string_view::npos); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /platform/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # unit test apps 2 | ihft_add_test(test_cmdline) 3 | target_link_libraries(test_cmdline PRIVATE ihft_types) 4 | target_link_libraries(test_cmdline PRIVATE ihft_platform) 5 | 6 | ihft_add_test(test_platform) 7 | target_link_libraries(test_platform PRIVATE ihft_platform) 8 | -------------------------------------------------------------------------------- /platform/test/test_cmdline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace ihft::types; 11 | 12 | namespace 13 | { 14 | 15 | template 16 | std::ostream& operator<<(std::ostream& os, std::vector const& vec) 17 | { 18 | os << "[ "; 19 | for(auto const& elem : vec) 20 | { 21 | os << elem << ' '; 22 | } 23 | os << "]"; 24 | return os; 25 | } 26 | 27 | } 28 | 29 | void test_impl(temp_file const& file, std::vector result) 30 | { 31 | std::vector data_isolated; 32 | std::vector data_nohz_fulled; 33 | std::vector data_rcu_nocbsed; 34 | 35 | ihft::impl::cmdline cmdline(file.fpath().c_str()); 36 | 37 | for(unsigned cpu = 0; cpu < 64; cpu++) 38 | { 39 | if (cmdline.is_isolated(cpu)) 40 | { 41 | data_isolated.push_back(cpu); 42 | } 43 | 44 | if (cmdline.is_nohz_fulled(cpu)) 45 | { 46 | data_nohz_fulled.push_back(cpu); 47 | } 48 | 49 | if (cmdline.is_rcu_nocbsed(cpu)) 50 | { 51 | data_rcu_nocbsed.push_back(cpu); 52 | } 53 | } 54 | 55 | REQUIRE( data_isolated == result ); 56 | REQUIRE( data_nohz_fulled == result ); 57 | REQUIRE( data_rcu_nocbsed == result ); 58 | } 59 | 60 | TEST_CASE("test_1") 61 | { 62 | temp_file file("cmdline_1.txt", 63 | "BOOT_IMAGE=/boot/vmlinuz-3.16.0-44-generic auto noprompt priority=critical locale=en_US isolcpus=3,5,7 nohz_full=3,5,7 rcu_nocbs=3,5,7 quiet\r\n"); 64 | 65 | test_impl(file, {3,5,7}); 66 | } 67 | 68 | TEST_CASE("test_2") 69 | { 70 | temp_file file("cmdline_2.txt", 71 | "BOOT_IMAGE=/boot/vmlinuz-3.16.0-44-generic auto noprompt priority=critical locale=en_US quiet nohz_full=9,17,2 rcu_nocbs=9,17,2 isolcpus=9,17,2\r\n"); 72 | 73 | test_impl(file, {2,9,17}); 74 | } 75 | 76 | TEST_CASE("test_3") 77 | { 78 | temp_file file("cmdline_3.txt", 79 | "BOOT_IMAGE=/boot/vmlinuz-3.16.0-44-generic auto noprompt priority=critical quiet isolcpus=8-11,17 nohz_full=8-11,17 rcu_nocbs=8-11,17 locale=en_US\r\n"); 80 | 81 | test_impl(file, {8,9,10,11,17}); 82 | } 83 | 84 | TEST_CASE("test_4") 85 | { 86 | temp_file file("cmdline_4.txt", 87 | "BOOT_IMAGE=/boot/vmlinuz-3.16.0-44-generic auto noprompt priority=critical quiet isolcpus=1-abc locale=en_US\r\n"); 88 | 89 | test_impl(file, {}); 90 | } 91 | 92 | TEST_CASE("test_5") 93 | { 94 | temp_file file("cmdline_5.txt", 95 | "BOOT_IMAGE=/boot/vmlinuz-3.16.0-44-generic auto noprompt priority=critical quiet isolcpus=1A3 locale=en_US\r\n"); 96 | 97 | test_impl(file, {}); 98 | } 99 | -------------------------------------------------------------------------------- /platform/test/test_platform.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | TEST_CASE("get_cpu_isolation_status") 8 | { 9 | for(unsigned int i = 0; i < std::thread::hardware_concurrency(); i++) 10 | { 11 | ihft::platform::trait::get_cpu_isolation_status(i); 12 | } 13 | } 14 | 15 | TEST_CASE("set_current_thread_name") 16 | { 17 | REQUIRE(ihft::platform::trait::set_current_thread_name("test")); 18 | } 19 | 20 | TEST_CASE("set_current_thread_cpu") 21 | { 22 | REQUIRE(ihft::platform::trait::set_current_thread_cpu(0)); 23 | } 24 | 25 | TEST_CASE("reset_current_thread_cpu") 26 | { 27 | REQUIRE(ihft::platform::trait::reset_current_thread_cpu()); 28 | } 29 | 30 | TEST_CASE("get_total_cpus") 31 | { 32 | REQUIRE(ihft::platform::trait::get_total_cpus() > 0); 33 | } 34 | 35 | TEST_CASE("lock_memory_pages") 36 | { 37 | // Github CI platforms has problem with mlockall. 38 | // I decided to skip this test in CI env. 39 | (ihft::platform::trait::lock_memory_pages(true, true)); 40 | } 41 | -------------------------------------------------------------------------------- /timer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_timer INTERFACE) 2 | target_include_directories(ihft_timer INTERFACE include) 3 | 4 | add_subdirectory(benchmark) 5 | -------------------------------------------------------------------------------- /timer/README.md: -------------------------------------------------------------------------------- 1 | # ihft::timer 2 | 3 | This module contains code with a low-level function for manipulating time. 4 | 5 | `cpu_counter` provides an access for native core tick counter. 6 | 7 | ```cpp 8 | namespace ihft::timer 9 | { 10 | 11 | inline unsigned long long cpu_counter() // aka RDTSC 12 | 13 | } 14 | ``` 15 | 16 | `cpu_pause` allows to sleep a several cpu cycles without enter in os mode. 17 | 18 | ```cpp 19 | namespace ihft::timer 20 | { 21 | 22 | inline void cpu_pause() // aka PAUSE 23 | 24 | } 25 | ``` 26 | 27 | ## Benchmarks 28 | 29 | [timer benchmark](benchmark/benchmark_timer.cpp) 30 | -------------------------------------------------------------------------------- /timer/benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_benchmark(benchmark_timer) 2 | target_link_libraries(benchmark_timer PRIVATE ihft_timer) 3 | -------------------------------------------------------------------------------- /timer/benchmark/benchmark_timer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | /* 7 | 8 | % sysctl -n machdep.cpu.brand_string 9 | 10 | Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz 11 | 12 | ------------------------------------------------------------------------------- 13 | cpu_counter benchmark 14 | ------------------------------------------------------------------------------- 15 | ../timer/benchmark/benchmark_timer.cpp:8 16 | ............................................................................... 17 | 18 | benchmark name samples iterations estimated 19 | mean low mean high mean 20 | std dev low std dev high std dev 21 | ------------------------------------------------------------------------------- 22 | cpu_counter() 100 5231 3.6617 ms 23 | 7.35063 ns 7.12136 ns 8.05525 ns 24 | 1.72034 ns 0.00152105 ns 3.80921 ns 25 | 26 | ------------------------------------------------------------------------------- 27 | cpu_pause benchmark 28 | ------------------------------------------------------------------------------- 29 | ../timer/benchmark/benchmark_timer.cpp:16 30 | ............................................................................... 31 | 32 | benchmark name samples iterations estimated 33 | mean low mean high mean 34 | std dev low std dev high std dev 35 | ------------------------------------------------------------------------------- 36 | cpu_pause() 100 652 3.7816 ms 37 | 45.1592 ns 43.0884 ns 47.5848 ns 38 | 11.4076 ns 10.1455 ns 14.7173 ns 39 | 40 | */ 41 | 42 | using namespace ihft::timer; 43 | 44 | TEST_CASE("cpu_counter benchmark") 45 | { 46 | BENCHMARK("cpu_counter()") 47 | { 48 | return cpu_counter(); 49 | }; 50 | } 51 | 52 | TEST_CASE("cpu_pause benchmark") 53 | { 54 | BENCHMARK("cpu_pause()") 55 | { 56 | return cpu_pause(); 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /timer/include/timer/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if __x86_64__ 6 | #include 7 | #endif 8 | 9 | namespace ihft::timer 10 | { 11 | 12 | inline std::uint64_t cpu_counter() 13 | { 14 | #if __x86_64__ 15 | // @todo : support ARM64 16 | return __rdtsc(); 17 | #else 18 | return 0; 19 | #endif 20 | } 21 | 22 | inline void cpu_pause() 23 | { 24 | #if __x86_64__ 25 | _mm_pause(); 26 | #else 27 | asm volatile("yield"); 28 | #endif 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /types/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(ihft_types INTERFACE) 2 | target_include_directories(ihft_types INTERFACE include) 3 | 4 | add_subdirectory(test) 5 | add_subdirectory(benchmark) 6 | -------------------------------------------------------------------------------- /types/README.md: -------------------------------------------------------------------------------- 1 | # ihft::types 2 | 3 | This module contains the code of basic auxiliary types that are missing in the current standard library. 4 | 5 | The box is memory control primitive. It has functionality similar to unique_ptr, but it doesn't call a delete, instead of this it calls destroy_at at destructor. It is useful with manually memory placed objects. [The source code: [box](include/types/box.h)]. 6 | 7 | ```cpp 8 | namespace ihft::types 9 | { 10 | 11 | template 12 | class box final 13 | ``` 14 | 15 | The type of delegate for calling a function over an interface without ownership. It provides a similar call price, but significantly benefits in the speed of construction, because it doesn't pack an extra data in a local buffer or heap. [The source code: [function_ref](include/types/function_ref.h)]. 16 | 17 | ```cpp 18 | namespace ihft::types 19 | { 20 | 21 | template 22 | class function_ref final 23 | ``` 24 | 25 | The type of function result. It contains result type or error type and provides cozy interface. [The source code: [result](include/types/result.h)]. 26 | 27 | ```cpp 28 | namespace ihft::types 29 | { 30 | 31 | template 32 | class result final 33 | ``` 34 | 35 | ## Benchmarks 36 | 37 | [function_ref benchmark](benchmark/benchmark_function_ref.cpp) 38 | -------------------------------------------------------------------------------- /types/benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_benchmark(benchmark_function_ref) 2 | target_link_libraries(benchmark_function_ref PRIVATE ihft_types ihft_compiler) 3 | 4 | ihft_add_benchmark(benchmark_memory_access) 5 | target_link_libraries(benchmark_memory_access PRIVATE ihft_compiler) 6 | 7 | ihft_add_benchmark(benchmark_charcmp_vs_strcmp) 8 | target_link_libraries(benchmark_charcmp_vs_strcmp PRIVATE ihft_compiler) 9 | -------------------------------------------------------------------------------- /types/benchmark/benchmark_charcmp_vs_strcmp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ------------------------------------------------------------------------------- 4 | char_cmp benchmark 5 | ------------------------------------------------------------------------------- 6 | ../types/benchmark/benchmark_char 7 | _vs_strcmp.cpp:13 8 | ............................................................................... 9 | 10 | benchmark name samples iterations estimated 11 | mean low mean high mean 12 | std dev low std dev high std dev 13 | ------------------------------------------------------------------------------- 14 | char_cmp('A', 'a') 100 7990 5.593 ms 15 | 7.01381 ns 6.79556 ns 7.41639 ns 16 | 1.46849 ns 0.949145 ns 2.33642 ns 17 | 18 | 19 | ------------------------------------------------------------------------------- 20 | str_cmp benchmark 21 | ------------------------------------------------------------------------------- 22 | ../types/benchmark/benchmark_char_vs_strcmp.cpp:29 23 | ............................................................................... 24 | 25 | benchmark name samples iterations estimated 26 | mean low mean high mean 27 | std dev low std dev high std dev 28 | ------------------------------------------------------------------------------- 29 | str_cmp("A", "a") 100 6514 5.8626 ms 30 | 10.2284 ns 9.76705 ns 11.0137 ns 31 | 3.00717 ns 1.957 ns 4.27222 ns 32 | 33 | 34 | =============================================================================== 35 | test cases: 2 | 2 passed 36 | assertions: - none - 37 | 38 | */ 39 | 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | IHFT_NOINLINE bool char_cmp(char c1, char c2) 47 | { 48 | return c1 == c2; 49 | } 50 | 51 | TEST_CASE("char_cmp benchmark") 52 | { 53 | const char c1 = 'A'; 54 | const char c2 = 'a'; 55 | 56 | BENCHMARK("char_cmp('A', 'a')") 57 | { 58 | return char_cmp(c1, c2); 59 | }; 60 | } 61 | 62 | IHFT_NOINLINE bool str_cmp(const char * const s1, const char * const s2) 63 | { 64 | return 0 == strcmp(s1, s2); 65 | } 66 | 67 | TEST_CASE("str_cmp benchmark") 68 | { 69 | const char* const s1 = "A"; 70 | const char* const s2 = "a"; 71 | 72 | BENCHMARK("str_cmp(\"A\", \"a\")") 73 | { 74 | return str_cmp(s1, s2); 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /types/benchmark/benchmark_memory_access.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | 8 | namespace 9 | { 10 | static int64_t g_counter{}; 11 | } 12 | 13 | IHFT_NOINLINE int64_t accumulate_global() 14 | { 15 | g_counter++; 16 | return g_counter; 17 | } 18 | 19 | TEST_CASE("global_variable benchmark") 20 | { 21 | BENCHMARK("accumulate_global()") 22 | { 23 | return accumulate_global(); 24 | }; 25 | } 26 | 27 | namespace 28 | { 29 | static thread_local int64_t g_tl_counter{}; 30 | } 31 | 32 | IHFT_NOINLINE int64_t accumulate_thread_local() 33 | { 34 | g_tl_counter++; 35 | return g_tl_counter; 36 | } 37 | 38 | TEST_CASE("thread_local_variable benchmark") 39 | { 40 | BENCHMARK("accumulate_thread_local()") 41 | { 42 | return accumulate_thread_local(); 43 | }; 44 | } 45 | 46 | IHFT_NOINLINE int64_t accumulate_by_reference(int64_t& ref) 47 | { 48 | ref++; 49 | return ref; 50 | } 51 | 52 | TEST_CASE("stack_local_variable benchmark") 53 | { 54 | int64_t var{}; 55 | BENCHMARK("accumulate_be_reference()") 56 | { 57 | return accumulate_by_reference(var); 58 | }; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /types/include/types/box.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace ihft::types 6 | { 7 | 8 | /// 9 | /// A simple pointer life-time control type. 10 | /// It has functionality similar to unique_ptr, but it doesn't call a delete, 11 | /// instead of this it calls destroy_at at destructor. 12 | /// It is useful with manually memory placed objects. 13 | /// 14 | 15 | template 16 | class box final 17 | { 18 | public: 19 | explicit box(T* iptr) 20 | : m_ptr(iptr) 21 | { 22 | } 23 | 24 | explicit box(std::nullptr_t) = delete; 25 | 26 | ~box() noexcept 27 | { 28 | if (has_value()) 29 | { 30 | std::destroy_at(m_ptr); 31 | } 32 | } 33 | 34 | box(box&& other) noexcept 35 | : m_ptr(other.m_ptr) 36 | { 37 | other.m_ptr = nullptr; 38 | } 39 | 40 | box& operator=(box&& data) noexcept = delete; 41 | 42 | box(const box&) = delete; 43 | box& operator=(const box&) = delete; 44 | 45 | bool has_value() const noexcept 46 | { 47 | return m_ptr != nullptr; 48 | } 49 | 50 | operator T const& () const noexcept 51 | { 52 | return *m_ptr; 53 | } 54 | 55 | private: 56 | T* m_ptr; 57 | }; 58 | 59 | } 60 | -------------------------------------------------------------------------------- /types/include/types/function_ref.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// 4 | /// ---------------------------------------------------------------------------- 5 | /// function_ref (Extra additions to ) 6 | /// ---------------------------------------------------------------------------- 7 | /// 8 | /// This code based on my own experience and some public code: 9 | /// 10 | /// https://llvm.org/doxygen/STLFunctionalExtras_8h_source.html 11 | /// https://github.com/rigtorp/Function/blob/master/Function.h 12 | /// https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible 13 | /// 14 | /// An efficient, type-erasing, non-owning reference to a callable. This is 15 | /// intended for use as the type of a function parameter that is not used 16 | /// after the function in question returns. 17 | /// 18 | /// This class does not own the callable, so it is not in general safe to store a function_ref. 19 | /// 20 | 21 | #include 22 | #include 23 | 24 | namespace ihft::types 25 | { 26 | 27 | template 28 | class function_ref; 29 | 30 | template 31 | class function_ref final 32 | { 33 | public: 34 | constexpr function_ref() noexcept = default; 35 | constexpr function_ref(std::nullptr_t) noexcept 36 | { 37 | } 38 | 39 | constexpr function_ref(Ret(*ptr)(Params...)) noexcept 40 | : m_callable(reinterpret_cast(ptr)) 41 | , m_callback(callback) 42 | { 43 | } 44 | 45 | template requires 46 | // This is not the copy-constructor. 47 | (!std::is_same_v, function_ref>) && 48 | // Functor must be callable and return a suitable type. 49 | (std::is_void_v || std::is_convertible_v()(std::declval()...)), Ret>) 50 | constexpr function_ref(Callable&& callable) noexcept 51 | : m_callable(reinterpret_cast(&callable)) 52 | , m_callback(callback::type>) 53 | { 54 | } 55 | 56 | constexpr Ret operator()(Params ... params) const 57 | { 58 | return m_callback(m_callable, std::forward(params) ...); 59 | } 60 | 61 | constexpr explicit operator bool() const noexcept 62 | { 63 | return m_callable != 0; 64 | } 65 | 66 | friend constexpr void swap(function_ref& f1, function_ref& f2) noexcept 67 | { 68 | std::swap(f1.m_callable, f2.m_callable); 69 | std::swap(f1.m_callback, f2.m_callback); 70 | } 71 | 72 | private: 73 | using callable_ptr = intptr_t; 74 | using callback_ptr = Ret(*)(callable_ptr, Params...); 75 | 76 | callable_ptr m_callable = 0; 77 | callback_ptr m_callback = nullptr; 78 | 79 | template 80 | static Ret callback(callable_ptr callable, Params ... params) 81 | { 82 | auto ptr = reinterpret_cast(callable); 83 | return (*ptr)(std::forward(params) ...); 84 | } 85 | }; 86 | 87 | } 88 | -------------------------------------------------------------------------------- /types/include/types/result.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // 6 | // This code was inspired by: 7 | // 8 | // https://doc.rust-lang.org/std/result/enum.Result.html 9 | // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html 10 | // 11 | // Class template result is a vocabulary type which contains an expected value of type T, or an error E. 12 | // 13 | 14 | namespace ihft::types 15 | { 16 | 17 | template 18 | class result final 19 | { 20 | public: 21 | constexpr result(T data) : m_variant(std::move(data)) 22 | { 23 | } 24 | 25 | constexpr result(E error) : m_variant(std::move(error)) 26 | { 27 | } 28 | 29 | constexpr bool has_value() const noexcept 30 | { 31 | return std::holds_alternative(m_variant); 32 | } 33 | 34 | // Returns true if parsing succeeeded. 35 | constexpr bool succeeded() const noexcept 36 | { 37 | return has_value(); 38 | } 39 | 40 | // Returns true if parsing failed. 41 | constexpr bool failed() const noexcept 42 | { 43 | return std::holds_alternative(m_variant); 44 | } 45 | 46 | constexpr operator bool() const noexcept 47 | { 48 | return has_value(); 49 | } 50 | 51 | // Get value 52 | 53 | constexpr T& value() & noexcept 54 | { 55 | return std::get(m_variant); 56 | } 57 | 58 | constexpr const T& value() const & noexcept 59 | { 60 | return std::get(m_variant); 61 | } 62 | 63 | constexpr T&& value() && noexcept 64 | { 65 | return std::get(std::move(m_variant)); 66 | } 67 | 68 | constexpr const T&& value() const && noexcept 69 | { 70 | return std::get(std::move(m_variant)); 71 | } 72 | 73 | // Get error 74 | 75 | constexpr E& error() & noexcept 76 | { 77 | return std::get(m_variant); 78 | } 79 | 80 | constexpr const E& error() const & noexcept 81 | { 82 | return std::get(m_variant); 83 | } 84 | 85 | constexpr E&& error() && noexcept 86 | { 87 | return std::get(std::move(m_variant)); 88 | } 89 | 90 | constexpr const E&& error() const && noexcept 91 | { 92 | return std::get(std::move(m_variant)); 93 | } 94 | 95 | template 96 | friend S& operator<<(S& os, const result& iresult) 97 | { 98 | if (iresult) 99 | { 100 | os << iresult.value(); 101 | } 102 | else 103 | { 104 | os << iresult.error(); 105 | } 106 | return os; 107 | } 108 | 109 | private: 110 | std::variant m_variant; 111 | }; 112 | 113 | } 114 | -------------------------------------------------------------------------------- /types/include/types/scope_exit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /// 4 | /// ---------------------------------------------------------------------------- 5 | /// scope_exit 6 | /// ---------------------------------------------------------------------------- 7 | /// 8 | /// This code based on my own experience and some public code: 9 | /// 10 | /// https://llvm.org/docs/doxygen/ScopeExit_8h_source.html 11 | /// 12 | /// This class allows to use lambda as scope destructor. 13 | /// 14 | 15 | #include 16 | #include 17 | 18 | namespace ihft::types 19 | { 20 | 21 | template 22 | class scope_exit final 23 | { 24 | public: 25 | template 26 | explicit scope_exit(Fp &&F) 27 | : m_exit_function(std::forward(F)) 28 | , m_engaged(true) 29 | { 30 | } 31 | 32 | scope_exit(scope_exit &&other) noexcept 33 | : m_exit_function(std::move(other.m_exit_function)) 34 | , m_engaged(other.m_engaged) 35 | { 36 | other.release(); 37 | } 38 | 39 | scope_exit(const scope_exit &) = delete; 40 | scope_exit &operator=(scope_exit &&) = delete; 41 | scope_exit &operator=(const scope_exit &) = delete; 42 | 43 | void release() 44 | { 45 | m_engaged = false; 46 | } 47 | 48 | ~scope_exit() { 49 | if (m_engaged) { 50 | m_exit_function(); 51 | } 52 | } 53 | 54 | private: 55 | Callable m_exit_function; 56 | bool m_engaged; // False once moved-from or release()d. 57 | }; 58 | 59 | 60 | template 61 | [[nodiscard]] scope_exit> make_scope_exit(Callable &&F) 62 | { 63 | return scope_exit>(std::forward(F)); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /types/include/types/temp_file.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace ihft::types 8 | { 9 | 10 | class temp_file final 11 | { 12 | public: 13 | temp_file(std::string fname, std::string_view content) 14 | : m_fname(std::move(fname)) 15 | { 16 | std::ofstream output(m_fname); 17 | output << content; 18 | } 19 | 20 | ~temp_file() 21 | { 22 | std::remove(m_fname.c_str()); 23 | } 24 | 25 | std::string const& fpath() const noexcept 26 | { 27 | return m_fname; 28 | } 29 | 30 | private: 31 | std::string m_fname; 32 | }; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /types/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ihft_add_test(test_sso) 2 | 3 | ihft_add_test(test_result) 4 | target_link_libraries(test_result PRIVATE ihft_types) 5 | 6 | ihft_add_test(test_function_ref) 7 | target_link_libraries(test_function_ref PRIVATE ihft_types) 8 | 9 | ihft_add_test(test_box) 10 | target_link_libraries(test_box PRIVATE ihft_types) 11 | 12 | ihft_add_test(test_scope_exit) 13 | target_link_libraries(test_scope_exit PRIVATE ihft_types) 14 | -------------------------------------------------------------------------------- /types/test/test_box.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | using namespace ihft::types; 8 | 9 | class RAII final 10 | { 11 | public: 12 | RAII(long& counter) : m_counter(counter) 13 | { 14 | m_counter++; 15 | } 16 | 17 | ~RAII() 18 | { 19 | m_counter--; 20 | } 21 | 22 | private: 23 | long& m_counter; 24 | }; 25 | 26 | TEST_CASE("create and destroy") 27 | { 28 | std::aligned_storage_t memory; 29 | 30 | long counter{}; 31 | 32 | REQUIRE(counter == 0); 33 | 34 | { 35 | box box(std::construct_at(reinterpret_cast(&memory), counter)); 36 | 37 | REQUIRE(counter == 1); 38 | REQUIRE(box.has_value()); 39 | REQUIRE(std::addressof(static_cast(box)) == reinterpret_cast(&memory)); 40 | } 41 | 42 | REQUIRE(counter == 0); 43 | } 44 | -------------------------------------------------------------------------------- /types/test/test_result.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ihft::types; 6 | 7 | TEST_CASE("succeeded") 8 | { 9 | constexpr int VALUE = 1024; 10 | 11 | result res(VALUE); 12 | 13 | REQUIRE(res); 14 | 15 | REQUIRE(res.succeeded()); 16 | 17 | REQUIRE(res.value() == VALUE); 18 | } 19 | 20 | TEST_CASE("failed") 21 | { 22 | const std::string ERROR = "Invalid output"; 23 | 24 | result res(ERROR); 25 | 26 | REQUIRE(!res); 27 | 28 | REQUIRE(res.failed()); 29 | 30 | REQUIRE(res.error() == ERROR); 31 | } 32 | -------------------------------------------------------------------------------- /types/test/test_scope_exit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | using namespace ihft::types; 6 | 7 | TEST_CASE("scope_exit") 8 | { 9 | int check = 0; 10 | 11 | { 12 | auto exit = make_scope_exit([&]() { 13 | check = 2024; 14 | }); 15 | } 16 | 17 | REQUIRE(2024 == check); 18 | } 19 | 20 | TEST_CASE("scope_exit_move") 21 | { 22 | int check = 0; 23 | 24 | { 25 | auto exit = make_scope_exit([&]() { 26 | check = 2024; 27 | }); 28 | 29 | auto next = std::move(exit); 30 | 31 | REQUIRE(0 == check); 32 | 33 | exit.release(); 34 | 35 | REQUIRE(0 == check); 36 | } 37 | 38 | REQUIRE(2024 == check); 39 | } 40 | -------------------------------------------------------------------------------- /types/test/test_sso.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace 7 | { 8 | static int g_call_allocate = 0; 9 | static int g_call_deallocate = 0; 10 | 11 | template 12 | class test_allocator 13 | { 14 | public: 15 | using value_type = T; 16 | 17 | [[nodiscard]] T* allocate(std::size_t size) noexcept 18 | { 19 | g_call_allocate++; 20 | auto bytes = size * sizeof(T); 21 | auto ptr = reinterpret_cast(std::malloc(bytes)); 22 | std::cout << "allocate: " << ptr << " size: " <, test_allocator>; 38 | 39 | TEST_CASE("SSO") 40 | { 41 | REQUIRE(g_call_allocate == 0); 42 | REQUIRE(g_call_deallocate == 0); 43 | 44 | { 45 | std::cout << "sizeof: " << sizeof(istring) << std::endl; 46 | 47 | #if defined (__clang__) 48 | constexpr int SSO_BUFFER_SIZE = 22; 49 | #elif defined (__GNUC__) 50 | constexpr int SSO_BUFFER_SIZE = 15; 51 | #else 52 | # error "Unsupported compiler !!!" 53 | #endif 54 | 55 | istring text; 56 | for(int i = 0; i < SSO_BUFFER_SIZE; i++) 57 | { 58 | text.push_back(static_cast('a' + i)); 59 | } 60 | } 61 | 62 | REQUIRE(g_call_allocate == 0); 63 | REQUIRE(g_call_deallocate == 0); 64 | } 65 | --------------------------------------------------------------------------------