文字列を1つずつ連結するのは非効率ですか?


11

Cでのプログラミングの日々を思い出します。2つの文字列が結合されると、OSは結合された文字列にメモリを割り当てる必要があります。解放されます。したがって、リストに参加する場合のようにこれが複数回行われると、OSは次の連結後に解放されるために、より多くのメモリを常に割り当てる必要があります。Cでこれを行うより良い方法は、結合された文字列の合計サイズを決定し、結合された文字列のリスト全体に必要なメモリを割り当てることです。

現在、最新のプログラミング言語(C#など)では、コレクションを反復処理し、すべての文字列を一度に1つの文字列参照に追加することで、コレクションの内容が結合されるのがよく見られます。これは、最新のコンピューティング能力を備えていても非効率ではありませんか?


コンパイラとプロファイラに任せてください、彼らはそれを気にします、あなたの時間は文字列の連結よりもはるかに高価です。
OZ_

7
実装に依存します-特定の文字列ライブラリのドキュメントを実際に確認する必要があります。O(1)時間で、参照によって連結する文字列を実装することができます。いずれにせよ、任意の長さの文字列のリストを連結する必要がある場合、この種のことのために設計されたクラスまたは関数を使用する必要があります。
来襲

通常、文字列の連結などは、オペレーティングシステムではなくライブラリ関数によって処理されます。OSはメモリ割り当てに関与する可能性がありますが、文字列などの比較的小さなオブジェクトにはおそらく関与しません。
カレブ

@Caleb OSはすべてのメモリ割り当てに関与しています。この規則に従わないことは、メモリリークの一種です。例外は、アプリケーションに文字列がハードコーディングされている場合です。これらは、生成されたアセンブリ内のバイナリデータとして書き込まれます。ただし、文字列を操作(または場合によっては割り当て)したらすぐに、メモリに格納する必要があります(つまり、メモリを割り当てる必要があります)。
-JSideris

4
@Bizorke典型的なシナリオでは、OSによってプロセスに既に割り当てられているメモリからさまざまなメモリチャンクを割り当てるために、malloc()(OSではなくC標準ライブラリの一部)のようなメモリアロケータが使用されます。プロセスがメモリ不足になり、さらに要求する必要がない限り、OSが関与する必要はありません。また、割り当てによってページフォールトが発生する場合は、下位レベルに参加することもあります。はい、OSは最終的にメモリを提供しますが、プロセス内の文字列やその他のオブジェクトの断片的な割り当てに必ずしも関与するわけではありません。
カレブ

回答:


21

少なくとも私がよく知っている言語(C、Java、C#)では、非効率な理由の説明は正確です。私は仕事C#コードでの豊富な使用法がありStringBuilderString.Format過剰に再割り当てを避けるためにtechiniquesを保存するすべてのメモリされている、など。

したがって、あなたの質問の答えを得るために、別の質問をしなければなりません:文字列を連結することが本当にStringBuilderStringBuffer問題にならないなら 、なぜクラスが好き存在するのでしょうか?なぜこのようなクラスの使用は、半初心者のプログラミングの本やクラスにも含まれているのですか?どうやら早すぎる最適化のアドバイスがそれほど顕著になるのでしょうか?

ほとんどの文字列連結開発者が回答を純粋に経験に基づいている場合、ほとんどはそれが違いを生むことはないと言い、「読みやすい」を支持してそのようなツールの使用を避けfor (int i=0; i<1000; i++) { strA += strB; }ます。 しかし、彼らはそれを決して測定しませんでした。

この質問に対する本当の答えは、このSOの回答で見つけることができます。これは、1つのインスタンスで、50,000文字列(アプリケーションによってはよくあることかもしれません)を連結すると、小さな文字列でも1000xのパフォーマンスヒットにつながることを明らかにしています。

パフォーマンスが文字通り何も意味しない場合は、必ず連結してください。しかし、代替(StringBuilder)を使用することは困難であるか読みにくいことに同意しません。したがって、「時期尚早な最適化」防御を起動すべきではない合理的なプログラミング手法となります。

更新:

これが何に帰着するかは、プラットフォームを知っており、そのベストプラクティスに従うことだと思います。2つの異なる「現代言語」からの2つの例:

  1. 別のSO回答正反対の性能特性(+ = VS array.join)があることが判明した時々で真のJavaScript。一部のブラウザでは、文字列の連結は自動的に最適化されるように見えますが、そうでない場合もあります。(少なくともSOの質問では)推奨事項は、連結するだけで、心配しないことです。
  2. 別のケースでは、Javaコンパイラ、連結をStringBuilderなどのより効率的な構成に自動的に置き換えることができます。ただし、他の人が指摘しているように、これは不確定であり、保証されていません。StringBuilderを使用しても読みやすさは損なわれません。この特定のケースでは、大規模なコレクションの連結の使用や、不確定なJavaコンパイラの動作に依存することをお勧めします。同様に、.NETでは、並べ替えの最適化は実行されません

すべてのプラットフォームのすべてのニュアンスをすぐに知らないのは、まさに重大な罪ではありませんが、このような重要なプラットフォームの問題を無視することは、JavaからC ++に移行し、メモリの割り当てを気にしないことに似ています。


-1:メジャーBSが含まれます。StringBuilderを使用する場合strA + strBまったく同じです。パフォーマンスが1倍になります。または、測定方法に応じて0x。詳細については、codinghorror.com / blog / 2009/01 /…
amara

5
@sparkleshy:私の推測では、SOの答えはJavaを使用し、リンクされた記事はC#を使用しています。「実装に依存する」および「特定の環境で測定する」と言う人々に同意します。
甲Chanちゃん

1
@KaiChan:文字列の連結はjavaとc#で基本的に同じです
アマラ

3
@sparkleshy-ポイントをとるが、StringBuilder、String.Joinなどを使用して正確に2つの文字列を連結することは、推奨されることはほとんどありません。さらに、OPの質問は、具体的には「結合されるコレクションのコンテンツ」に関するものであり、そうではありません(StringBuilderなどが非常に適用可能)。とにかく、私は例をより重要なものに更新します。
ケビンマコーミック

3
私はこの質問の目的のために言語を気にしません。一部の言語で舞台裏でstringbuilderを使用すると、文字列のリスト全体を連結することが非効率ではない理由が説明され、私の質問に答えています。ただし、この回答は、リストへの参加が潜在的に危険である可能性があることを説明しており、代替として文字列ビルダーを推奨しています。評判の損失や誤解の可能性を避けるために、コンパイラーが裏で文字列ビルダーを使用することを答えに追加することをお勧めします。
-JSideris

2

おおよそ説明した理由により、効率的ではありません。C#およびJavaの文字列は不変です。文字列に対する操作は、Cの場合とは異なり、元のインスタンスを変更する代わりに個別のインスタンスを返します。複数の文字列を連結する場合、各ステップで個別のインスタンスが作成されます。これらの未使用のインスタンスを割り当てて後でガベージコレクションすると、パフォーマンスが低下する可能性があります。今回のみ、メモリ管理がガベージコレクタによって処理されます。

C#とJavaはどちらも、このタイプのタスク専用の可変文字列としてStringBuilderクラスを導入しています。Cで同等の方法は、連結された文字列を配列に結合する代わりに、連結された文字列のリンクリストを使用することです。C#は、文字列のコレクションを結合するための、文字列に対する便利なJoinメソッドも提供します。


1

厳密に言うと、CPUサイクルの使用効率は低いため、正しいです。しかし、開発者の時間、メンテナンスコストなどはどうでしょうか。方程式に時間のコストを追加する場合、ほとんどの場合、最も簡単なことを行い、必要に応じてスロービットをプロファイリングして最適化する方が効率的です。
「プログラム最適化の最初のルール:実行しないでください。プログラム最適化の2番目のルール(専門家のみ!):まだ実行しないでください。」


3
あまり効果的なルールではないと思います。
OZ_

@OZ_:これは広く使われている引用(マイケルA.ジャクソン)とドナルド・クヌースのような人によるものです...それから、この1つがあります。必ずしも達成することなく)盲目的愚かさを含む他の単一の理由よりも。
-mattnz

2
マイケル・A・ジャクソンはイギリス人だったので、最適化ではなく最適化だということを指摘しておきます。ある時点で、ウィキペディアのページを本当に修正する必要があります。* 8 ')
マークブース

私は完全に同意します、あなたはそれらのスペルミスを修正するべきです。私の母国語はクイーンズイングリッシュですが、イントラウェブでアメリカ語を話すのは簡単だと思います
.......-mattnz

誰かがユーザーのことを考えないでしょう。開発者が作成するのを少し速くすることもできますが、その場合、顧客の一人一人がそのために苦しむことになります。あなたのためではなく、彼らのためにあなたのコードを書いてください。
gbjbaanb

1

実用的なテストなしでパフォーマンスについて何かを言うのは非常に難しいです。最近、JavaScriptでナイーブな文字列の連結が、推奨される「メークリストと結合」ソリューションよりも高速であることがわかりました(ここでテスト、t1とt4を比較してください)。私はまだそれがなぜ起こるのか戸惑っています。

パフォーマンス(特にメモリ使用量)について推論するときに尋ねる可能性のある質問は次のとおりです。1)入力はどれくらいですか?2)私のコンパイラはどれくらい賢いですか?3)ランタイムはどのようにメモリを管理しますか?これは網羅的なものではありませんが、出発点です。

  1. 入力はどれくらいですか?

    複雑なソリューションのオーバーヘッドは、多くの場合、実行する追加操作の形で、または必要な追加メモリで固定されます。これらのソリューションは大きなケースを処理するように設計されているため、実装者は通常、余分なコストを導入しても問題ありません。したがって、入力が十分に小さい場合、単純なソリューションは、このオーバーヘッドを回避するためだけに、複雑なソリューションよりもパフォーマンスが向上する可能性があります。(何が「十分に小さい」かを判断するのは難しい部分です)

  2. 私のコンパイラはどれくらい賢いですか?

    多くのコンパイラは、書き込まれているが読み取られない変数を「最適化」するのに十分なほど賢いです。同様に、優れたコンパイラーは、ナイーブな文字列連結を(コア)ライブラリ使用に変換できる場合があり、それらの多くが読み取りなしで作成された場合、それらの操作間で文字列に変換する必要はありません(たとえあなたのソースコードはまさにそれを行うようです)。私はそこにあるコンパイラがそれを行うかどうか、またはそれがどの程度行われるかわかりません(少なくとも、同じ式のいくつかの連結を一連のStringBuffer操作に置き換えることはAFAIK Javaです)が、可能性はあります。

  3. ランタイムはどのようにメモリを管理しますか?

    現代のCPUでは、ボトルネックは通常プロセッサではなくキャッシュです。コードが短時間で多くの「離れた」メモリアドレスにアクセスする場合、キャッシュレベル間でそのすべてのメモリを移動するのにかかる時間は、使用される命令のほとんどの最適化を上回ります。これは、世代別ガベージコレクターを使用するランタイムでは特に重要です。最近作成された変数(たとえば、同じ関数スコープ内)は通常、連続したメモリアドレスにあるためです。また、これらのランタイムは、メソッド呼び出し間でメモリを定期的に移動します。

    文字列の連結に影響を与える可能性のある1つの方法(免責事項:これはワイルドな推測です、私は確かに言うほど十分に知識がありません)は、ナイーブなもののメモリがそれを使用する残りのコードの近くに割り当てられた場合です(偶数ライブラリオブジェクトのメモリがそこから遠く離れて割り当てられている間(コードの計算中、ライブラリの消費中、ライブラリの消費中、コードの計算中などで多くのコンテキストが変更されるため、多くのキャッシュミスが発生します)。もちろん、OTOHの大きな入力ではキャッシュミスが発生します。そのため、複数の割り当ての問題はより顕著になります。

とはいえ、私はこれやその方法の使用を支持しているのではなく、テストとプロファイリングとベンチマークのみがパフォーマンスに関する理論的分析に先行している必要があります。今日のシステムのほとんどは、主題に関する深い専門知識なしでは完全に理解するには複雑すぎるためです。


確かに、これは間違いなくコンパイラーが理論上、文字列の束を一緒に追加し、文字列ビルダーを使用しているかのように最適化しようとしていることを認識できる分野であることに同意します。ただし、これは簡単なことではなく、最新のコンパイラに実装されているとは思いません。学部生の研究プロジェクトの素晴らしいアイデアを教えてくれました:D。
JSideris

この回答を確認してください。Javaコンパイラは既に内部StringBuilderで使用していtoStringます。必要なのは、変数が実際に必要になるまで呼び出さないことだけです。正しく思い出せば、それ単一の式に対して行われますが、私の唯一の疑問は、それが同じメソッド内の複数のステートメントに適用されるかどうかです。.NET内部については何も知りませんが、C#コンパイラでも同様の戦略が採用される可能性があると考えています。
mgibsonbr

0

ジョエルはしばらく前にこの主題について素晴らしい記事を書い。他の一部が指摘したように、それは言語に大きく依存しています。Cでの文字列の実装方法(長さフィールドなしのゼロ終端)のため、標準のstrcatライブラリルーチンは非常に非効率的です。ジョエルは、はるかに効率的な小さな変更を加えた代替案を提示します。


-1

文字列を1つずつ連結するのは非効率ですか?

番号。

「マイクロ最適化シアターの悲劇」を読みましたか?


4
「早すぎる最適化はすべての悪の根源です。」-クヌース
スコットCウィルソン

4
最適化におけるすべての悪の根源は、コンテキストなしでこのフレーズを使用することです。
OZ_

このようなフォーラムでは、何らかのサポート理由を提供せずに何かが真実であると言うだけでは役に立ちません。
エドワードストレンジ

@クレイジー・エディ:なぜジェフ・アトウッドが言わなければならないのか読んだのですか?
ジムG.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.