高校での最初のプログラミングクラス以来、文字列操作は神話上の「平均的な操作」よりも遅い、つまりコストが高いと聞いてきました。なぜそんなに遅くするのですか?(この質問は意図的に広く残されました。)
高校での最初のプログラミングクラス以来、文字列操作は神話上の「平均的な操作」よりも遅い、つまりコストが高いと聞いてきました。なぜそんなに遅くするのですか?(この質問は意図的に広く残されました。)
回答:
「平均的な操作」はプリミティブで行われます。しかし、文字列がプリミティブとして扱われる言語でさえ、それらは依然として内部の配列であり、文字列全体を含むすべての処理にはO(N)時間かかります。Nは文字列の長さです。
たとえば、2つの数値を追加するには、通常2〜4のASM命令が必要です。2つの文字列を連結( "追加")するには、新しいメモリ割り当てと、文字列全体を含む1つまたは2つの文字列コピーが必要です。
特定の言語要因により悪化する可能性があります。たとえば、Cでは、文字列は単にヌルで終わる文字の配列へのポインタです。これは、その長さがわからないことを意味するため、高速移動操作で文字列コピーループを最適化する方法はありません。nullターミネータの各バイトをテストできるように、一度に1文字をコピーする必要があります。
char*
ではなくaを期待し、strbuf
あなたは正方形1に戻ります。悪いデザインが言語に組み込まれたときにできることです。
buf
ポインタはそこにあります。私はそれが利用できないことを意味するつもりはありませんでした。むしろ、それは必要です。標準ライブラリと 同じくらい基本的なものも含めて、最適化されているが非標準の文字列型を知らないコードは、まだ低速で安全ではありませんchar*
。必要に応じてそのFUDを呼び出すことができますが、そうではありません。
これは古いスレッドであり、他の答えは素晴らしいと思いますが、何かを見落としているので、ここに私の(後)2セントがあります。
文字列の問題は、ほとんどの言語で二流の市民であり、実際にはほとんどの場合、実際に言語仕様自体の一部ではないということです。使用する際の苦痛を軽減します。
これの直接的な結果は、言語が複雑さの大部分をあなたの視界から隠すことです。他のプリミティブ型(トップ投票の回答などで説明されています)。
この根本的な「複雑さ」の要素の1つは、ほとんどの文字列実装が、文字列を表すために連続したメモリ空間を持つ単純なデータ構造を使用することにあります。
文字列全体へのアクセスを高速にしたいので、これは理にかなっています。ただし、この文字列を操作する場合は、恐ろしい費用がかかる可能性があります。後のインデックスを知っていれば、中央の要素にアクセスするのは速いかもしれませんが、条件に基づいて要素を探すのはそうではありません。
言語が文字列の長さをキャッシュせず、文字数をカウントするために文字列の長さを調べる必要がある場合、文字列のサイズを返すことでさえもコストがかかる場合があります。
同様の理由で、文字列に要素を追加すると、この操作を実行するためにメモリの再割り当てが必要になる可能性が高いため、コストがかかります。
そのため、異なる言語はこれらの問題に対して異なるアプローチを取ります。たとえば、Javaは、いくつかの正当な理由(キャッシングの長さ、スレッドセーフ)のために文字列を不変にする自由を取り、可変の対応物(StringBufferおよびStringBuilder)は、割り当てる必要のない大きなサイズのチャンクを使用してサイズを割り当てることを選択します毎回ですが、ベストケースシナリオを希望します。一般的にはうまく機能しますが、マイナス面は時々メモリの影響に対価を支払うことです。
また、これはあなたの言語の構文糖衣がこれをあなたから隠して素敵にプレイするという事実によるものです、あなたはしばしばそれをユニコードサポートの用語だとは思わないでしょう(特にあなたが本当にそれを必要としない限り)その壁にぶつかります)。また、いくつかの言語は先進的であるため、単純な8ビットcharプリミティブの基本配列を持つ文字列を実装しません。それらはUTF-8またはUTF-16で焼き付けられているか、何を持っているかをサポートします。その結果、非常に多くのメモリ消費が必要になりますが、メモリの割り当て、文字列の処理、コードポイントの操作と連動するすべてのロジックを実装します。
このすべての結果は、擬似コードで次のことと同等のことをすると:
hello = "hello,"
world = " world!"
str = hello + world
それはそうではないかもしれません-言語開発者が彼らがあなたが望むように振る舞わせるために全力を尽くしたにもかかわらず-
a = 1;
b = 2;
shouldBeThree = a + b
フォローアップとして、以下をお読みください。
「平均操作」という語句は、おそらく理論的なランダムアクセスストアドプログラムマシンの 1つの操作の省略形です。これは、さまざまなアルゴリズムの実行時間を分析するために通常使用される理論上のマシンです。
一般的な操作は、通常、ロード、加算、減算、保存、分岐と見なされます。たぶん、読んだり、印刷したり、停止したりすることもあります。
ただし、ほとんどの文字列操作には、これらの基本的な操作がいくつか必要です。たとえば、通常、文字列の複製にはコピー操作が必要です。したがって、文字列の長さに比例する(つまり、「線形」である)操作数が必要です。別の文字列内の部分文字列を見つけることにも線形の複雑さがあります。
それは、操作、文字列の表現方法、および存在する最適化に完全に依存します。文字列の長さが4バイトまたは8バイト(および整列)である場合、それらは必ずしも低速ではありません-多くの操作はプリミティブと同じくらい高速です。または、すべての文字列に32ビットまたは64ビットのハッシュがある場合、多くの操作も同様に高速になります(ただし、ハッシュコストは前払いします)。
また、「遅い」という意味にも依存します。ほとんどのプログラムは、必要なものに対して文字列を高速に処理します。文字列の比較は、2つのintの比較ほど高速ではないかもしれませんが、プロファイリングのみがプログラムの「遅い」の意味を明らかにします。