Rubyで文字列を作成するときに、シャベル演算子(<<)が正等号(+ =)よりも優先されるのはなぜですか?


156

私はRuby Koansに取り組んでいます。

about_strings.rbtest_the_shovel_operator_modifies_the_original_stringKoanには、次のコメントが含まれています。

Rubyプログラマーは、文字列を作成するときに、プラスの等号演算子(+ =)よりもシャベル演算子(<<)を優先する傾向があります。どうして?

私の推測では速度が関係していると思いますが、ショベルオペレーターの速度を上げるフードの下での動作は理解できません。

誰かがこの設定の背後にある詳細を説明できますか?


4
shovelオペレーターは、新しいStringオブジェクトを作成するのではなく(メモリを消費して)、Stringオブジェクトを変更します。構文はきれいではないですか?cf. Javaと.NETにはStringBuilderクラスがあります
大佐パニック

回答:


257

証明:

a = 'foo'
a.object_id #=> 2154889340
a << 'bar'
a.object_id #=> 2154889340
a += 'quux'
a.object_id #=> 2154742560

したがって<<、新しい文字列を作成するのではなく、元の文字列を変更します。これは、ルビでa += bは代入である(a = a + b他の<op>=演算子についても同じことが言えます)の構文の省略形だからです。一方<<、エイリアスは、concat()その場でレシーバーを変更します。


3
ありがとう、noodl!つまり、本質的に、<<は新しいオブジェクトを作成しないので高速です。
erinbrown

1
このベンチマークは、Array#joinを使用するよりも遅いことを示してい<<ます。
Andrew Grimm

5
EdgeCaseの男の1人がパフォーマンス番号を使って説明を投稿しました:A String More About Strings
Cincinnati Joe

8
上記の@CincinnatiJoeリンクは壊れているようです。ここに新しいリンクがあります:文字
列のもう少し

Javaユーザーの場合:Rubyの「+」演算子はStringBuilderオブジェクトを介した追加に対応し、「<<」は文字列オブジェクトの連結に対応
nanosoft

79

性能証明:

#!/usr/bin/env ruby

require 'benchmark'

Benchmark.bmbm do |x|
  x.report('+= :') do
    s = ""
    10000.times { s += "something " }
  end
  x.report('<< :') do
    s = ""
    10000.times { s << "something " }
  end
end

# Rehearsal ----------------------------------------
# += :   0.450000   0.010000   0.460000 (  0.465936)
# << :   0.010000   0.000000   0.010000 (  0.009451)
# ------------------------------- total: 0.470000sec
# 
#            user     system      total        real
# += :   0.270000   0.010000   0.280000 (  0.277945)
# << :   0.000000   0.000000   0.000000 (  0.003043)

70

RubyをRubyの最初のプログラミング言語として学んでいる友人が、Ruby KoansシリーズでRubyの文字列を調べているときに同じ質問をしました。私はそれを次の類推を使って彼に説明しました。

あなたは半分が満たされた水のグラスを持っており、グラスを補充する必要があります。

最初の方法は、新しいグラスを用意し、水道の途中から水をグラスに入れ、この2番目のフルグラスを使ってグラスを補充します。あなたはあなたのガラスを補充する必要があるたびにこれを行います。

もう1つの方法は、半分のグラスを取り出し、水道水から直接水を補充する方法です。

1日の終わりに、グラスを補充する必要があるたびに新しいグラスを選ぶことを選択した場合、より多くのグラスをきれいにする必要があります。

同じことがシャベル演算子とプラス等号演算子にも当てはまります。プラスの等しいオペレーターは、ガラスを補充する必要があるたびに新しい「ガラス」を選びますが、ショベルのオペレーターは同じガラスを取り、それを補充します。1日の終わりに、Plus equal演算子の「グラス」コレクションが増えます。


2
素晴らしいアナロジー、それを愛した。
GMA 2013

5
素晴らしいアナロジーですが、ひどい結論です。あなたはそれらを気にする必要がないようにメガネが他の誰かによって掃除されていることを追加する必要があります。
Filip Bartuzi

1
素晴らしいアナロジー、それは素晴らしい結論に達していると思います。誰がガラスをきれいにする必要があるかよりも、使用されるガラスの数が多いと思います。特定のアプリケーションがマシンのメモリの限界を押し上げていること、およびそれらのマシンが一度に特定の数のグラスしかクリーニングできないことを想像できます。
チャーリーL

11

これは古い質問ですが、私はそれに遭遇しただけで、既存の回答に完全に満足していません。シャベルには多くの良い点があります<<連結よりも高速です+ =ですが、意味的な考慮事項もあります。

@noodlからの受け入れられた回答は、<<が既存のオブジェクトを変更し、+ =が新しいオブジェクトを作成することを示しています。したがって、文字列へのすべての参照に新しい値を反映させるか、または既存の参照をそのままにしてローカルで使用する新しい文字列値を作成するかを検討する必要があります。更新された値を反映するためにすべての参照が必要な場合は、<<を使用する必要があります。他の参照をそのままにしたい場合は、+ =を使用する必要があります。

非常に一般的なケースは、文字列への参照が1つしかない場合です。この場合、意味上の違いは重要ではなく、<<その速度のために<<を選択するのが自然です。


10

より高速なので、文字列のコピーを作成しません<->ガベージコレクターを実行する必要はありません。


上記の回答は詳細を示していますが、完全な回答を得るためにこれらをまとめるのはこれだけです。ここで重要なのは、「文字列を作成する」という言い回しのようです。つまり、元の文字列を必要としないか、必要としないことを意味します。
Drew Verlee、2015年

この答えは誤った前提に基づいています。短命なオブジェクトの割り当てと解放はどちらも、中途半端な最新のGCでは基本的に無料です。少なくともCでのスタック割り当てと同じくらい高速で、malloc/ よりもかなり高速ですfree。また、より最近のRuby実装では、オブジェクトの割り当てと文字列の連結を完全に最適化する可能性があります。大藤、変異オブジェクトはGCのパフォーマンスのためにひどいです。
イェルクWミッターク

4

回答の大半がカバーしている間+=、それは新しいコピーを作成するため、それがいることを心に留めておくことが重要です遅い+=<< されない交換可能!それぞれを別のケースで使用したい。

を使用<<すると、を指す変数も変更されbます。ここで、変更aしたくない場合も変更します。

2.3.1 :001 > a = "hello"
 => "hello"
2.3.1 :002 > b = a
 => "hello"
2.3.1 :003 > b << " world"
 => "hello world"
2.3.1 :004 > a
 => "hello world"

+=新しいコピーを作成するので、それを指す変数も変更されません。

2.3.1 :001 > a = "hello"
 => "hello"
2.3.1 :002 > b = a
 => "hello"
2.3.1 :003 > b += " world"
 => "hello world"
2.3.1 :004 > a
 => "hello"

この違いを理解することで、ループを処理するときに多くの頭痛の種を減らすことができます。


2

あなたの質問への直接の回答ではありませんが、なぜ「完全に上向きのビン」は常に私のお気に入りのRuby記事の1つでした。また、ガベージコレクションに関する文字列に関する情報も含まれています。


先端をありがとう、マイケル!まだRubyでこれを実現できていませんが、将来的には間違いなく役に立ちます。
erinbrown 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.