Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: CI

on:
push:
branches: [main]
pull_request:

env:
CTEST_OUTPUT_ON_FAILURE: "1"

jobs:
sanitizers:
name: ${{ matrix.compiler }}-${{ matrix.sanitizer }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
compiler: [gcc, clang]
sanitizer: [address, undefined, thread]
include:
- compiler: gcc
c: gcc
cxx: g++
- compiler: clang
c: clang
cxx: clang++
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure CMake
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=${{ matrix.c }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCMAKE_C_FLAGS="-O1 -g -fno-omit-frame-pointer -fno-common -fsanitize=${{ matrix.sanitizer }}" \
-DCMAKE_CXX_FLAGS="-O1 -g -fno-omit-frame-pointer -fno-common -fsanitize=${{ matrix.sanitizer }}" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=${{ matrix.sanitizer }}"

- name: Build
run: cmake --build build --parallel

- name: Test
run: ctest --test-dir build --output-on-failure
env:
ASAN_OPTIONS: detect_leaks=0
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
TSAN_OPTIONS: halt_on_error=1

coverage:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install gcovr
run: python3 -m pip install --user gcovr

- name: Configure CMake (coverage)
env:
CC: gcc
CXX: g++
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_COMPILER=gcc \
-DCMAKE_CXX_COMPILER=g++ \
-DCMAKE_C_FLAGS="--coverage -O0" \
-DCMAKE_CXX_FLAGS="--coverage -O0" \
-DCMAKE_EXE_LINKER_FLAGS="--coverage"

- name: Build
run: cmake --build build --parallel

- name: Run tests with coverage
run: ctest --test-dir build --output-on-failure

- name: Generate coverage report
run: |
~/.local/bin/gcovr --root . --object-directory build --txt --output coverage-summary.txt
~/.local/bin/gcovr --root . --object-directory build --xml-pretty --output coverage.xml

- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage-summary.txt
coverage.xml

perf:
uses: ./.github/workflows/perf-smoke.yml
with:
threshold_ns: 300
34 changes: 34 additions & 0 deletions .github/workflows/perf-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: perf-smoke

on:
workflow_call:
inputs:
threshold_ns:
description: Maximum acceptable nanoseconds per iteration
required: false
type: number
default: 300
iterations:
description: Number of iterations to run in the benchmark
required: false
type: number
default: 5000000

jobs:
perf-smoke:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure CMake
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release

- name: Build perf benchmark
run: cmake --build build --target perf_smoke --parallel

- name: Run perf benchmark
run: ./build/tests/perf_smoke
env:
TSD_PERF_THRESHOLD_NS: ${{ inputs.threshold_ns }}
TSD_PERF_ITERATIONS: ${{ inputs.iterations }}
10 changes: 9 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.15)
project(thermal_simd_dispatcher VERSION 0.1.0 LANGUAGES C)
project(thermal_simd_dispatcher VERSION 0.1.0 LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(THERMAL_SIMD_DISPATCHER_CPU_FLAGS "-msse4.1" CACHE STRING "CPU-specific compiler flags for thermal_simd")

Expand Down Expand Up @@ -66,6 +68,12 @@ if(BUILD_TESTING)
target_compile_options(test_thermal_simd PRIVATE -Wall -Wextra -O1 -pthread -fPIC)
target_include_directories(test_thermal_simd PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_test(NAME thermal_simd COMMAND test_thermal_simd)

add_executable(perf_smoke tests/perf_smoke.cpp)
target_link_libraries(perf_smoke PRIVATE thermal_simd_core_tests)
target_compile_features(perf_smoke PRIVATE cxx_std_17)
target_compile_options(perf_smoke PRIVATE -Wall -Wextra -O3)
set_target_properties(perf_smoke PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests)
endif()

install(TARGETS thermal_simd_core
Expand Down
85 changes: 85 additions & 0 deletions tests/perf_smoke.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <random>

extern "C" {
#include <thermal/simd/statistics.h>
}

namespace {

double parse_env_double(const char *name, double fallback) {
const char *value = std::getenv(name);
if (!value || value[0] == '\0') {
return fallback;
}
try {
return std::stod(value);
} catch (const std::exception &) {
std::cerr << "[perf_smoke] Ignoring invalid value for " << name << ": " << value
<< std::endl;
return fallback;
}
}

std::uint64_t parse_env_uint64(const char *name, std::uint64_t fallback) {
const char *value = std::getenv(name);
if (!value || value[0] == '\0') {
return fallback;
}
try {
return static_cast<std::uint64_t>(std::stoull(value));
} catch (const std::exception &) {
std::cerr << "[perf_smoke] Ignoring invalid value for " << name << ": " << value
<< std::endl;
return fallback;
}
}

std::uint64_t run_benchmark(std::uint64_t iterations) {
std::mt19937 rng(1337u);
std::uniform_int_distribution<std::uint32_t> dist(800, 1200);

std::uint64_t ewma = 0;
const unsigned ewma_shift = 3;

auto start = std::chrono::steady_clock::now();
for (std::uint64_t i = 0; i < iterations; ++i) {
ewma = tsd_update_ewma(ewma, dist(rng), ewma_shift);
}
auto stop = std::chrono::steady_clock::now();

return static_cast<std::uint64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start).count());
}

} // namespace

int main() {
const std::uint64_t iterations = parse_env_uint64("TSD_PERF_ITERATIONS", 5'000'000ULL);
const double threshold_ns = parse_env_double("TSD_PERF_THRESHOLD_NS", 300.0);

if (iterations == 0) {
std::cerr << "[perf_smoke] iteration count must be non-zero" << std::endl;
return EXIT_FAILURE;
}

const std::uint64_t total_ns = run_benchmark(iterations);
const double per_iteration = static_cast<double>(total_ns) / static_cast<double>(iterations);

std::cout << std::fixed << std::setprecision(2);
std::cout << "[perf_smoke] iterations=" << iterations << ", total_ns=" << total_ns
<< ", per_iteration_ns=" << per_iteration
<< ", threshold_ns=" << threshold_ns << std::endl;

if (per_iteration > threshold_ns) {
std::cerr << "[perf_smoke] performance regression detected: " << per_iteration
<< " ns/iter exceeds threshold of " << threshold_ns << " ns/iter" << std::endl;
return EXIT_FAILURE;
}

return EXIT_SUCCESS;
}
4 changes: 2 additions & 2 deletions tests/test_thermal_simd.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ static int run_monitor_thread_scenario(void) {
return 1;
}
int rc = 0;
if (wait_for_width(SIMD_SSE41, 500) != 0) {
if (wait_for_width(SIMD_SSE41, 5000) != 0) {
fprintf(stderr, "width did not downgrade\n");
rc = 1;
}
if (rc == 0 && wait_for_width(SIMD_AVX2, 500) != 0) {
if (rc == 0 && wait_for_width(SIMD_AVX2, 5000) != 0) {
fprintf(stderr, "width did not upgrade\n");
rc = 1;
}
Expand Down
Loading