b74add4813
The five binary operators on Int32x4 (+, -, |, &, ^) were never added
to the recognized-method list as graph intrinsics, so calls to them
were left as runtime calls through external-name bodies. In JIT the
call specializer picked kInt32x4Cid from IC feedback and emitted a
native SimdOpInstr, but AOT has no IC feedback and therefore fell back
to boxed calls, making Int32x4List inner loops 10-70x slower than both
the JIT version and a hand-written scalar equivalent.
This CL wires the same specialization paths that already exist for
Float32x4.+,-,*,/:
- recognize the five operators as graph intrinsics and mark them
with `@pragma("vm:recognized", "graph-intrinsic")` plus an
exact-result-type pragma;
- add Build_Int32x4{Add,Sub,BitAnd,BitOr,BitXor} helpers that
delegate to the existing BuildSimdOp;
- extend SimdOpInstr::KindForOperator and CreateFromCall;
- extend CallSpecializer::InlineSimdOp and TryInlineRecognizedMethod
so the non-speculative null-check path used for Float32x4 operators
in AOT also applies here.
Measured on macOS arm64 (M-series), `dart compile exe`:
Issue 63217 orSimd : 12.58 -> 0.32 us/iter (39x)
Issue 63217 andNotSimd : 23.51 -> 0.34 us/iter (69x)
Issue 53662 mandelbrot : 4038.5 -> 55.5 ms (72x)
A new benchmark benchmarks/SimdInt32x4 exercises all five operators
with a scalar and a SIMD variant so the specialization stays covered
by the benchmark bots; it is registered in Omnibus and OmnibusDeferred.
Existing tests/lib/typed_data/simd_*_test.dart still pass in JIT and
AOT.
TEST=tests/lib/typed_data/int32x4_arithmetic_test; benchmarks/SimdInt32x4
Bug: https://github.com/dart-lang/sdk/issues/53662
Bug: https://github.com/dart-lang/sdk/issues/63217
Change-Id: I9b76ab4fff228ff1a5e3d3c86f4bfc059e66a49a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/497000
Reviewed-by: Slava Egorov <vegorov@google.com>
Commit-Queue: Slava Egorov <vegorov@google.com>
Auto-Submit: Modestas Valauskas <valauskasmodestas@gmail.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
178 lines
4.2 KiB
Dart
178 lines
4.2 KiB
Dart
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
// Regression benchmark for https://github.com/dart-lang/sdk/issues/63217 and
|
|
// https://github.com/dart-lang/sdk/issues/53662: the five binary operators on
|
|
// Int32x4 (+, -, |, &, ^) were not specialized in AOT mode, so `Int32x4List`
|
|
// loops fell back to boxed runtime calls that ran 10-70x slower than the
|
|
// hand-written scalar version or than the JIT.
|
|
//
|
|
// For every operator there is a scalar variant and a SIMD variant over the
|
|
// same Uint32List buffer, so the benchmark suite exposes both the absolute
|
|
// cost of each SIMD op and its speedup over the scalar baseline.
|
|
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:benchmark_harness/benchmark_harness.dart';
|
|
|
|
const int words = 2048;
|
|
|
|
abstract class SimdBench extends BenchmarkBase {
|
|
SimdBench(String name) : super('SimdInt32x4.$name');
|
|
|
|
late final Uint32List a;
|
|
late final Uint32List b;
|
|
|
|
@override
|
|
void setup() {
|
|
a = Uint32List(words);
|
|
b = Uint32List(words);
|
|
for (int i = 0; i < words; i++) {
|
|
a[i] = 0xA5A5A5A5 ^ i;
|
|
b[i] = 0x5A5A5A5A ^ (i * 31);
|
|
}
|
|
}
|
|
}
|
|
|
|
class OrScalar extends SimdBench {
|
|
OrScalar() : super('orScalar');
|
|
@override
|
|
void run() {
|
|
final n = a.length;
|
|
for (int i = 0; i < n; i++) {
|
|
a[i] |= b[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
class OrSimd extends SimdBench {
|
|
OrSimd() : super('orSimd');
|
|
@override
|
|
void run() {
|
|
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
|
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
|
for (int j = 0; j < la.length; j++) {
|
|
la[j] = la[j] | lb[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
class AndScalar extends SimdBench {
|
|
AndScalar() : super('andScalar');
|
|
@override
|
|
void run() {
|
|
final n = a.length;
|
|
for (int i = 0; i < n; i++) {
|
|
a[i] &= b[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
class AndSimd extends SimdBench {
|
|
AndSimd() : super('andSimd');
|
|
@override
|
|
void run() {
|
|
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
|
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
|
for (int j = 0; j < la.length; j++) {
|
|
la[j] = la[j] & lb[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
class XorScalar extends SimdBench {
|
|
XorScalar() : super('xorScalar');
|
|
@override
|
|
void run() {
|
|
final n = a.length;
|
|
for (int i = 0; i < n; i++) {
|
|
a[i] ^= b[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
class XorSimd extends SimdBench {
|
|
XorSimd() : super('xorSimd');
|
|
@override
|
|
void run() {
|
|
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
|
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
|
for (int j = 0; j < la.length; j++) {
|
|
la[j] = la[j] ^ lb[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
class AddScalar extends SimdBench {
|
|
AddScalar() : super('addScalar');
|
|
@override
|
|
void run() {
|
|
final n = a.length;
|
|
for (int i = 0; i < n; i++) {
|
|
a[i] = a[i] + b[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
class AddSimd extends SimdBench {
|
|
AddSimd() : super('addSimd');
|
|
@override
|
|
void run() {
|
|
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
|
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
|
for (int j = 0; j < la.length; j++) {
|
|
la[j] = la[j] + lb[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
class SubScalar extends SimdBench {
|
|
SubScalar() : super('subScalar');
|
|
@override
|
|
void run() {
|
|
final n = a.length;
|
|
for (int i = 0; i < n; i++) {
|
|
a[i] = a[i] - b[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
class SubSimd extends SimdBench {
|
|
SubSimd() : super('subSimd');
|
|
@override
|
|
void run() {
|
|
final la = Int32x4List.view(a.buffer, a.offsetInBytes, a.length >> 2);
|
|
final lb = Int32x4List.view(b.buffer, b.offsetInBytes, b.length >> 2);
|
|
for (int j = 0; j < la.length; j++) {
|
|
la[j] = la[j] - lb[j];
|
|
}
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
final benchmarks = <BenchmarkBase Function()>[
|
|
OrScalar.new,
|
|
OrSimd.new,
|
|
AndScalar.new,
|
|
AndSimd.new,
|
|
XorScalar.new,
|
|
XorSimd.new,
|
|
AddScalar.new,
|
|
AddSimd.new,
|
|
SubScalar.new,
|
|
SubSimd.new,
|
|
];
|
|
|
|
for (final bm in benchmarks) {
|
|
bm()
|
|
..setup()
|
|
..run()
|
|
..run();
|
|
}
|
|
|
|
for (final bm in benchmarks) {
|
|
bm().report();
|
|
}
|
|
}
|