Skip to content
Merged
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
25 changes: 25 additions & 0 deletions .github/workflows/ci-benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Benchmarks

# Runs the runtime benchmarks in Benchmarks/ and fails if any benchmark allocates more
# than recorded in Benchmarks/Thresholds/ (currently zero for every benchmark).
# Only allocation counts gate CI; timings on shared runners are too noisy to compare.

on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]
workflow_dispatch:

jobs:
allocations:
name: Allocation Thresholds
runs-on: ubuntu-latest
container: swift:6.4.1
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Check Allocation Thresholds
working-directory: Benchmarks
run: swift package benchmark thresholds check --metric mallocCountTotal --no-progress
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import Benchmark
import FirebladeMath

let benchmarks: @Sendable () -> Void = {
Benchmark.defaultConfiguration = .init(
metrics: [.wallClock, .throughput, .mallocCountTotal],
scalingFactor: .kilo,
maxDuration: .seconds(2),
thresholds: [.mallocCountTotal: .init(absolute: [.p90: 0])]
)

let axis = normalize(Vec3f(1, 2, 3))
let rotation = Quat4f(angle: 0.7, axis: axis)
let transform = Mat4x4f(rotation: 0.3, axis: Vec3f(0, 1, 0)) * Mat4x4f(translation: Vec3f(1, 2, 3))

// MARK: - Own implementation on every platform

//
// Some of these compose FirebladeMath's thin vector/libm wrappers (normalize, cross, sin, ...),
// but the algorithm itself is FirebladeMath's own.

Benchmark("Mat4x4f.adjugate") { benchmark in
for _ in benchmark.scaledIterations {
blackHole(adjugate(identity(transform)))
}
}

Benchmark("Mat4x4f.initRotation") { benchmark in
for index in benchmark.scaledIterations {
blackHole(Mat4x4f(rotation: Float(index) * 0.001, axis: identity(axis)))
}
}

Benchmark("Mat4x4f.lookAt") { benchmark in
for index in benchmark.scaledIterations {
let eye = Vec3f(Float(index) * 0.001, 2, 5)
blackHole(Mat4x4f.look(from: eye, at: identity(Vec3f.zero), up: Vec3f(0, 1, 0)))
}
}

Benchmark("Mat4x4f.perspectiveRH") { benchmark in
for index in benchmark.scaledIterations {
let aspect = 1.5 + Float(index % 100) * 0.001
blackHole(Mat4x4f.perspectiveRH(fovy: 1.2, aspect: aspect, zNear: 0.1, zFar: identity(100)))
}
}

Benchmark("Quat4f.fromEulerAngles123") { benchmark in
for index in benchmark.scaledIterations {
blackHole(Quat4f.fromEulerAngles_123(Vec3f(Float(index) * 0.001, 0.2, 0.3)))
}
}

Benchmark("Quat4f.toEulerAngles123") { benchmark in
for _ in benchmark.scaledIterations {
blackHole(quaternionToEulerAngles_123(identity(rotation)))
}
}

Benchmark("Mat4x4f.flatSubscript") { benchmark in
for _ in benchmark.scaledIterations {
let source = identity(transform)
var sum: Float = 0
for index in 0..<16 {
sum += source[index]
}
blackHole(sum)
}
}

Benchmark("Quat4f.iteration") { benchmark in
for _ in benchmark.scaledIterations {
var sum: Float = 0
for component in identity(rotation) {
sum += component
}
blackHole(sum)
}
}

// MARK: - Own implementation on the portable (non-simd) path

//
// With the `simd` trait (default on Apple platforms) these forward to Apple's simd library.
// Their numbers describe FirebladeMath's code only on Linux/Windows (CI) or locally with
// FRB_MATH_BENCHMARK_NO_SIMD=1.

Benchmark("Mat4x4f.initColumns") { benchmark in
let column0 = Vec4f(1, 2, 3, 4)
let column1 = Vec4f(5, 6, 7, 8)
let column2 = Vec4f(9, 10, 11, 12)
let column3 = Vec4f(13, 14, 15, 16)
for index in benchmark.scaledIterations {
let scale = Float(index)
blackHole(Mat4x4f(column0 * scale, column1 * scale, column2 * scale, column3 * scale))
}
}

Benchmark("Mat4x4f.multiply") { benchmark in
var matrix = Mat4x4f(diagonal: Vec4f(repeating: 0.5))
let factor = Mat4x4f(diagonal: Vec4f(repeating: 1.0000001))
for _ in benchmark.scaledIterations {
matrix = identity(matrix * factor)
}
blackHole(matrix)
}

Benchmark("Mat4x4f.inverse") { benchmark in
for _ in benchmark.scaledIterations {
blackHole(inverse(identity(transform)))
}
}

Benchmark("Mat4x4f.fromQuaternion") { benchmark in
for _ in benchmark.scaledIterations {
blackHole(matrix4x4(from: identity(rotation)))
}
}

Benchmark("Quat4f.fromMatrix") { benchmark in
for _ in benchmark.scaledIterations {
blackHole(quaternion(matrix: identity(transform)))
}
}

Benchmark("Quat4f.multiply") { benchmark in
var quaternion = rotation
for _ in benchmark.scaledIterations {
quaternion = identity(quaternion * rotation)
}
blackHole(quaternion)
}

Benchmark("Quat4f.actNormalize") { benchmark in
var vector = Vec3f(1, 2, 3)
let step = Quat4f(angle: 0.001, axis: axis)
for _ in benchmark.scaledIterations {
vector = identity(normalize(act(step, vector)))
}
blackHole(vector)
}
}
78 changes: 78 additions & 0 deletions Benchmarks/Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions Benchmarks/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// swift-tools-version: 6.3
// Runtime benchmarks for FirebladeMath, kept in a separate package so that the
// benchmark dependency never becomes part of FirebladeMath's dependency graph.
//
// Run from this directory:
// swift package benchmark
// Benchmark the portable (non-simd) code path on Apple platforms:
// FRB_MATH_BENCHMARK_NO_SIMD=1 swift package benchmark

import class Foundation.ProcessInfo
import PackageDescription

let disableSIMD = ProcessInfo.processInfo.environment["FRB_MATH_BENCHMARK_NO_SIMD"] != nil

let package = Package(
name: "FirebladeMathBenchmarks",
platforms: [.macOS(.v13)],
dependencies: [
// The package identity of a path dependency is its directory name, i.e. the repository name.
.package(path: "..", traits: disableSIMD ? [] : [.defaults]),
.package(url: "https://github.com/ordo-one/benchmark", .upToNextMajor(from: "1.38.0"))
],
targets: [
.executableTarget(
name: "FirebladeMathBenchmarks",
dependencies: [
.product(name: "FirebladeMath", package: "math"),
.product(name: "Benchmark", package: "benchmark")
],
path: "Benchmarks/FirebladeMathBenchmarks",
plugins: [
.plugin(name: "BenchmarkPlugin", package: "benchmark")
]
)
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"mallocCountTotal" : 0
}
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ DOCS_VERSION_PATH ?= main
HOSTING_BASE_PATH ?= $(REPO_NAME)/$(DOCS_VERSION_PATH)

# Targets
.PHONY: setup lint lint-fix check-inlinable test test-coverage clean pre-commit docs docs-preview docs-generate docs-coverage profile-stats benchmark-commits
.PHONY: setup lint lint-fix check-inlinable test test-coverage clean pre-commit docs docs-preview docs-generate docs-coverage profile-stats benchmark-commits benchmark benchmark-thresholds-check benchmark-thresholds-update

setup:
@echo "Detected Package Swift Version: $(PACKAGE_SWIFT_VERSION)"
Expand Down Expand Up @@ -87,3 +87,12 @@ benchmark-commits:
chmod +x Scripts/profile-compiler-stats.sh
chmod +x Scripts/benchmark-commits.py
python3 Scripts/benchmark-commits.py

benchmark:
cd Benchmarks && swift package benchmark

benchmark-thresholds-check:
cd Benchmarks && swift package benchmark thresholds check --metric mallocCountTotal

benchmark-thresholds-update:
cd Benchmarks && swift package --allow-writing-to-package-directory benchmark thresholds update --metric mallocCountTotal
Loading