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
Truncating division operator (a ~/ b)
is slower than (a / b).toInt()
#46855
Comments
Labeling as an analyzer issue as perhaps we should just remove this performance hint. The Not sure what the source of that hint is - @mraleph @rakudrama ? |
Slowness here comes from the fact that We should just replace |
@mraleph - thanks! Shall we keep the analyzer hint and reassign this to the VM then? |
I think we should remove the hint, it is not going to be faster than |
I edited sample, added int operator ~/(num other) => (this / other).toInt(); It would be faster than For analysis, I think, this rule shouldn't be removed. |
I propose the following action items:
An int-only hint was probably the original intention - when porting code from Java or C++ the user might expect The two forms are not exact replacements on the VM due edge cases: print(((-9223372036854775808) / (-1)).toInt()); // 9223372036854775808.0 .toInt() -> 9223372036854775807 (clipped)
print((-9223372036854775808) ~/ (-1)); // -9223372036854775808 (overflow into sign) @untp Tips on benchmarking: When I run the dart2js code, the first one is always fastest, even when I swap the calls to The following version avoids some problems with the benchmark code
To compensate for these effects, the benchmarks should be all warmed before any measurements are taken. import 'dart:math';
final random = Random();
void main() {
equals((a, b) => (a / b).toInt(), (a, b) => a ~/ b);
equals((a, b) => (a / b).truncate().toInt(), (a, b) => a ~/ b);
for (int i = 0; i < 3; i++) {
print('');
measure(' (a / b).toInt()', (a, b) => (a / b).toInt());
measure('(a / b).truncate().toInt()', (a, b) => (a / b).truncate().toInt());
measure(' a ~/ b', (a, b) => a ~/ b);
}
}
void measure(String name, int Function(double a, double b) function) {
final watch = Stopwatch()..start();
for (var i = 0; i < 10000000; i++) {
final a = random.nextDouble();
final b = random.nextDouble();
function(a, b);
}
print('$name ${watch.elapsed}');
}
void equals(int Function(double a, double b) function0, int Function(double a, double b) function1) {
for (var i = 0; i < 10000000; i++) {
final a = random.nextDouble();
final b = random.nextDouble();
if (function0(a, b) != function1(a, b)) {
throw Error();
}
}
} With dart2js I get:
You can see that the results settle down to being very similar. |
@rakudrama Thanks for comment. I didn't know there would be an edge case. I created another benchmark code for all possibilities with your benchmark tips. It also measures a no-op function, subtracts that from all durations. import 'dart:math';
final random = Random();
const functionNames = [
'a / b',
'(a / b).toInt()',
'(a / b).truncate()',
'(a / b).truncate().toInt()',
'(a / b).truncateToDouble().toInt()',
'a ~/ b',
];
final functions = <num Function(num, num)>[
(a, b) => a / b,
(a, b) => (a / b).toInt(),
(a, b) => (a / b).truncate(),
(a, b) => (a / b).truncate().toInt(),
(a, b) => (a / b).truncateToDouble().toInt(),
(a, b) => a ~/ b,
];
void main() {
final maximumNameLength =
functionNames.map((e) => e.length).reduce((a, b) => a > b ? a : b);
for (var _ = 0; _ < 3; _++) {
final c = measure((a, b) => 0);
for (var i = 0; i < functions.length; i++) {
print('${functionNames[i].padLeft(maximumNameLength)} '
'${measure(functions[i]) - c}');
}
print('');
}
print('Edge case');
final a = -9223372036854775808;
final b = -1;
for (var i = 0; i < functions.length; i++) {
print('${functionNames[i].padLeft(maximumNameLength)} '
'${functions[i](a, b)}');
}
}
Duration measure(num Function(num, num) function) {
final watch = Stopwatch()..start();
for (var i = 0; i < 10000000; i++) {
final a = random.nextDouble();
final b = random.nextDouble();
function(a, b);
}
return watch.elapsed;
} vm output
In dart2js, functions duration's are similar. I don't understand why For your proposed items:
Yes, this would be better. But which implementation should we use?
I am against this, because |
Avoid going into runtime for every operation. #46855 TEST=ci Change-Id: I5ab1d224f895342c8dd7fb9b8a7a98cfb0359db0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/209705 Reviewed-by: Stephen Adams <sra@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Slava Egorov <vegorov@google.com>
Since the timings are very close now, I don't think this has a lot of benefit as a performance-centered item. Now I think it is a stylistic report, and should be moved to linter. CC @pq |
Run this code in dart vm.
It outputs these durations on my environment.
I also compiled to exe with
dart compile exe
, and run:It is very slow.
Also analyzer says
The operator x ~/ y is more efficient than (x / y).toInt().
but it is wrong.x ~/ y
is actually more slower than(x / y).toInt()
.Problem is the this line:
sdk/runtime/lib/double.cc
Line 111 in bf2691c
It first calls
trunc
thenDoubleToInteger
.trunc
returns a double with given a double.DoubleToInteger
checks nan and finite, then casts double to int. Casting double to int, already truncates the value. So why slowtrunc
is used? It could beDoubleToInteger(left / right,
. Relevant SO questionDart SDK Version (
dart --version
)Dart SDK version: 2.13.4 (stable) (Wed Jun 23 13:08:41 2021 +0200) on "windows_x64"
The text was updated successfully, but these errors were encountered: