Skip to content

Commit 443d345

Browse files
peng.li24claude
andcommitted
feat: add CMake config package and Google Benchmark suite
- find_package(numpycpp) support via installed cmake config - INTERFACE target for header-only consumers - bench/ with google-benchmark microbenchmarks (sqrt, exp, log, sin, cos, sum, mean, max, dot, norm) - Python numpy reference benchmark for comparison - Fix header install directory structure Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1ef5183 commit 443d345

6 files changed

Lines changed: 234 additions & 2 deletions

File tree

CMakeLists.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,41 @@ set(CMAKE_CXX_STANDARD 17)
66
set(CMAKE_CXX_STANDARD_REQUIRED ON)
77
set(CMAKE_CXX_EXTENSIONS OFF)
88

9+
# ---- INTERFACE target for find_package consumers ----------------------------
10+
add_library(numpycpp INTERFACE)
11+
target_include_directories(numpycpp INTERFACE
12+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
13+
$<INSTALL_INTERFACE:include/numpycpp>
14+
)
15+
target_compile_features(numpycpp INTERFACE cxx_std_17)
16+
917
# ---- Install — header-only C++ library --------------------------------------
10-
install(DIRECTORY numpy/
18+
install(DIRECTORY numpy
1119
DESTINATION include/numpycpp
1220
FILES_MATCHING PATTERN "*.h"
1321
)
1422

23+
install(TARGETS numpycpp
24+
EXPORT numpycpp-targets
25+
DESTINATION lib/cmake/numpycpp
26+
)
27+
28+
include(CMakePackageConfigHelpers)
29+
configure_package_config_file(
30+
numpycpp-config.cmake.in
31+
${CMAKE_CURRENT_BINARY_DIR}/numpycpp-config.cmake
32+
INSTALL_DESTINATION lib/cmake/numpycpp
33+
)
34+
35+
install(EXPORT numpycpp-targets
36+
NAMESPACE numpycpp::
37+
DESTINATION lib/cmake/numpycpp
38+
)
39+
40+
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/numpycpp-config.cmake
41+
DESTINATION lib/cmake/numpycpp
42+
)
43+
1544
# ---- CPack DEB packaging ----------------------------------------------------
1645
set(CPACK_PACKAGE_NAME "numpycpp-dev")
1746
set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,28 @@ double s = numpy::sum(data.data(), data.size());
3838
3939
### Install
4040
41+
**Ubuntu (DEB)**
42+
43+
Download the [latest `.deb` release](https://github.com/array2d/numpycpp/releases) or build from source:
44+
4145
```bash
4246
mkdir build && cd build
4347
cmake .. -DCMAKE_BUILD_TYPE=Release
4448
make deb
4549
sudo dpkg -i numpcpp-dev-*.deb
46-
# headers installed to /usr/include/numpycpp/
4750
```
4851

52+
Headers are installed to `/usr/include/numpycpp/` along with CMake config. Consuming projects use:
53+
54+
```cmake
55+
find_package(numpycpp REQUIRED)
56+
target_link_libraries(myapp PRIVATE numpycpp::numpycpp)
57+
```
58+
59+
**Manual (header-only)**
60+
61+
Add `-Ipath/to/numpycpp` to your compiler flags and include the headers directly. No build step, no copy required.
62+
4963
### Testing
5064

5165
The test suite verifies pixel-level precision alignment between every C++ function and Python numpy.

bench/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(numpycpp-bench LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_CXX_EXTENSIONS OFF)
7+
8+
# FetchContent google-benchmark
9+
include(FetchContent)
10+
FetchContent_Declare(googlebench
11+
GIT_REPOSITORY https://github.com/google/benchmark.git
12+
GIT_TAG v1.8.3
13+
)
14+
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
15+
FetchContent_MakeAvailable(googlebench)
16+
17+
add_executable(bench_core bench_core.cpp)
18+
target_include_directories(bench_core PRIVATE ..)
19+
target_link_libraries(bench_core PRIVATE benchmark::benchmark)

bench/bench_core.cpp

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#include "numpy/core.h"
2+
#include "numpy/linalg.h"
3+
#include <benchmark/benchmark.h>
4+
#include <vector>
5+
#include <random>
6+
#include <cmath>
7+
8+
// ---- helpers -----------------------------------------------------------------
9+
10+
std::vector<double> make_data(size_t n) {
11+
std::vector<double> v(n);
12+
std::mt19937 rng(42);
13+
std::uniform_real_distribution<double> dist(1.0, 100.0);
14+
for (size_t i = 0; i < n; ++i) v[i] = dist(rng);
15+
return v;
16+
}
17+
18+
// ---- element-wise -------------------------------------------------------------
19+
20+
#define BENCH_ELEMWISE(NAME) \
21+
static void BM_##NAME(benchmark::State& state) { \
22+
size_t n = state.range(0); \
23+
auto src = make_data(n); \
24+
std::vector<double> dst(n); \
25+
for (auto _ : state) { \
26+
numpy::NAME(src.data(), dst.data(), n); \
27+
benchmark::DoNotOptimize(dst.data()); \
28+
} \
29+
state.SetItemsProcessed(state.iterations() * n); \
30+
} \
31+
BENCHMARK(BM_##NAME)->Range(1 << 10, 1 << 22);
32+
33+
BENCH_ELEMWISE(sqrt)
34+
BENCH_ELEMWISE(abs)
35+
BENCH_ELEMWISE(exp)
36+
BENCH_ELEMWISE(log)
37+
BENCH_ELEMWISE(sin)
38+
BENCH_ELEMWISE(cos)
39+
40+
// ---- reduction ---------------------------------------------------------------
41+
42+
static void BM_sum(benchmark::State& state) {
43+
size_t n = state.range(0);
44+
auto src = make_data(n);
45+
for (auto _ : state) {
46+
double s = numpy::sum(src.data(), n);
47+
benchmark::DoNotOptimize(s);
48+
}
49+
state.SetItemsProcessed(state.iterations() * n);
50+
}
51+
BENCHMARK(BM_sum)->Range(1 << 10, 1 << 22);
52+
53+
static void BM_mean(benchmark::State& state) {
54+
size_t n = state.range(0);
55+
auto src = make_data(n);
56+
for (auto _ : state) {
57+
double m = numpy::mean(src.data(), n);
58+
benchmark::DoNotOptimize(m);
59+
}
60+
state.SetItemsProcessed(state.iterations() * n);
61+
}
62+
BENCHMARK(BM_mean)->Range(1 << 10, 1 << 22);
63+
64+
static void BM_max(benchmark::State& state) {
65+
size_t n = state.range(0);
66+
auto src = make_data(n);
67+
for (auto _ : state) {
68+
double m = numpy::max(src.data(), n);
69+
benchmark::DoNotOptimize(m);
70+
}
71+
state.SetItemsProcessed(state.iterations() * n);
72+
}
73+
BENCHMARK(BM_max)->Range(1 << 10, 1 << 22);
74+
75+
// ---- dot product (1D) ---------------------------------------------------------
76+
77+
static void BM_dot(benchmark::State& state) {
78+
size_t n = state.range(0);
79+
auto a = make_data(n);
80+
auto b = make_data(n);
81+
for (auto _ : state) {
82+
double d = numpy::dot(a.data(), b.data(), n);
83+
benchmark::DoNotOptimize(d);
84+
}
85+
state.SetItemsProcessed(state.iterations() * n);
86+
}
87+
BENCHMARK(BM_dot)->Range(1 << 10, 1 << 22);
88+
89+
// ---- linalg norm --------------------------------------------------------------
90+
91+
static void BM_norm(benchmark::State& state) {
92+
size_t n = state.range(0);
93+
auto src = make_data(n);
94+
for (auto _ : state) {
95+
double r = numpy::linalg::norm(src.data(), n);
96+
benchmark::DoNotOptimize(r);
97+
}
98+
state.SetItemsProcessed(state.iterations() * n);
99+
}
100+
BENCHMARK(BM_norm)->Range(1 << 10, 1 << 22);
101+
102+
// ---- main --------------------------------------------------------------------
103+
104+
BENCHMARK_MAIN();

bench/bench_numpy.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Reference numpy benchmarks for comparison with numpcpp bench_core.
2+
3+
Usage: python bench_numpy.py
4+
"""
5+
import time
6+
import numpy as np
7+
8+
SIZES = [1 << 10, 1 << 12, 1 << 14, 1 << 16, 1 << 18, 1 << 20, 1 << 22]
9+
REPEAT = 10
10+
11+
rng = np.random.default_rng(42)
12+
13+
14+
def make_data(n):
15+
return rng.uniform(1.0, 100.0, size=n)
16+
17+
18+
def bench(name, fn, n, src, *args):
19+
t0 = time.perf_counter()
20+
for _ in range(REPEAT):
21+
result = fn(src, *args)
22+
t1 = time.perf_counter()
23+
elapsed_ms = (t1 - t0) / REPEAT * 1000
24+
print(f" {name:12s} n={n:8d} {elapsed_ms:10.4f} ms ({n / elapsed_ms * 1000 / 1e6:.2f} Melem/s)")
25+
26+
27+
elementwise = [
28+
("sqrt", np.sqrt),
29+
("abs", np.abs),
30+
("exp", np.exp),
31+
("log", np.log),
32+
("sin", np.sin),
33+
("cos", np.cos),
34+
]
35+
36+
reductions = [
37+
("sum", np.sum),
38+
("mean", np.mean),
39+
("max", np.max),
40+
]
41+
42+
print("=== element-wise ===")
43+
for n in SIZES:
44+
src = make_data(n)
45+
for name, fn in elementwise:
46+
bench(name, fn, n, src)
47+
48+
print("\n=== reduction ===")
49+
for n in SIZES:
50+
src = make_data(n)
51+
for name, fn in reductions:
52+
bench(name, fn, n, src)
53+
54+
print("\n=== dot (1D) ===")
55+
for n in SIZES:
56+
a = make_data(n)
57+
b = make_data(n)
58+
bench("dot", np.dot, n, a, b)
59+
60+
print("\n=== norm (L2) ===")
61+
for n in SIZES:
62+
src = make_data(n)
63+
bench("norm", np.linalg.norm, n, src)

numpycpp-config.cmake.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@PACKAGE_INIT@
2+
include("${CMAKE_CURRENT_LIST_DIR}/numpycpp-targets.cmake")
3+
check_required_components(numpycpp)

0 commit comments

Comments
 (0)