RustでStringまたは&strを返す関数を作成する

翻訳者から



これは、私が翻訳しているHerman RadtkeのRust and String storage and memoryシリーズの最後の記事です。 それは私にとって最も有用であるように思われ、最初はそれから翻訳を始めたいと思っていましたが、その後、シリーズの残りの記事もコンテキストを作成し、この記事が失われない言語のよりシンプルだが非常に重要な瞬間に導入するために必要であるように思われましたユーティリティ。




引数としてStringまたは&str英語を取る関数を作成する方法を学びました 。 ここで、 String



または&str



を返す関数を作成する方法を示し&str



。 また、なぜこれが必要なのかも議論したい。



まず、指定された文字列からすべてのスペースを削除する関数を作成しましょう。 関数は次のようになります。



 fn remove_spaces(input: &str) -> String { let mut buf = String::with_capacity(input.len()); for c in input.chars() { if c != ' ' { buf.push(c); } } buf }
      
      







この関数は、文字列バッファーにメモリを割り当て、 input



文字列のすべての文字を反復処理し、すべての非空白文字をbuf



バッファーに追加します。 質問は次のとおりです。入力に単一のスペースがない場合はどうなりますか? その場合、 input



値はbuf



とまったく同じになります。 この場合、 buf



をまったく作成しないほうが効率的です。 代わりに、与えられたinput



関数のユーザーに返したいだけです。 input



タイプは&str



ですが、この関数はString



返します。 input



タイプをString



変更できます。



 fn remove_spaces(input: String) -> String { ... }
      
      





しかし、2つの問題があります。 まず、 input



String



になった場合、関数のユーザーはinput



所有権を関数に移動する必要があるため、将来同じデータを処理できなくなります。 本当に必要な場合にのみ、 input



を取得する必要があります。 次に、入力はすでに&str



である可能性があり、ユーザーに文字列をString



に変換するように強制し、 buf



メモリを割り当てないようにする試みを無効にします。



レコードの複製



実際、スペースがない場合は入力文字列( &str



)を返し、スペースがある場合は新しい文字列( String



)を返し、それらを削除する必要があります。 これが、コピーオンライトタイプ(クローン-オン-ライト)のが助けになる場所です。 Cow



タイプでは、変数を所有している( Owned



)か、単に借りた( Borrowed



)かを無視できます。 この例では、 &str



は既存の文字列への参照であるため、これは借用データになります。 文字列にスペースがある場合、新しいString



メモリを割り当てる必要があります。 buf



変数この文字列を所有しています。 通常、 buf



所有権を移動し、ユーザーに返します。 Cow



を使用する場合、 buf



所有権をCow



タイプに移動してから、すでに所有権を返したいと考えています。



 use std::borrow::Cow; fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { if input.contains(' ') { let mut buf = String::with_capacity(input.len()); for c in input.chars() { if c != ' ' { buf.push(c); } } return Cow::Owned(buf); } return Cow::Borrowed(input); }
      
      





この関数は、元のinput



引数に少なくとも1つのスペースが含まれているかどうかをチェックしてから、新しいバッファーにメモリを割り当てます。 input



にスペースが含まれていない場合、そのまま返されます。 メモリ処理を最適化するために、実行時に少し複雑になりますCow



タイプの寿命は&str



と同じです。 前述したように、コンパイラは&str



リンクの使用を監視して、いつメモリを解放しても安全かを判断する必要があります(または、型がDrop



実装している場合はデストラクタメソッドを呼び出します)。



Cow



Deref



Deref



実装しているため、結果に新しいバッファが割り当てられているかどうかを知らなくても、これらのデータを変更しないメソッドを呼び出すことができることです。 例:



 let s = remove_spaces("Herman Radtke"); println!(" : {}", s.len());
      
      





s



を変更する必要がある場合は、 into_owned()



メソッドを使用して所有変数に変換できます。 Cow



に借用データが含まれる場合( Borrowed



選択されている場合)、メモリが割り当てられます。 このアプローチにより、変数への書き込み(または変更)が本当に必要な場合にのみ、遅延してクローンを作成(つまり、メモリを割り当て)することができます。



変更可能なCow::Borrowed



Cow::Borrowed







 let s = remove_spaces("Herman"); // s   Cow::Borrowed let len = s.len(); //         Deref let owned: String = s.into_owned(); //      String
      
      





変更可能なCow::Owned



Cow::Owned







 let s = remove_spaces("Herman Radtke"); // s   Cow::Owned let len = s.len(); //         Deref let owned: String = s.into_owned(); //    ,      String
      
      





Cow



アイデアは次のとおりです。





Into特性を使用する



Intoトレイトを使用して&str



String



に変換することについて話していました。 同様に、これを使用して&str



またはString



を目的のCow



オプションに変換できます。 .into()



を呼び出すと、コンパイラは正しい変換オプションを自動的に選択します。 .into()



を使用してもコードが遅くなることはありません。これは、 Cow::Owned



またはCow::Borrowed



を明示的に指定することをなくすための方法にすぎません。



 fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { if input.contains(' ') { let mut buf = String::with_capacity(input.len()); let v: Vec<char> = input.chars().collect(); for c in v { if c != ' ' { buf.push(c); } } return buf.into(); } return input.into(); }
      
      





最後に、イテレータを少し使用して例を簡単にできます。



 fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { if input.contains(' ') { input .chars() .filter(|&x| x != ' ') .collect::<std::string::String>() .into() } else { input.into() } }
      
      





牛の実際の使用



スペースを削除する私の例は少々難易度が高いように見えますが、実際のコードではこの戦略もアプリケーションを見つけます。 Rustカーネルには、無効なバイトの組み合わせなくしてバイトをUTF-8ストリングに変換する関数と、 行末をCRLFからLFに変換する関数があります。 これらの関数の両方について、最適なケースで&str



を返すことができる場合と、 String



メモリ割り当てを必要とする最適でないケースがあります。 私の頭に浮かぶ他の例は、文字列を有効なXML / HTMLにコーディングするか、SQLクエリで特殊文字を正しくエスケープすることです。 多くの場合、入力データは既に正しくエンコードまたはシールドされているため、単純に入力文字列をそのまま返す方が適切です。 データを変更する必要がある場合は、文字列バッファーにメモリを割り当てて、既に返す必要があります。



String :: with_capacity()を使用する理由



効率的なメモリ管理について話している間、文字列バッファーの作成時にString::new()



代わりにString::new()



String::with_capacity()



を使用したことに注意してください。 String::with_capacity()



String::new()



代わりにString::new()



使用できますが、バッファーに新しい文字を追加するときに再割り当てするのではなく、バッファーに必要なすべてのメモリを一度に割り当てる方がはるかに効率的です。



String



は、実際にはUTF-8コードポイントからのVec



ベクトルです。 String::new()



呼び出されると、Rustは長さゼロのベクトルを作成します。 たとえば、 input.push('a')



を使用して文字列バッファーに文字a



を配置すると、Rustはベクトルの容量を増やす必要があります。 これを行うには、2バイトのメモリを割り当てます。 さらにバッファーに文字を配置し、割り当てられたメモリサイズを超えると、Rustは行のサイズを2倍にし、メモリを再割り当てします。 彼はベクトルが超過するたびに容量を増やし続けます。 割り当てられた容量のシーケンスは次のとおりです: 0, 2, 4, 8, 16, 32, …, 2^n



、ここでnは割り当てられたメモリを超えたことをRustが検出した回数です。 メモリの再割り当てが非常に遅い(訂正:kmc_v3 、思ったほど遅くないかもしれないと説明した)。 Rustはカーネルに新しいメモリを割り当てるように要求するだけでなく、ベクトルの内容を古いメモリから新しいメモリにコピーする必要もあります。 Vec :: pushのソースコードを見て、自分でベクトルのサイズを変更するためのロジックを確認してください。



kmc_v3からのメモリ割り当ての更新
すべてがそれほど悪くないかもしれません:



  • 適切なアロケーターは、OSに大きなチャンクでメモリを要求し、それをユーザーに提供します。
  • まともなマルチスレッドメモリアロケーターも各スレッドのキャッシュをサポートしているため、常にアクセスを同期する必要はありません。
  • 非常に頻繁に、割り当てられたメモリを所定の場所に増やすことができます。そのような場合、データのコピーは行われません。 100バイトしか割り当てられていない場合もありますが、次の1000バイトが空いている場合は、アロケータがそれらを単に与えます。
  • コピーの場合でも、 memcpy



    バイトコピーmemcpy



    、完全に予測可能な方法でメモリにアクセスします。 したがって、これはおそらくメモリからメモリにデータを移動する最も効率的な方法です。 libcシステムライブラリには通常、特定のマイクロアーキテクチャ用に最適化されたmemcpy



    が含まれています。
  • MMUを再構成することで、割り当てられた大きなメモリチャンクを「移動」することもできます。つまり、1ページのデータをコピーするだけで済みます。 ただし、通常、ページテーブルの変更には大きな固定コストがかかるため、この方法は非常に大きなベクトルにのみ適しています。 Rustのjemalloc



    がそのような最適化を行うかどうかはjemalloc



    ません。


C ++でのstd::vector



サイズ変更は、要素ごとにmoveコンストラクターを個別に呼び出す必要があるため非常に遅くなり、例外をスローする可能性があります。


一般に、新しいメモリは、必要なときにのみ、必要なだけ割り当てるようにします。 remove_spaces("Herman Radtke")



などの短い行の場合、メモリ割り当てのオーバーヘッドは大きな役割を果たしません。 しかし、サイト上のすべてのJavaScriptファイル内のすべてのスペースを削除したい場合はどうすればよいですか? バッファにメモリを再割り当てするオーバーヘッドははるかに大きくなります。 データをベクター( String



またはその他)に配置する場合、ベクターの作成時に必要なメモリーのサイズを示すことは非常に便利です。 最良の場合、ベクトルの容量を正確に設定できるように、必要な長さを事前に知っています。 Vec



コードに対するコメントは、同じことについて警告しています。



他に読むものは何ですか?






All Articles