Rustの動的バッファとベンチマーク

この記事は [**セゾンテクノロジー Advent Calendar 2024**](https://qiita.com/advent-calendar/2024/saison-tech) 8日目の記事です。
シリーズ2は [**HULFT10**](https://www.hulft.com/software/hulft-10) のエンジニアによる投稿をお届けします。

お仕事でRustを書いてて気になったことを少しだけ調べて見たので書いてみます👀

## 動的バッファ

I/Oを伴うプログラムを書くと、なんだかんだバッファが必要になると思います。
普段は固定長バッファを用意しといて使い回せば良いのですが、大きいサイズのバッファが必要になったりでヒープにバッファを取りたい（スタックに取りたくない）ケースがあると思います。

## Rustでの動的バッファ

Rustのバッファは（多分）他言語と同じくバイト列`&[u8]`で、これをヒープに取る場合は`Vec<u8>`を作るのが主流かな～と勝手に思ってます。

じゃあ実際どうやって作るのか調べてみたところ、それっぽいのが2つありました。

### `Vec::with_capacity`&`Vec::set_len`

１つ目が`Vec::with_capacity`と`Vec::set_len`を使って作る方法

```rust
let len = 128;

let mut buf: Vec<u8> = Vec::with_capacity(len);
// lenはまだ0のままなので、u8スライスも長さ0
assert_eq!(buf.len(), 0);
assert_eq!(buf.as_slice().len(), 0);

// set_lenで長さを伸ばしてあげる必要がある
unsafe {
    buf.set_len(len);
}
assert_eq!(buf.len(), len);
assert_eq!(buf.as_slice().len(), len);
```

`Vec::set_len`を使う都合、`unsafe`ブロックを合わせて使う必要があります。
大したことはしてないんですがそれでもunsafe、なんか嫌ですね（個人的感想）

### `vec!`

もう一つ、`vec!`マクロでも作れます。

```rust
let len = 128;
let mut buf = vec![0u8; len];
assert_eq!(buf.len(), len);
assert_eq!(buf.as_slice().len(), len);
```

この場合、`unsafe`が不要です😀

ただ、初期化用の要素を指定してその値でクリアしてることからオーバーヘッドがありそうなのが気になります。

## ベンチマーク

ということでネタ作りも兼ねてベンチ取ってみました。
測定には[criterion.rs](https://github.com/bheisler/criterion.rs)を使ってみます。初なので使い方間違ってたらごめんなさい。

```rust:ソース
use criterion::{criterion_group, criterion_main, Criterion};

const LEN: usize = 10485760;

fn bench_buffer(c: &mut Criterion) {
    let mut group = c.benchmark_group("buffer");
    group.bench_function("vec_with_capacity", |b| {
        b.iter(|| {
            let mut buf: Vec<u8> = Vec::with_capacity(LEN);
            unsafe {
                buf.set_len(LEN);
            }
        })
    });
    group.bench_function("vec_macro", |b| {
        b.iter(|| {
            let mut _buf = vec![0u8; LEN];
        })
    });
}

criterion_group!(benches, bench_buffer);
criterion_main!(benches);
```

### 結果

```
buffer/vec_with_capacity
                        time:   [117.22 ps 118.46 ps 119.88 ps]
Found 7 outliers among 100 measurements (7.00%)
  4 (4.00%) high mild
  3 (3.00%) high severe
buffer/vec_macro        time:   [0.0000 ps 0.0000 ps 0.0000 ps]
Found 12 outliers among 100 measurements (12.00%)
  4 (4.00%) high mild
  8 (8.00%) high severe
```

マクロを使ったほうが早い…？というか一瞬で終わってる……
ちょっと意外

### 再測定

何度か再測定しても同じ結果だったので、1クリアするケースを追加して比較してみます。

```rust:ソース
use criterion::{criterion_group, criterion_main, Criterion};

const LEN: usize = 10485760;

fn bench_buffer(c: &mut Criterion) {
    let mut group = c.benchmark_group("buffer");
    group.bench_function("vec_with_capacity", |b| {
        b.iter(|| {
            let mut buf: Vec<u8> = Vec::with_capacity(LEN);
            unsafe {
                buf.set_len(LEN);
            }
        })
    });
    group.bench_function("vec_macro", |b| {
        b.iter(|| {
            let mut _buf = vec![0u8; LEN];
        })
    });
    // 1クリアするケース
    group.bench_function("vec_macro_one", |b| {
        b.iter(|| {
            let mut _buf = vec![1u8; LEN];
        })
    });
}

criterion_group!(benches, bench_buffer);
criterion_main!(benches);
```

```:結果
buffer/vec_with_capacity
                        time:   [115.12 ps 116.00 ps 117.17 ps]
Found 15 outliers among 100 measurements (15.00%)
  9 (9.00%) high mild
  6 (6.00%) high severe
buffer/vec_macro        time:   [0.0000 ps 0.0000 ps 0.0000 ps]
Found 12 outliers among 100 measurements (12.00%)
  4 (4.00%) high mild
  8 (8.00%) high severe
buffer/vec_macro_one    time:   [115.61 ps 116.78 ps 118.23 ps]
Found 11 outliers among 100 measurements (11.00%)
  1 (1.00%) high mild
  10 (10.00%) high severe
```

1クリアだと遅くなりました。0クリアだとよしなに最適化してくれてるんでしょうか？

## まとめ

動的バッファ作成には`vec!`で0クリアが良いのかもしれない…
けどpsレベルの差なので何でも良い気がする。

## 余談

`Vec`のままだとpushとかの操作でlenが変えられちゃうのが若干気になるので、Boxにしておいたほうがいいかも。私は面倒でやってないですが。

```rust
let len = 128;
let mut buf: Box<[u8]> = vec![0u8; len].into_boxed_slice();
assert_eq!(buf.len(), len);
assert_eq!(buf.as_ref().len(), len);
```

## 参考サイト

- https://mmi.hatenablog.com/entry/2017/08/06/230823
- https://stackoverflow.com/questions/32414567/allocating-a-buffer-on-the-heap-at-runtime
- https://qiita.com/termoshtt/items/b3129c473b1becef5878
- https://zenn.dev/kotabrog/articles/bcb72de6ad9849