Rust でバイト列を16進数な文字列に変換する（Hex dump in Rust）

##環境
toolchain: nightly-i686-pc-windows-msvc
rustc 1.34.0-nightly (e1c6d0057 2019-02-22)


##目的
Rustで`01011110 01101011 01001011 00001101`みたいなバイト列を`5E6B4B0D`みたいな16進数表記の文字列に変換するということを考えてみます。

##実装

###format! マクロ

これが一番お手軽です。format! のパラメタについては[ここ](https://doc.rust-lang.org/std/fmt/index.html)を参照してください。

```rust
let data: [u8; 4] = [0b01011110, 0b01101011, 0b01001011, 0b00001101];
let result = data.iter().map(|n| format!("{:02X}", n)).collect::<String>();
println!("{}", result); //5E6B4B0D
```

でもこれって都度`format!()`で変換の処理があるし、遅いんじゃないか？と思ったのでベンチマーク取ってみます。

```rust
#[bench]
fn bench_to_hex_format(b: &mut Bencher) {
    let data: [u8; 4] = [0b01011110, 0b01101011, 0b01001011, 0b00001101];
    b.iter(|| {
        data.iter().map(|n| format!("{:02X}", n)).collect::<String>()
    });
}
```

私の PC では`test bench_to_hex_format ... bench: 3,796 ns/iter (+/- 598)`でした。ここからもうちょっと速くする方法を考えてみよう。

###ルックアップテーブル

変換処理をあらかじめ実行しておこうというアイディアですね。

```rust
#[bench]
fn bench_to_hex_lut(b: &mut Bencher) {
    let data: [u8; 4] = [0b01011110, 0b01101011, 0b01001011, 0b00001101];
    let lut: Vec<String> = (0u8..=255)
                            .map(|n| format!("{:02X}", n)).collect();
    b.iter(|| {
        data.iter()
            .map(|&n| lut.get(n as usize).unwrap().to_owned())
            .collect::<String>()
    });
}
```

結果は`test bench_to_hex_lut ... bench: 2,404 ns/iter (+/- 458)`です。最初の format! マクロバージョンより 1.6 倍くらいは速くなりました。

いや、まだいけるでしょ！

###Unsafe Rust

前述のルックアップテーブルとポインタ操作でゴリゴリやっていきます。

```rust
#[bench]
fn bench_to_hex_unsafe(b: &mut Bencher) {
    let data: [u8; 4] = [0b01011110, 0b01101011, 0b01001011, 0b00001101];
    let lut: Vec<u8> = (0u8..=255)
                        .map(|n| format!("{:02X}", n).into_bytes())
                        .flatten()
                        .collect();
    b.iter(|| {
        let mut result: [u8; 8] = [0u8; 8];
        data.iter().enumerate().for_each(|(i, &n)| {
            unsafe {
                ptr::copy_nonoverlapping(
                    lut.as_ptr().offset(n as isize * 2),
                    result.as_mut_ptr().offset(i as isize * 2),
                    2
                );
            }
        });
        unsafe {
            str::from_utf8_unchecked(
                slice::from_raw_parts(result.as_ptr(), 8)
            ).to_owned()
        }
    });
}
```

結果は`test bench_to_hex_unsafe ... bench: 273 ns/iter (+/- 18)`でした。最初の format! マクロバージョンより 14 倍くらいは速くなりました。やったぜ！

###追記

上記 Unsafe Rust のコードでは公平を期す？ために`to_owned()`で`String`に変換していますが、`&str`のままで良いのならさらに速くなります。ただ、`to_owned()`を消すと何も処理しないように最適化されて、ベンチマークの結果が 2ns とか異常な結果になってしまうので、`test::black_box()`で包んで最適化されないようにしてやる必要があります。

```rust
#[bench]
fn bench_to_hex_unsafe_str(b: &mut Bencher) {
    // 省略
    b.iter(|| {
        // 省略
        test::black_box(unsafe {
            str::from_utf8_unchecked(
                slice::from_raw_parts(result.as_ptr(), 8)
            )
        })
    });
}
```

結果は`test bench_to_hex_unsafe_str ... bench: 89 ns/iter (+/- 6)`でした。format! マクロバージョンの 42 倍。Awesome!!

##まとめ
`test bench_to_hex_format ....... bench: 3,796 ns/iter (+/- 598)`
`test bench_to_hex_lut .......... bench: 2,404 ns/iter (+/- 458)`
`test bench_to_hex_unsafe ....... bench: 273 ns/iter (+/- 18)`
`test bench_to_hex_unsafe_str ... bench: 89 ns/iter (+/- 6)`