Kürek operatörü (<<) Ruby'de bir dize oluştururken artı-eşittir (+ =) yerine neden tercih edilir?


156

Ruby Koans üzerinde çalışıyorum.

About_strings.rb içindeki test_the_shovel_operator_modifies_the_original_stringKoan aşağıdaki yorumu içerir:

Ruby programcıları, dizeleri oluştururken kürek operatörünü (<<) artı operatöre eşittir (+ =). Neden?

Benim tahminim hız içermesi, ancak kürek operatörünün daha hızlı olmasına neden olacak kaputun altındaki eylemi anlamıyorum.

Birisi bu tercihin arkasındaki ayrıntıları açıklayabilir mi?


4
Kürek operatörü, yeni bir String nesnesi (bellek belleği) oluşturmak yerine String nesnesini değiştirir. Sözdizimi hoş değil mi? bakınız Java ve .NET'in StringBuilder sınıfları vardır
Albay Panic

Yanıtlar:


257

Kanıt:

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

Böylece <<yeni bir dizgi oluşturmak yerine orijinal dizgiyi değiştirir. Bunun nedeni a += b, yakutta bir ödev olan sözdizimsel kısayol a = a + b(aynı diğer <op>=operatörler için de geçerlidir ) olmasıdır. Öte yandan <<, concat()alıcıyı yerinde değiştiren bir takma addır.


3
Teşekkürler, erişte! Yani, özünde, << daha hızlıdır çünkü yeni nesneler yaratmaz mı?
erinbrown

1
Bu kıyaslama , bunun Array#joinkullanmaktan daha yavaş olduğunu söylüyor <<.
Andrew Grimm

5
EdgeCase adamlarından biri performans numaraları ile bir açıklama yayınladı: Dizeler Hakkında Biraz Daha Fazlası
Cincinnati Joe

8
Yukarıdaki @CincinnatiJoe bağlantısı kopmuş gibi görünüyor, işte yeni bir tane: Dizeler Hakkında Biraz Daha Fazlası
jasoares

Java halkı için: Ruby'deki '+' operatörü StringBuilder nesnesi aracılığıyla
eklenmeye

79

Performans kanıtı:

#!/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

İlk programlama dili olarak Ruby'yi öğrenen bir arkadaşım, Ruby Koans serisinde Ruby'de Dizeler'den geçerken bana aynı soruyu sordu. Bunu aşağıdaki benzetmeyi kullanarak ona anlattım;

Yarı dolu bir bardak suyunuz var ve bardağınızı doldurmanız gerekiyor.

İlk olarak yeni bir bardak alarak, bir musluktan suyla yarıya kadar doldurarak ve daha sonra bu ikinci yarı dolu bardağı kullanarak camınızı doldurun. Bunu camınızı her doldurmanız gerektiğinde yaparsınız.

İkinci şekilde yarım bardakınızı alın ve doğrudan musluktan suyla doldurun.

Günün sonunda, camınızı doldurmak için her ihtiyaç duyduğunuzda yeni bir bardak seçmeyi seçerseniz, temizlemek için daha fazla bardağa sahip olursunuz.

Aynı şey kürek operatörü ve artı eşit operatör için de geçerlidir. Artı eşit operatör, her seferinde camını doldurmak gerektiğinde yeni bir 'cam' alırken, kürek operatörü sadece aynı camı alır ve yeniden doldurur. Günün sonunda Plus eşit operatör için daha fazla 'cam' koleksiyonu.


2
Büyük benzetme, onu sevdi.
GMA

5
büyük benzetme ama korkunç sonuçlar. Gözlüklerin başka biri tarafından temizlendiğini eklemeniz gerekir, böylece onlara bakmak zorunda kalmazsınız.
Filip Bartuzi

1
Büyük benzetme, bence iyi bir sonuca varıyor. Sanırım camı kimin temizlemesi gerektiği ve kullanılan gözlük sayısı hakkında daha az şey var. Bazı uygulamaların makinelerinde bellek sınırlarını zorladığını ve bu makinelerin aynı anda yalnızca belirli sayıda gözlüğü temizleyebileceğini hayal edebilirsiniz.
Charlie L

11

Bu eski bir soru, ama ben sadece onunla karşılaştım ve mevcut cevaplardan tam olarak memnun değilim. Kürek << birleştirme + = daha hızlı olması hakkında çok iyi noktalar vardır, ama aynı zamanda anlamsal bir dikkate vardır.

@Noodl tarafından kabul edilen cevap, << öğesinin mevcut nesneyi değiştirdiğini gösterirken + = yeni bir nesne oluşturur. Bu nedenle, dizeye yapılan tüm referansların yeni değeri yansıtmasını veya mevcut referansları tek başına bırakmak ve yerel olarak kullanmak için yeni bir dize değeri oluşturmak isteyip istemediğinizi göz önünde bulundurmanız gerekir. Güncellenen değeri yansıtmak için tüm referanslara ihtiyacınız varsa, << kullanmanız gerekir. Diğer referansları yalnız bırakmak istiyorsanız, + = kullanmanız gerekir.

Çok yaygın bir durum, dizeye yalnızca tek bir başvuru olmasıdır. Bu durumda, semantik fark önemli değildir ve hızı nedeniyle << tercih etmek doğaldır.


10

Daha hızlı olduğundan / dizenin bir kopyasını yaratmadığından <-> çöp toplayıcısının çalıştırılması gerekmez.


Yukarıdaki cevaplar daha fazla ayrıntı verirken, bu cevapları tam cevap için bir araya getiren tek cevaptır. Buradaki anahtar, "dizeleri oluşturma" ifadenizin orijinal dizeleri istemediğiniz veya buna ihtiyacınız olmadığı anlamına gelir.
Drew Verlee

Bu cevap yanlış bir önermeye dayanmaktadır: Kısa ömürlü nesneleri tahsis etmek ve serbest bırakmak, yarım terbiyeli modern GC'de esasen ücretsizdir. En azından C'de yığın tahsisi kadar hızlı ve malloc/ ' den önemli ölçüde daha hızlıdır free. Ayrıca, bazı daha modern Ruby uygulamaları muhtemelen nesne tahsisini ve dize birleştirmesini tamamen optimize edecektir. OTOH, mutasyona uğrayan nesneleri GC performansı için korkunçtur.
Jörg W Mittag

4

Cevaplar kapağın bir çoğunluğu ise +=yeni bir kopyasını oluşturur çünkü daha yavaş, bu akılda tutmak önemlidir +=ve << olmayan değiştirilebilir! Her birini farklı durumlarda kullanmak istiyorsunuz.

Kullanmak <<ayrıca işaret edilen değişkenleri de değiştirecektir b. Burada da aistemediğimiz zaman mutasyon geçiriyoruz .

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"

Çünkü +=yeni bir kopyasını yapar, o da değişmeden buna işaret ediyor herhangi bir değişken bırakır.

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"

Bu ayrımı anlamak, döngülerle uğraşırken çok fazla baş ağrısından kurtarabilir!


2

Sorunuza doğrudan bir cevap olmasa da, neden Tamamen Kalkık Bin her zaman en sevdiğim Ruby makalelerimden biri oldu. Ayrıca çöp toplama konusunda dizelerle ilgili bazı bilgiler içerir.


Bahşiş için teşekkürler, Michael! Henüz Ruby'de bu kadar ilerlemedim, ama gelecekte kesinlikle kullanışlı olacak.
erinbrown
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.