Skip to content

Improve performance of functions with dynamic arguments #345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
May 1, 2025
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
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
## 2.2.0-wip

- Performance of functions that take `dynamic` arguments improved.
- Most of the members that take `dynamic` arguments are deprecated. New members
with non-`dynamic` arguments added.

Deprecated members:
- `Matrix4.translate`

Instead use: `Matrix4.translateByDouble`, `Matrix4.translateByVector3`,
`Matrix4.translateByVector4`.

- `Matrix4.leftTranslate`

Instead use: `Matrix4.leftTranslateByDouble`,
`Matrix4.leftTranslateByVector3`, `Matrix4.leftTranslateByVector4`.

- `Matrix4.scale`

Instead use: `Matrix4.scaleByDouble`, `Matrix4.scaleByVector3`,
`Matrix4.scaleByVector4`.

- `Matrix4.scaled`

Instead use: `Matrix4.scaledByDouble`, `Matrix4.scaledByVector3`,
`Matrix4.scaledByVector4`.

## 2.1.5

- Fixed `operator -()` of Quaternion (Contributed by tlserver)
Expand Down
150 changes: 150 additions & 0 deletions benchmark/matrix_bench.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// ignore_for_file: deprecated_member_use_from_same_package

import 'dart:math' as math;
import 'dart:typed_data';

Expand Down Expand Up @@ -321,6 +323,148 @@ class Matrix3TransposeMultiplyBenchmark extends BenchmarkBase {
}
}

class Matrix4TranslateByDoubleGenericBenchmark extends BenchmarkBase {
Matrix4TranslateByDoubleGenericBenchmark()
: super('Matrix4.translateByDoubleGeneric');

final temp = Matrix4.zero()..setIdentity();

static void main() {
Matrix4TranslateByDoubleGenericBenchmark().report();
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translate(10.0, 20.0, 30.0);
}
}
}

class Matrix4TranslateByVector3GenericBenchmark extends BenchmarkBase {
Matrix4TranslateByVector3GenericBenchmark()
: super('Matrix4.translateByVector3Generic');

final temp = Matrix4.zero()..setIdentity();
final vec = Vector3(10.0, 20.0, 30.0);

static void main() {
Matrix4TranslateByVector3GenericBenchmark().report();
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translate(vec);
}
}
}

class Matrix4TranslateByVector4GenericBenchmark extends BenchmarkBase {
Matrix4TranslateByVector4GenericBenchmark()
: super('Matrix4.translateByVector4Generic');

final temp = Matrix4.zero()..setIdentity();
final vec = Vector4(10.0, 20.0, 30.0, 40.0);

static void main() {
Matrix4TranslateByVector4GenericBenchmark().report();
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translate(vec);
}
}
}

class Matrix4TranslateByDoubleBenchmark extends BenchmarkBase {
Matrix4TranslateByDoubleBenchmark() : super('Matrix4.translateByDouble');

final temp = Matrix4.zero()..setIdentity();

static void main() {
Matrix4TranslateByDoubleBenchmark().report();
}

// Call the benchmarked method with random arguments to make sure TFA won't
// specialize it based on the arguments passed and wasm-opt won't inline it,
// for fair comparison with the generic case.
@override
void setup() {
for (var i = 0; i < 10; i++) {
temp.translateByDouble(
i.toDouble(), (i * 10).toDouble(), (i * 5).toDouble(), 1.0);
}
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translateByDouble(10.0, 20.0, 30.0, 1.0);
}
}
}

class Matrix4TranslateByVector3Benchmark extends BenchmarkBase {
Matrix4TranslateByVector3Benchmark() : super('Matrix4.translateByVector3');

final temp = Matrix4.zero()..setIdentity();
final vec = Vector3(10.0, 20.0, 30.0);

static void main() {
Matrix4TranslateByVector3Benchmark().report();
}

// Call the benchmarked method with random arguments to make sure TFA won't
// specialize it based on the arguments passed and wasm-opt won't inline it,
// for fair comparison with the generic case.
@override
void setup() {
for (var i = 0; i < 10; i++) {
temp.translateByVector3(
Vector3(i.toDouble(), (i * 10).toDouble(), (i * 5).toDouble()));
}
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translateByVector3(vec);
}
}
}

class Matrix4TranslateByVector4Benchmark extends BenchmarkBase {
Matrix4TranslateByVector4Benchmark() : super('Matrix4.translateByVector4');

final temp = Matrix4.zero()..setIdentity();
final vec = Vector4(10.0, 20.0, 30.0, 40.0);

static void main() {
Matrix4TranslateByVector4Benchmark().report();
}

// Call the benchmarked method with random arguments to make sure TFA won't
// specialize it based on the arguments passed and wasm-opt won't inline it,
// for fair comparison with the generic case.
@override
void setup() {
for (var i = 0; i < 10; i++) {
temp.translateByVector4(Vector4(i.toDouble(), (i * 10).toDouble(),
(i * 5).toDouble(), (i * 20).toDouble()));
}
}

@override
void run() {
for (var i = 0; i < 100; i++) {
temp.translateByVector4(vec);
}
}
}

void main() {
MatrixMultiplyBenchmark.main();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This structure of running each benchmark in turn is susceptible to JIT-effects where the code is initially specialized to the first benchmark, and then de-optimized and recompiled against less constrained inputs.
When this happens, the reported performance is dependent on the order of the benchmarks,
To counter this, I usually create a list of benchmarks, warm them all up, then do the actual measurements.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do it separately. It's going to be a big change to update all of the benchmarks, and it can be done separately.

SIMDMatrixMultiplyBenchmark.main();
Expand All @@ -335,4 +479,10 @@ void main() {
Matrix3TransformVector3Benchmark.main();
Matrix3TransformVector2Benchmark.main();
Matrix3TransposeMultiplyBenchmark.main();
Matrix4TranslateByDoubleGenericBenchmark.main();
Matrix4TranslateByVector3GenericBenchmark.main();
Matrix4TranslateByVector4GenericBenchmark.main();
Matrix4TranslateByDoubleBenchmark.main();
Matrix4TranslateByVector3Benchmark.main();
Matrix4TranslateByVector4Benchmark.main();
}
20 changes: 12 additions & 8 deletions lib/src/vector_math/matrix2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,21 @@ class Matrix2 {
}

/// Returns a new vector or matrix by multiplying this with [arg].
@pragma('wasm:prefer-inline')
@pragma('vm:prefer-inline')
@pragma('dart2js:prefer-inline')
dynamic operator *(dynamic arg) {
final Object result;
if (arg is double) {
return scaled(arg);
}
if (arg is Vector2) {
return transformed(arg);
}
if (arg is Matrix2) {
return multiplied(arg);
result = scaled(arg);
} else if (arg is Vector2) {
result = transformed(arg);
} else if (arg is Matrix2) {
result = multiplied(arg);
} else {
throw ArgumentError(arg);
}
throw ArgumentError(arg);
return result;
}

/// Returns new matrix after component wise this + [arg]
Expand Down
20 changes: 12 additions & 8 deletions lib/src/vector_math/matrix3.dart
Original file line number Diff line number Diff line change
Expand Up @@ -326,17 +326,21 @@ class Matrix3 {
}

/// Returns a new vector or matrix by multiplying this with [arg].
@pragma('wasm:prefer-inline')
@pragma('vm:prefer-inline')
@pragma('dart2js:prefer-inline')
dynamic operator *(dynamic arg) {
final Object result;
if (arg is double) {
return scaled(arg);
}
if (arg is Vector3) {
return transformed(arg);
}
if (arg is Matrix3) {
return multiplied(arg);
result = scaled(arg);
} else if (arg is Vector3) {
result = transformed(arg);
} else if (arg is Matrix3) {
result = multiplied(arg);
} else {
throw ArgumentError(arg);
}
throw ArgumentError(arg);
return result;
}

/// Returns new matrix after component wise this + [arg]
Expand Down
Loading