rs.nkmk.me

RustでVec, Stringのcapacityを取得・追加・縮小

Posted: | Tags: Rust, ベクタ, 文字列

Rustにおいて、ベクタVecは容量capacityの情報を持つ。capacityはそのベクタのために確保されたヒープ領域の容量。あらかじめcapacityを設定しておくと、余計な再割り当て(reallocation)を避けることができる。

文字列StringはUTF-8として有効なバイトのベクタVec<u8>であり、Vecと同様に容量capacityの情報を持つ。Stringの例は最後にまとめて示す。

ベクタVecの構成要素: ポインタ、容量、長さ

Rustにおいて、ベクタVecは3つの構成要素からなる。

Most fundamentally, Vec is and always will be a (pointer, capacity, length) triplet. No more, no less. Vec in std::vec - Capacity and reallocation - Rust

  • ポインタ(pointer): ヒープ領域のデータへのポインタ
  • 容量(capacity): そのベクタのために確保された領域の容量
  • 長さ(length): 要素数

容量を超えて要素を追加しようとすると、メモリの再割り当て(reallocation)が行われる。容量は「ベクタが再割り当てなしに保持できる要素数」ともいえる。

容量の確認: capacity

ベクタの長さと容量はそれぞれlen, capacityメソッドで取得できる。

let mut v = vec![0, 1, 2];
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);

len == capacity、すなわち、要素数が容量の上限に達した状態からpushで要素を追加すると、メモリの再割り当て(reallocation)が行われる。

v.push(3);
assert_eq!(v, [0, 1, 2, 3]);
assert_eq!(v.len(), 4);
assert_eq!(v.capacity(), 6);

ここでは元の容量(3)の倍の容量(6)が新たに確保されている。

再割り当て時に確保される容量は保証されていない。

Vec does not guarantee any particular growth strategy when reallocating when full, nor when reserve is called. The current strategy is basic and it may prove desirable to use a non-constant growth factor. Whatever strategy is used will of course guarantee O(1) amortized push. Vec in std::vec - Capacity and reallocation - Rust

rustc 1.67.0時点では元の容量の倍の容量が確保される模様。

容量を指定して空のベクタを生成: Vec::with_capacity

再割り当ては「新たに領域を確保し元の要素をそこにコピーする」という高コストな処理。

余計な再割り当てを避けるために、Vec::with_capacityで容量を指定して確保した上で空のベクタを生成できる。

let mut v = Vec::with_capacity(10);
assert_eq!(v.len(), 0);
assert_eq!(v.capacity(), 10);

v.push(0);
v.push(1);
assert_eq!(v.len(), 2);
assert_eq!(v.capacity(), 10);

必要な要素数があらかじめ分かっている場合はVec::newではなくVec::with_capacityを使うとpushによる再割り当てを避けることができるので効率的。

空のベクタを生成する方法についての詳細は以下の記事を参照。

容量を追加(確保): reserve, reserve_exact

reserve, reserve_exact

既存のベクタの容量を追加して確保するにはreserveメソッドを使う。

reserveおよび以降で説明するreserve_exact, try_reserve, try_reserve_exactはミュータブルなVecからのみ呼べる。

引数にnを指定すると、少なくともn個の要素を追加できる分の容量を確保する。reserve実行後のcapacitylen + n以上となる。すでに十分な容量があれば何もしない。

let mut v = vec![0, 1, 2];
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);

v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);

v.reserve(5);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);

reserveでは、例えば1個分の容量を追加すると、上述のpushの場合と同様に元の容量の倍の容量が確保される(rustc 1.67.0時点)。

let mut v = vec![0, 1, 2];
v.reserve(1);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 6);

reserve_exactメソッドを使うと最小限の容量が確保される。

let mut v = vec![0, 1, 2];
v.reserve_exact(1);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 4);

正確に最小であることを保証するものではないので要注意。

Note that the allocator may give the collection more space than it requests. Therefore, capacity can not be relied upon to be precisely minimal. Prefer reserve if future insertions are expected. Vec::reserve_exact in std::vec - Rust

try_reserve, try_reserve_exact

reserve, reserve_exactメソッドは、新しい容量がisize::MAXバイトを超えるとパニックになる。

Panics if the new capacity exceeds isize::MAX bytes. Vec::reserve in std::vec - Rust

try_reserve, try_reserve_exactResultを返す。

容量が上限を超えそうな場合はreserve, reserve_exactではなくtry_reserve, try_reserve_exactを使うとエラー処理が可能。

エラーの場合、容量capacityはそのまま。

let mut v = vec![0, 1, 2];
let result = v.try_reserve(10);
assert!(result.is_ok());
assert_eq!(v.capacity(), 13);

let mut v = vec![0, 1, 2];
let result = v.try_reserve(isize::MAX as usize);
assert!(result.is_err());
assert_eq!(v.capacity(), 3);

容量を縮小(解放): shrink_to_fit, shrink_to

余分な容量がある(=capacitylenより大きい)場合、shrink_to_fit, shrink_toメソッドで容量を縮小しメモリを開放できる。どちらもミュータブルなVecからのみ呼べる。

shrink_to_fitは容量を可能な限り小さくする。

let mut v = vec![0, 1, 2];
v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);

v.shrink_to_fit();
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);

shrink_toは指定した値を下限として、可能な限り容量を小さくする。

let mut v = vec![0, 1, 2];
v.reserve(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 13);

v.shrink_to(10);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 10);

v.shrink_to(0);
assert_eq!(v.len(), 3);
assert_eq!(v.capacity(), 3);

文字列Stringの場合

文字列StringはUTF-8として有効なバイトのベクタVec<u8>であり、Vecと同様に容量capacityの情報を持つ。容量に関連するメソッドも同様に提供されている。

capacityで容量を取得する。

バイトu8単位なので注意。例えば絵文字などは1文字で4バイト。

let s = String::from("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 3);

let s = String::from("😀");
assert_eq!(s.len(), 4);
assert_eq!(s.capacity(), 4);

バイト数ではなく文字数を取得したい場合は以下の記事を参照。

String::with_capacityであらかじめ容量を設定する。文字列を追加していく場合はString::newよりも効率的。

let mut s = String::with_capacity(10);
s.push_str("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 10);

reserveで容量を追加する。例は省略するが、reserve_exacttry_reserve, try_reserve_exactも提供されている。

let mut s = String::from("abc");
assert_eq!(s.len(), 3);
assert_eq!(s.capacity(), 3);

s.reserve(10);
assert_eq!(s.capacity(), 13);

続けてshrink_to, shrink_to_fitで容量を縮小する。

s.shrink_to(5);
assert_eq!(s.capacity(), 5);

s.shrink_to_fit();
assert_eq!(s.capacity(), 3);

Vecと同様に、reserve, reserve_exact, try_reserve, try_reserve_exactおよびshrink_to, shrink_to_fitはミュータブルなStringからのみ呼べる。

関連カテゴリー

関連記事