about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPietro Albini <pietro@pietroalbini.org>2018-11-15 11:04:46 +0100
committerGitHub <noreply@github.com>2018-11-15 11:04:46 +0100
commitf40f04bcc145afd1a3a96d3b5a8dd5c5575cf207 (patch)
tree51a5d31e9a971144988903432cbd7a215da45af6
parentc915f921d7267ca701b438e322e78fe04f0e12cd (diff)
parent7843e2792dce0f20d23b3c1cca51652013bef0ea (diff)
downloadrust-f40f04bcc145afd1a3a96d3b5a8dd5c5575cf207.tar.gz
rust-f40f04bcc145afd1a3a96d3b5a8dd5c5575cf207.zip
Rollup merge of #55932 - Turbo87:to_digit, r=alexcrichton
core/char: Speed up `to_digit()` for `radix <= 10`

I noticed that `char::to_digit()` seemed to do a bit of extra work for handling `[a-zA-Z]` characters. Since `to_digit(10)` seems to be the most common case (at least in the `rust` codebase) I thought it might be valuable to create a fast path for that case, and according to the benchmarks that I added in one of the commits it seems to pay off. I also created another fast path for the `radix < 10` case, which also seems to have a positive effect.

It is very well possible that I'm measuring something entirely unrelated though, so please verify these numbers and let me know if I missed something!

### Before

```
# Run 1
test char::methods::bench_to_digit_radix_10      ... bench:      16,265 ns/iter (+/- 1,774)
test char::methods::bench_to_digit_radix_16      ... bench:      13,938 ns/iter (+/- 2,479)
test char::methods::bench_to_digit_radix_2       ... bench:      13,090 ns/iter (+/- 524)
test char::methods::bench_to_digit_radix_36      ... bench:      14,236 ns/iter (+/- 1,949)

# Run 2
test char::methods::bench_to_digit_radix_10      ... bench:      16,176 ns/iter (+/- 1,589)
test char::methods::bench_to_digit_radix_16      ... bench:      13,896 ns/iter (+/- 3,140)
test char::methods::bench_to_digit_radix_2       ... bench:      13,158 ns/iter (+/- 1,112)
test char::methods::bench_to_digit_radix_36      ... bench:      14,206 ns/iter (+/- 1,312)

# Run 3
test char::methods::bench_to_digit_radix_10      ... bench:      16,221 ns/iter (+/- 2,423)
test char::methods::bench_to_digit_radix_16      ... bench:      14,361 ns/iter (+/- 3,926)
test char::methods::bench_to_digit_radix_2       ... bench:      13,097 ns/iter (+/- 671)
test char::methods::bench_to_digit_radix_36      ... bench:      14,388 ns/iter (+/- 1,068)
```

### After

```
# Run 1
test char::methods::bench_to_digit_radix_10      ... bench:      11,521 ns/iter (+/- 552)
test char::methods::bench_to_digit_radix_16      ... bench:      12,926 ns/iter (+/- 684)
test char::methods::bench_to_digit_radix_2       ... bench:      11,266 ns/iter (+/- 1,085)
test char::methods::bench_to_digit_radix_36      ... bench:      14,213 ns/iter (+/- 614)

# Run 2
test char::methods::bench_to_digit_radix_10      ... bench:      11,424 ns/iter (+/- 1,042)
test char::methods::bench_to_digit_radix_16      ... bench:      12,854 ns/iter (+/- 1,193)
test char::methods::bench_to_digit_radix_2       ... bench:      11,193 ns/iter (+/- 716)
test char::methods::bench_to_digit_radix_36      ... bench:      14,249 ns/iter (+/- 3,514)

# Run 3
test char::methods::bench_to_digit_radix_10      ... bench:      11,469 ns/iter (+/- 685)
test char::methods::bench_to_digit_radix_16      ... bench:      12,852 ns/iter (+/- 568)
test char::methods::bench_to_digit_radix_2       ... bench:      11,275 ns/iter (+/- 1,356)
test char::methods::bench_to_digit_radix_36      ... bench:      14,188 ns/iter (+/- 1,501)
```

I ran the benchmark using:

```sh
python x.py bench src/libcore --stage 1 --keep-stage 0 --test-args "bench_to_digit"
```
-rw-r--r--src/libcore/benches/char/methods.rs42
-rw-r--r--src/libcore/benches/char/mod.rs11
-rw-r--r--src/libcore/benches/lib.rs1
-rw-r--r--src/libcore/char/methods.rs25
4 files changed, 71 insertions, 8 deletions
diff --git a/src/libcore/benches/char/methods.rs b/src/libcore/benches/char/methods.rs
new file mode 100644
index 00000000000..faf820d871c
--- /dev/null
+++ b/src/libcore/benches/char/methods.rs
@@ -0,0 +1,42 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use test::Bencher;
+
+const CHARS: [char; 9] = ['0', 'x', '2', '5', 'A', 'f', '7', '8', '9'];
+const RADIX: [u32; 5] = [2, 8, 10, 16, 32];
+
+#[bench]
+fn bench_to_digit_radix_2(b: &mut Bencher) {
+    b.iter(|| CHARS.iter().cycle().take(10_000).map(|c| c.to_digit(2)).min())
+}
+
+#[bench]
+fn bench_to_digit_radix_10(b: &mut Bencher) {
+    b.iter(|| CHARS.iter().cycle().take(10_000).map(|c| c.to_digit(10)).min())
+}
+
+#[bench]
+fn bench_to_digit_radix_16(b: &mut Bencher) {
+    b.iter(|| CHARS.iter().cycle().take(10_000).map(|c| c.to_digit(16)).min())
+}
+
+#[bench]
+fn bench_to_digit_radix_36(b: &mut Bencher) {
+    b.iter(|| CHARS.iter().cycle().take(10_000).map(|c| c.to_digit(36)).min())
+}
+
+#[bench]
+fn bench_to_digit_radix_var(b: &mut Bencher) {
+    b.iter(|| CHARS.iter().cycle()
+        .zip(RADIX.iter().cycle())
+        .take(10_000)
+        .map(|(c, radix)| c.to_digit(*radix)).min())
+}
diff --git a/src/libcore/benches/char/mod.rs b/src/libcore/benches/char/mod.rs
new file mode 100644
index 00000000000..a656e82cb61
--- /dev/null
+++ b/src/libcore/benches/char/mod.rs
@@ -0,0 +1,11 @@
+// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+mod methods;
diff --git a/src/libcore/benches/lib.rs b/src/libcore/benches/lib.rs
index ced77d77918..d44f1577d56 100644
--- a/src/libcore/benches/lib.rs
+++ b/src/libcore/benches/lib.rs
@@ -15,6 +15,7 @@ extern crate core;
 extern crate test;
 
 mod any;
+mod char;
 mod hash;
 mod iter;
 mod num;
diff --git a/src/libcore/char/methods.rs b/src/libcore/char/methods.rs
index 35181afea3d..d6fcff644ac 100644
--- a/src/libcore/char/methods.rs
+++ b/src/libcore/char/methods.rs
@@ -121,15 +121,24 @@ impl char {
     #[stable(feature = "rust1", since = "1.0.0")]
     #[inline]
     pub fn to_digit(self, radix: u32) -> Option<u32> {
-        if radix > 36 {
-            panic!("to_digit: radix is too high (maximum 36)");
-        }
-        let val = match self {
-          '0' ..= '9' => self as u32 - '0' as u32,
-          'a' ..= 'z' => self as u32 - 'a' as u32 + 10,
-          'A' ..= 'Z' => self as u32 - 'A' as u32 + 10,
-          _ => return None,
+        assert!(radix <= 36, "to_digit: radix is too high (maximum 36)");
+
+        // the code is split up here to improve execution speed for cases where
+        // the `radix` is constant and 10 or smaller
+        let val = if radix <= 10  {
+            match self {
+                '0' ..= '9' => self as u32 - '0' as u32,
+                _ => return None,
+            }
+        } else {
+            match self {
+                '0'..='9' => self as u32 - '0' as u32,
+                'a'..='z' => self as u32 - 'a' as u32 + 10,
+                'A'..='Z' => self as u32 - 'A' as u32 + 10,
+                _ => return None,
+            }
         };
+
         if val < radix { Some(val) }
         else { None }
     }