Repository navigation
AOT: int.toDouble() in a hot loop is 5x slower than the same conversion written as 0.0 + x #64209
Description
acc += x.toDouble() and acc += x compute the same thing. In JIT they take the same time. In AOT the first is 5x slower.
The practical damage is to SIMD over integer data: Float32x4 takes doubles, so building one from a Uint8List has to convert, and that makes the vector code slower than the scalar loop it was meant to replace.
import 'dart:typed_data';
const n = 1024;
const iters = 200000;
double explicitConversion(Uint8List a) {
var acc = 0.0;
for (var i = 0; i < a.length; i++) {
acc += a[i].toDouble();
}
return acc;
}
double implicitPromotion(Uint8List a) {
var acc = 0.0;
for (var i = 0; i < a.length; i++) {
acc += a[i];
}
return acc;
}
double simdExplicit(Uint8List a) {
var acc = Float32x4.zero();
for (var i = 0; i < a.length; i += 4) {
acc += Float32x4(
a[i].toDouble(),
a[i + 1].toDouble(),
a[i + 2].toDouble(),
a[i + 3].toDouble(),
);
}
return acc.x + acc.y + acc.z + acc.w;
}
double simdPromoted(Uint8List a) {
var acc = Float32x4.zero();
for (var i = 0; i < a.length; i += 4) {
acc += Float32x4(
0.0 + a[i],
0.0 + a[i + 1],
0.0 + a[i + 2],
0.0 + a[i + 3],
);
}
return acc.x + acc.y + acc.z + acc.w;
}
void main() {
final a = Uint8List(n);
for (var i = 0; i < n; i++) {
a[i] = (i * 37) & 0xff;
}
// All four compute the same sum.
print(
'results: ${explicitConversion(a)} ${implicitPromotion(a)} '
'${simdExplicit(a)} ${simdPromoted(a)}',
);
var sink = 0.0;
void bench(String label, double Function() f) {
var best = 1e9;
for (var r = 0; r < 6; r++) {
final sw = Stopwatch()..start();
for (var i = 0; i < iters; i++) {
sink += f();
}
sw.stop();
final dt = sw.elapsedMicroseconds / 1e6;
if (dt < best) best = dt;
}
print(
'${label.padRight(30)}${(best * 1000).toStringAsFixed(1).padLeft(7)} ms',
);
}
bench('acc += x.toDouble()', () => explicitConversion(a));
bench('acc += x', () => implicitPromotion(a));
bench('Float32x4(x.toDouble(), ...)', () => simdExplicit(a));
bench('Float32x4(0.0 + x, ...)', () => simdPromoted(a));
if (sink == -1) print('never');
}All four print 130560.0, so they do the same arithmetic.
dart compile exe repro.dart, best of 6, macos_arm64:
| 3.13.3 AOT | dev AOT | 3.13.3 JIT | dev JIT | |
|---|---|---|---|---|
acc += x.toDouble() |
664.1 ms | 676.1 ms | 127.1 ms | 126.5 ms |
acc += x |
127.9 ms | 130.6 ms | 127.0 ms | 128.8 ms |
Float32x4(x.toDouble(), ...) |
413.3 ms | 405.4 ms | 97.8 ms | 98.7 ms |
Float32x4(0.0 + x, ...) |
78.7 ms | 78.5 ms | 74.1 ms | 74.7 ms |
Not specific to Float32x4 or to float32: Float32List[i] = x.toDouble() costs ~480 ms against ~155 ms for storing a constant, and Float64List is the same.
Vectors themselves are in good shape - arithmetic over a populated Float32x4List beats the scalar loop by 2.5x in AOT. The whole loss is on the way in. Concretely, a Float32x4 sum of squared differences over a 16x16 byte block is 3x slower than scalar in AOT with toDouble(), and 14% faster with 0.0 + x.
Workaround: write 0.0 + x instead of x.toDouble().
#46182 asks for a Float32x4 constructor taking ints for exactly this reason ("avoiding 4 calls to i.toDouble()"). That would cover the SIMD case, but the slowdown is not limited to it - any toDouble() in a hot AOT loop pays it.
Tested on 3.13.2 stable, 3.13.3 stable and 3.14.0-180.0.dev, macos_arm64. Not tested on x64.
Activity
krll-kov commentedon Sep 4, 2026
For triage: same symptom as #63217, which was closed when cl/497000 added AOT specialization for the Int32x4 operators. This is a different operation and shows up outside SIMD as well, so it is not that one coming back.
Possibly the same shape, though: _IntegerImplementation.toDouble is in OTHER_RECOGNIZED_LIST but not in GRAPH_INTRINSICS_LIST or ASM_INTRINSICS_LIST, so it stays a call in AOT where there is no IC feedback to specialize it. That is what cl/497000 describes for the Int32x4 operators "never added to the recognized-method list as graph intrinsics … AOT has no IC feedback and therefore fell back to boxed calls" and those five are in GRAPH_INTRINSICS_LIST now.
modulovalue commentedon Sep 4, 2026
https://dart-review.googlesource.com/c/sdk/+/546100