dhat-rsは DHAT と同等の機能を提供するクレートで、 このクレートの dhat::DhatAlloc をアロケータに設定することで、ヒープの割当を追跡・計測してくれる。
計測する
サンプルとしてLeetCodeの問題を使う。
Check If Two String Arrays are Equivalent - LeetCode
2つの配列(文字列を要素に持つ配列)が引数で渡され、それが一致するかどうかを返すという問題。素直に実装するなら下記のように1行で済んでしまう。
impl Solution {
pub fn array_strings_are_equal(word1: Vec<String>, word2: Vec<String>) -> bool {
word1.concat() == word2.concat()
}
}
このプログラムに、下記のように100バイトの文字列(をいくつかに分割した配列)を与えるテストコードで、ヒープメモリの使用量を計測する。
#[test]
fn heap() {
let _dhat = Dhat::start_heap_profiling();
assert!(Solution::array_strings_are_equal(
vec!["12".into(), "34567890".into(), "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890".into()],
vec!["12".into(), "345678901234567890123456789012345678901234567890".into(), "12345678901234567890123456789012345678901234567890".into()],
));
}
計測結果は 544 bytes だった。
dhat: Total: 544 bytes in 10 blocks
dhat: At t-gmax: 544 bytes in 10 blocks
dhat: At t-end: 0 bytes in 0 blocks
ヒープの使用量を改善する
先程の解答ではスライスの concatメソッド を使っているのでコードが簡潔になるが、結合後の文字列が新しく作られるので、それだけ余分にヒープメモリを使っていることになる。
実験として、concatメソッドを使わずに、元の引数の参照を使って配列を比較する解答を実装してみる。
impl Solution {
pub fn array_strings_are_equal(word1: Vec<String>, word2: Vec<String>) -> bool {
let mut w2_index = 0;
let mut ww2_index = 0;
for w1 in word1.iter() {
for i in 0..w1.len() {
let ww1 = &w1[i..=i];
if word2.len() <= w2_index {
return false;
}
let w2 = word2.index(w2_index);
let ww2 = &w2[ww2_index..=ww2_index];
if ww1 != ww2 {
return false;
}
if w2.len() <= (ww2_index + 1) {
w2_index += 1;
ww2_index = 0;
} else {
ww2_index += 1;
}
}
}
if word2.len() >= (w2_index + 1) {
let w2 = &word2[w2_index];
if w2.len() >= (ww2_index + 1) {
return false;
}
}
true
}
}
かなりコードの見通しが悪いけど、このプログラムのヒープメモリの使用量は 344 bytes だった。最初のconcatを使った解答と比べると、ちょうど200bytes(100バイトの文字列の引数が2つ分)が減っていることがわかる。
dhat: Total: 344 bytes in 8 blocks
dhat: At t-gmax: 344 bytes in 8 blocks
dhat: At t-end: 0 bytes in 0 blocks