internal/strconv: check for digits first in readFloat
readFloat scans a float literal one byte at a time through a switch whose
arms were ordered rarest first. Every ordinary digit tested two rarer
cases and missed before matching. Reorder the arms so the two digit cases
come first. The case bodies are unchanged.
The package had no benchmark for hex or underscore input, so two are
added, and the existing benchmarks are converted to b.Loop.
goos: darwin
goarch: arm64
pkg: internal/strconv
cpu: Apple M2 Pro
│ old │ new │
│ sec/op │ sec/op vs base │
Atof64Decimal-10 18.38n ± 0% 15.12n ± 0% -17.74% (p=0.000 n=25)
Atof64Float-10 22.39n ± 1% 18.11n ± 0% -19.12% (p=0.000 n=25)
Atof64FloatExp-10 23.74n ± 0% 22.31n ± 0% -6.02% (p=0.000 n=25)
Atof64Big-10 58.92n ± 0% 42.75n ± 1% -27.44% (p=0.000 n=25)
Atof64RandomBits-10 63.19n ± 0% 53.62n ± 1% -15.14% (p=0.000 n=25)
Atof64RandomFloats-10 57.59n ± 0% 46.36n ± 0% -19.50% (p=0.000 n=25)
Atof64RandomLongFloats-10 72.98n ± 1% 63.56n ± 0% -12.91% (p=0.000 n=25)
Atof64Hex-10 38.01n ± 0% 35.51n ± 0% -6.58% (p=0.000 n=25)
Atof64Underscores-10 55.96n ± 0% 58.54n ± 0% +4.61% (p=0.000 n=25)
Atof32Decimal-10 17.81n ± 0% 14.53n ± 1% -18.42% (p=0.000 n=25)
Atof32Float-10 20.87n ± 0% 17.05n ± 0% -18.30% (p=0.000 n=25)
Atof32FloatExp-10 26.29n ± 0% 23.01n ± 0% -12.48% (p=0.000 n=25)
Atof32Random-10 37.93n ± 0% 34.25n ± 0% -9.70% (p=0.000 n=25)
Atof32RandomLong-10 64.19n ± 0% 55.40n ± 1% -13.69% (p=0.000 n=25)
geomean 36.60n 31.45n -14.06%
The linux/amd64 numbers below are from a shared 2-vCPU cloud instance and
are noisy, they should be treated as directional only. The smaller win is
expected because readFloat drops 25 instructions on arm64 against 10 on
amd64, since the reorder also removes a register shuffle the compiler
emits at the loop head.
goos: linux
goarch: amd64
pkg: internal/strconv
cpu: AMD EPYC 7763 64-Core Processor
│ old │ new │
│ sec/op │ sec/op vs base │
Atof64Decimal-2 32.41n ± 2% 28.59n ± 3% -11.79% (p=0.000 n=25)
Atof64Float-2 39.01n ± 3% 36.12n ± 3% -7.41% (p=0.000 n=25)
Atof64FloatExp-2 39.11n ± 4% 37.67n ± 3% -3.68% (p=0.003 n=25)
Atof64Big-2 99.93n ± 2% 79.83n ± 4% -20.11% (p=0.000 n=25)
Atof64RandomBits-2 100.70n ± 4% 89.12n ± 3% -11.50% (p=0.000 n=25)
Atof64RandomFloats-2 86.94n ± 5% 80.81n ± 4% -7.05% (p=0.000 n=25)
Atof64RandomLongFloats-2 121.8n ± 3% 117.3n ± 2% -3.69% (p=0.000 n=25)
Atof64Hex-2 67.69n ± 3% 64.36n ± 3% -4.92% (p=0.000 n=25)
Atof64Underscores-2 83.52n ± 3% 84.34n ± 4% ~ (p=0.200 n=25)
Atof32Decimal-2 29.56n ± 3% 27.17n ± 4% -8.09% (p=0.000 n=25)
Atof32Float-2 33.92n ± 3% 33.88n ± 4% ~ (p=0.229 n=25)
Atof32FloatExp-2 42.65n ± 3% 41.88n ± 4% -1.81% (p=0.038 n=25)
Atof32Random-2 61.15n ± 2% 58.62n ± 3% -4.14% (p=0.001 n=25)
Atof32RandomLong-2 107.70n ± 3% 99.94n ± 3% -7.21% (p=0.000 n=25)
geomean 60.29n 56.30n -6.62%
B/op and allocs/op are unchanged. Input that is mostly underscore
separators is about 5% slower, since that arm moved from first to last.
Updates #66327
diff --git a/src/internal/strconv/atof.go b/src/internal/strconv/atof.go
index 4567af3..64acd46 100644
--- a/src/internal/strconv/atof.go
+++ b/src/internal/strconv/atof.go
@@ -213,18 +213,8 @@
loop:
for ; i < len(s); i++ {
switch c := s[i]; true {
- case c == '_':
- underscores = true
- continue
-
- case c == '.':
- if sawdot {
- break loop
- }
- sawdot = true
- dp = nd
- continue
-
+ // Digits are by far the most common character here,
+ // so check for them first.
case '0' <= c && c <= '9':
sawdigits = true
if c == '0' && nd == 0 { // ignore leading zeros
@@ -252,6 +242,18 @@
trunc = true
}
continue
+
+ case c == '.':
+ if sawdot {
+ break loop
+ }
+ sawdot = true
+ dp = nd
+ continue
+
+ case c == '_':
+ underscores = true
+ continue
}
break
}
diff --git a/src/internal/strconv/atof_test.go b/src/internal/strconv/atof_test.go
index 05ac9e6..3c5135a 100644
--- a/src/internal/strconv/atof_test.go
+++ b/src/internal/strconv/atof_test.go
@@ -646,25 +646,25 @@
}
func BenchmarkAtof64Decimal(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("33909", 64)
}
}
func BenchmarkAtof64Float(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("339.7784", 64)
}
}
func BenchmarkAtof64FloatExp(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("-5.09e75", 64)
}
}
func BenchmarkAtof64Big(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("123456789123456789123456789", 64)
}
}
@@ -672,16 +672,20 @@
func BenchmarkAtof64RandomBits(b *testing.B) {
initAtof()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ i := 0
+ for b.Loop() {
ParseFloat(benchmarksRandomBits[i%1024], 64)
+ i++
}
}
func BenchmarkAtof64RandomFloats(b *testing.B) {
initAtof()
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ i := 0
+ for b.Loop() {
ParseFloat(benchmarksRandomNormal[i%1024], 64)
+ i++
}
}
@@ -693,7 +697,7 @@
}
b.ResetTimer()
idx := 0
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat(samples[idx], 64)
idx++
if idx == len(samples) {
@@ -702,20 +706,32 @@
}
}
+func BenchmarkAtof64Hex(b *testing.B) {
+ for b.Loop() {
+ ParseFloat("0x1.fp-2", 64)
+ }
+}
+
+func BenchmarkAtof64Underscores(b *testing.B) {
+ for b.Loop() {
+ ParseFloat("1_2_3_4_5_6_7_8.9_1", 64)
+ }
+}
+
func BenchmarkAtof32Decimal(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("33909", 32)
}
}
func BenchmarkAtof32Float(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("339.778", 32)
}
}
func BenchmarkAtof32FloatExp(b *testing.B) {
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
ParseFloat("12.3456e32", 32)
}
}
@@ -728,8 +744,10 @@
float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', -1, 32)
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ i := 0
+ for b.Loop() {
ParseFloat(float32strings[i%4096], 32)
+ i++
}
}
@@ -741,7 +759,9 @@
float32strings[i] = FormatFloat(float64(math.Float32frombits(n)), 'g', 20, 32)
}
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ i := 0
+ for b.Loop() {
ParseFloat(float32strings[i%4096], 32)
+ i++
}
}
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I want to double check that my benchmarks are after the b.Loop() modernization changes when I get home today. I think at least the Mac comparison was but need to verify the Linux one.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
I want to double check that my benchmarks are after the b.Loop() modernization changes when I get home today. I think at least the Mac comparison was but need to verify the Linux one.
Verified that they were both taken post-b.Loop() modernization.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Auto-Submit | +1 |
| Code-Review | +2 |
Nice, thanks!
LGTM but please address the minor grammar mistake.
// Digits are by far the most common character here,s/character/characters/
also, feel free to make this just one line (no need for line break)
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Nice, thanks!
LGTM but please address the minor grammar mistake.
Updated, thanks for the review!
// Digits are by far the most common character here,s/character/characters/
also, feel free to make this just one line (no need for line break)
Updated to this and made it one line. thanks!
> // Digits are by far the most common characters here, so check for them first.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |