From 6c17473c7dfabbb3ec2d51f9c97cd56cdd34cffc Mon Sep 17 00:00:00 2001 From: Ivan Tham Date: Sat, 6 Jul 2019 23:24:26 +0800 Subject: [PATCH] Use branchless binary search to find_min_version Constant search time and no unpredictable conditional branches Benchmark results: 39 ns/iter -> 2 ns/iter (including multiple call) Based on https://github.com/rust-lang/rust/pull/45333 Inspired by https://raw.githubusercontent.com/RustStudy/rust_daily_news/master/img/%E4%BA%8C%E7%BB%B4%E7%A0%81%E7%94%9F%E6%88%90.png --- src/bits.rs | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/src/bits.rs b/src/bits.rs index df2e0c0..4015369 100644 --- a/src/bits.rs +++ b/src/bits.rs @@ -3,7 +3,7 @@ use std::cmp::min; #[cfg(feature = "bench")] -use test::Bencher; +use test::{black_box, Bencher}; use cast::{As, Truncate}; use optimize::{total_encoded_len, Optimizer, Parser, Segment}; @@ -859,17 +859,20 @@ pub fn encode_auto(data: &[u8], ec_level: EcLevel) -> QrResult { /// Finds the smallest version (QR code only) that can store N bits of data /// in the given error correction level. fn find_min_version(length: usize, ec_level: EcLevel) -> Version { - let mut min = 0; - let mut max = 39; - while min < max { - let half = (min + max) / 2; - if DATA_LENGTHS[half][ec_level as usize] < length { - min = half + 1; - } else { - max = half; - } + let mut base = 0usize; + let mut size = 39; + while size > 1 { + let half = size / 2; + let mid = base + half; + // mid is always in [0, size). + // mid >= 0: by definition + // mid < size: mid = size / 2 + size / 4 + size / 8 ... + base = if DATA_LENGTHS[mid][ec_level as usize] > length { base } else { mid }; + size -= half; } - Version::Normal((min + 1).as_i16()) + // base is always in [0, mid) because base <= mid. + base = if DATA_LENGTHS[base][ec_level as usize] >= length { base } else { base + 1 }; + Version::Normal((base + 1).as_i16()) } #[cfg(test)] @@ -907,5 +910,19 @@ mod encode_auto_tests { } } +#[cfg(feature = "bench")] +#[bench] +fn bench_find_min_version(bencher: &mut Bencher) { + bencher.iter(|| { + black_box(find_min_version(60, EcLevel::L)); + black_box(find_min_version(200, EcLevel::L)); + black_box(find_min_version(200, EcLevel::H)); + black_box(find_min_version(20000, EcLevel::L)); + black_box(find_min_version(640, EcLevel::L)); + black_box(find_min_version(641, EcLevel::L)); + black_box(find_min_version(999999, EcLevel::H)); + }) +} + //}}} //------------------------------------------------------------------------------