Skip to content

Commit 3a8035a

Browse files
build: tune Python install for the host CPU by default
Compile the native library with -march=native (or -mcpu=native on Apple-silicon clang) and -ffast-math when the compiler accepts them, probing each flag first so the build degrades gracefully. Opt out with BFFT_NO_NATIVE=1 or BFFT_NO_FAST_MATH=1. Since no prebuilt binaries are distributed and the library is always compiled on the install machine, host-native codegen is the right default and lets the SIMD kernels actually use AVX2/AVX-512. Also document the flags and caveats in README.md and documentation/python.md, and switch pyproject license to an SPDX string. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9d48c7d commit 3a8035a

4 files changed

Lines changed: 88 additions & 10 deletions

File tree

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,16 @@ From a clone of the repository:
211211
pip install .
212212
```
213213

214-
This compiles `src/bfft.cpp` and `src/bodft.cpp` with your C++ compiler (override
215-
with the `CXX` environment variable) and bundles the resulting shared library
216-
inside the installed package. A C++17 compiler and NumPy are the only
217-
requirements.
214+
This compiles `src/bfft.cpp` and `src/bodft.cpp` with your C++ compiler and
215+
bundles the resulting shared library inside the installed package. A C++17
216+
compiler and NumPy are the only requirements.
217+
218+
Because the build runs on your own machine, it tunes for the local CPU by
219+
default, selecting `-O3`, `-march=native` (or `-mcpu=native` on Apple silicon),
220+
and `-ffast-math` when the compiler accepts them. Override with environment
221+
variables: `CXX` (compiler), `BFFT_CXXFLAGS` (extra flags), `BFFT_NO_NATIVE=1`
222+
(portable codegen), and `BFFT_NO_FAST_MATH=1` (strict IEEE math). See
223+
[`documentation/python.md`](documentation/python.md) for details and caveats.
218224

219225
Alternatively, build and install the native library system-wide first, then the
220226
Python loader will find it automatically:

documentation/python.md

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,39 @@ This compiles `src/bfft.cpp` and `src/bodft.cpp` with your C++ compiler and
1616
bundles the resulting shared library inside the installed package. The only
1717
requirements are a C++17 compiler and NumPy.
1818

19-
Override the compiler with the `CXX` environment variable, and pass extra flags
20-
with `BFFT_CXXFLAGS` if needed:
19+
### Optimization flags
20+
21+
Because the library is compiled on your own machine (no prebuilt binaries are
22+
distributed), the build tunes for the local CPU by default. It selects, when the
23+
compiler accepts them:
24+
25+
- `-O3`
26+
- `-march=native` (or `-mcpu=native` on Apple-silicon clang) — emit AVX2/AVX-512
27+
and other host-specific instructions.
28+
- `-ffast-math` — relaxed floating-point for faster math.
29+
30+
Each flag is probed against your compiler first, so the build degrades
31+
gracefully on toolchains that lack them. Control the defaults with environment
32+
variables:
33+
34+
| Variable | Effect |
35+
| --- | --- |
36+
| `BFFT_NO_NATIVE=1` | Skip `-march=native` / `-mcpu=native` (portable codegen). |
37+
| `BFFT_NO_FAST_MATH=1` | Keep strict IEEE math (drop `-ffast-math`). |
38+
| `CXX=...` | Choose the compiler. |
39+
| `BFFT_CXXFLAGS="..."` | Append extra flags to the compile. |
2140

2241
```sh
23-
CXX=clang++ BFFT_CXXFLAGS="-march=native" pip install .
42+
CXX=clang++ BFFT_NO_FAST_MATH=1 pip install .
2443
```
2544

45+
`-ffast-math` assumes no NaNs/infinities and reorders operations, so results may
46+
differ in the last bits from a strict-IEEE build (still accurate to
47+
floating-point precision for these transforms). It also enables
48+
flush-to-zero/denormals-are-zero for the process when the library loads, which
49+
can affect denormal handling elsewhere. Set `BFFT_NO_FAST_MATH=1` if you need
50+
bit-reproducible or strict denormal behavior.
51+
2652
### Using a system-installed library
2753

2854
If you prefer to build the native library separately, install it first and the

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ name = "bfft"
77
version = "0.1.0"
88
description = "Python bindings for the BFFT real Fourier transform library (drop-in numpy.fft.rfft + half-bin ODFT)."
99
readme = "README.md"
10-
license = { file = "LICENSE" }
10+
license = "MIT"
11+
license-files = ["LICENSE"]
1112
requires-python = ">=3.8"
1213
dependencies = ["numpy>=1.20"]
1314

setup.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import os
1010
import subprocess
1111
import sys
12-
import sysconfig
12+
import tempfile
1313
from pathlib import Path
1414

1515
from setuptools import setup
@@ -28,10 +28,55 @@ def _shared_lib_suffix() -> str:
2828
return ".so"
2929

3030

31+
def _env_off(name: str) -> bool:
32+
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
33+
34+
35+
def _compiler_accepts(cxx: str, flags: list) -> bool:
36+
"""Return True if the compiler accepts ``flags`` on a trivial source file."""
37+
with tempfile.TemporaryDirectory() as d:
38+
src = Path(d) / "probe.cpp"
39+
src.write_text("int main() { return 0; }\n")
40+
try:
41+
subprocess.run(
42+
[cxx, *flags, "-c", str(src), "-o", str(Path(d) / "probe.o")],
43+
stdout=subprocess.DEVNULL,
44+
stderr=subprocess.DEVNULL,
45+
check=True,
46+
)
47+
return True
48+
except (subprocess.CalledProcessError, OSError):
49+
return False
50+
51+
52+
def _optimization_flags(cxx: str) -> list:
53+
"""Pick the strongest optimization flags the host compiler accepts.
54+
55+
Because the library is compiled on the install machine (no prebuilt binaries
56+
are distributed), tuning for the local CPU is safe and is the default. Set
57+
BFFT_NO_NATIVE=1 to skip CPU-native codegen and BFFT_NO_FAST_MATH=1 to keep
58+
strict IEEE math.
59+
"""
60+
flags = ["-O3"]
61+
62+
if not _env_off("BFFT_NO_NATIVE"):
63+
# x86 / older clang / gcc use -march=native; Apple-silicon clang wants
64+
# -mcpu=native instead. Probe and take whichever the compiler accepts.
65+
if _compiler_accepts(cxx, ["-march=native"]):
66+
flags.append("-march=native")
67+
elif _compiler_accepts(cxx, ["-mcpu=native"]):
68+
flags.append("-mcpu=native")
69+
70+
if not _env_off("BFFT_NO_FAST_MATH") and _compiler_accepts(cxx, ["-ffast-math"]):
71+
flags.append("-ffast-math")
72+
73+
return flags
74+
75+
3176
def _compile_shared_library(out_path: Path) -> None:
3277
cxx = os.environ.get("CXX", "c++")
3378
out_path.parent.mkdir(parents=True, exist_ok=True)
34-
cmd = [cxx, "-O3", "-std=c++17", "-fPIC", "-shared"]
79+
cmd = [cxx, *_optimization_flags(cxx), "-std=c++17", "-fPIC", "-shared"]
3580
cmd += ["-I", str(ROOT / INCLUDE)]
3681
cmd += [str(ROOT / s) for s in SOURCES]
3782
cmd += ["-o", str(out_path)]

0 commit comments

Comments
 (0)