-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Closed
Labels
C-bugCategory: This is a bug.Category: This is a bug.I-slowIssue: Problems and improvements with respect to performance of generated code.Issue: Problems and improvements with respect to performance of generated code.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.
Description
I noticed the following performance cliff of 3 orders of magnitude:
#![feature(test)]
#[cfg(test)]
extern crate test;
#[bench]
fn bench_parse_1e304(b: &mut test::Bencher) {
// Anything under 1e305 is reasonably fast: ~120 ns/iter
b.iter(|| "1.234e304".parse::<f64>());
}
#[bench]
fn bench_parse_1e305(b: &mut test::Bencher) {
// Anything 1e305 or above is slower by 3 orders of magnitude: ~220,000 ns/iter
b.iter(|| "1.234e305".parse::<f64>());
}
Playground that reproduces the same behavior.
I tried Go and it parses both inputs in 60 ns. The resulting bits are the same in both implementations.
package test
import (
"strconv"
"testing"
)
func BenchmarkFast(b *testing.B) {
for n := 0; n < b.N; n++ {
strconv.ParseFloat("1.234e304", 64)
}
}
func BenchmarkAlsoFast(b *testing.B) {
for n := 0; n < b.N; n++ {
strconv.ParseFloat("1.234e305", 64)
}
}
Metadata
Metadata
Assignees
Labels
C-bugCategory: This is a bug.Category: This is a bug.I-slowIssue: Problems and improvements with respect to performance of generated code.Issue: Problems and improvements with respect to performance of generated code.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.Relevant to the library API team, which will review and decide on the PR/issue.
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
kennytm commentedon Aug 3, 2018
Parsing
0.99e305
is also fast 🤔.dtolnay commentedon Aug 3, 2018
Ah, I found this note in #27307:
Seems like we may want to look at what Go is doing to handle this better.
Mentioning @rkruppe.
kennytm commentedon Aug 3, 2018
https://golang.org/src/strconv/atof.go:
hanna-kruppe commentedon Aug 3, 2018
The general shape of the algorithm as quoted by @kennytm is
also what we're doing in the slow path(edit: I misread but it also involved bignums so in any case Go's algorithm doesn't seem like it would fit in 60ns). I haven't read the Go code in detail yet, but it also seems to have fast paths that don't construct any bignums. So my assumption is that this fast path is taken more often than the one in the Rust implementation (i.e., the check whether it's safe is more accurate) and that's why 1e305 is faster. Presumably there will be a similar performance cliff if you find an input that does force Go to actually go the bignum route.8 remaining items