ทำไมโอเปอเรเตอร์พลั่ว (<<) ต้องการให้มากกว่าบวก - เท่ากับ (+ =) เมื่อสร้างสตริงในรูบี?


156

ฉันทำงานผ่าน Ruby Koans

test_the_shovel_operator_modifies_the_original_stringปริศนาธรรมในabout_strings.rbรวมถึงการแสดงความคิดเห็นต่อไปนี้:

โปรแกรมเมอร์ทับทิมมักจะชอบโอเปอเรเตอร์พลั่ว (<<) มากกว่าเครื่องหมายบวก (+ =) เมื่อสร้างสตริง ทำไม?

ฉันเดาว่ามันเกี่ยวข้องกับความเร็ว แต่ฉันไม่เข้าใจการกระทำภายใต้ประทุนที่จะทำให้ผู้ปฏิบัติงานพลั่วทำงานเร็วขึ้น

ใครบางคนจะสามารถโปรดอธิบายรายละเอียดที่อยู่เบื้องหลังการตั้งค่านี้?


4
ผู้ดำเนินการ shovel ปรับเปลี่ยนวัตถุ String แทนที่จะสร้างวัตถุ String ใหม่ (หน่วยความจำการคิดต้นทุน) ไวยากรณ์ไม่สวยใช่ไหม cf เลย Java และ. NET มีคลาส StringBuilder
พันเอก Panic

คำตอบ:


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 ได้โพสต์คำอธิบายเกี่ยวกับตัวเลขประสิทธิภาพ: ข้อมูลเพิ่มเติมเล็กน้อยเกี่ยวกับ Strings
Cincinnati Joe

8
ลิงก์ @CincinnatiJoe ด้านบนดูเหมือนจะเสียหายนี่เป็นสิ่งใหม่: ข้อมูลเพิ่มเติมเล็กน้อยเกี่ยวกับ Strings
jasoares

สำหรับผู้ใช้ Java: '+' ตัวดำเนินการใน Ruby สอดคล้องกับการต่อท้ายวัตถุ StringBuilder และ '<<' สอดคล้องกับการต่อกันของวัตถุ String
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 เป็นภาษาการเขียนโปรแกรมแรกของเขาถามฉันคำถามเดียวกันนี้ในขณะที่ผ่าน Strings in Ruby ในซีรี่ส์ Ruby Koans ฉันอธิบายให้เขาฟังโดยใช้การเปรียบเทียบดังนี้

คุณมีน้ำหนึ่งแก้วที่ครึ่งหนึ่งและคุณต้องเติมแก้วของคุณ

วิธีแรกที่คุณทำได้โดยนำแก้วใหม่เติมน้ำครึ่งหนึ่งจากก๊อกจากนั้นใช้แก้วครึ่งที่สองนี้เพื่อเติมแก้วดื่มของคุณ คุณทำเช่นนี้ทุกครั้งที่คุณต้องการเติมแก้วของคุณ

วิธีที่สองที่คุณนำแก้วเต็มครึ่งหนึ่งของคุณและเติมน้ำตรงจากก๊อก

ในตอนท้ายของวันคุณจะมีแว่นตามากขึ้นในการทำความสะอาดถ้าคุณเลือกที่จะเลือกแก้วใหม่ทุกครั้งที่คุณต้องการที่จะเติมแก้วของคุณ

เช่นเดียวกับผู้ดำเนินการจอบและผู้ประกอบการบวกที่เท่าเทียมกัน ผู้ประกอบการที่เท่าเทียมกันจะเลือก 'แก้ว' ใหม่ทุกครั้งที่ต้องการเติมกระจกในขณะที่ผู้ดำเนินการขุดใช้แก้วเดียวกันและเติมมัน ในตอนท้ายของคอลเลกชัน 'แก้ว' มากขึ้นสำหรับผู้ประกอบการที่เท่าเทียมกันของพลัส


2
การเปรียบเทียบที่ยอดเยี่ยมชอบมาก
GMA

5
ข้อสรุปที่ยอดเยี่ยม แต่แย่มาก คุณต้องเพิ่มแว่นตาที่คนอื่นทำความสะอาดดังนั้นคุณจึงไม่ต้องสนใจพวกเขา
Filip Bartuzi

1
การเปรียบเทียบที่ดีฉันคิดว่ามันเป็นข้อสรุปที่ดี ฉันคิดว่ามันน้อยเกี่ยวกับผู้ที่ต้องทำความสะอาดกระจกและจำนวนของแว่นตาที่ใช้เลย คุณสามารถจินตนาการได้ว่าแอพพลิเคชั่นบางตัวกำลังเพิ่มขีด จำกัด ของหน่วยความจำบนเครื่องของพวกเขาและเครื่องเหล่านั้นสามารถทำความสะอาดแว่นตาได้ครั้งละจำนวนเท่านั้น
Charlie L

11

นี่เป็นคำถามเก่า แต่ฉันเพิ่งพบมันและฉันไม่พอใจอย่างเต็มที่กับคำตอบที่มีอยู่ มีจุดที่ดีมากมายเกี่ยวกับพลั่ว << ที่เร็วกว่าการต่อเรียง + = แต่ยังมีการพิจารณาความหมาย

คำตอบที่ได้รับการยอมรับจาก @noodl แสดงให้เห็นว่า << แก้ไขวัตถุที่มีอยู่ในสถานที่ในขณะที่ + = สร้างวัตถุใหม่ ดังนั้นคุณต้องพิจารณาว่าคุณต้องการให้การอ้างอิงทั้งหมดไปยังสตริงแสดงถึงค่าใหม่หรือไม่หรือต้องการออกจากการอ้างอิงที่มีอยู่คนเดียวและสร้างค่าสตริงใหม่เพื่อใช้ภายในเครื่อง หากคุณต้องการการอ้างอิงทั้งหมดเพื่อสะท้อนถึงค่าที่อัพเดตแล้วคุณต้องใช้ << หากคุณต้องการออกจากการอ้างอิงอื่น ๆ เพียงอย่างเดียวคุณต้องใช้ + =

กรณีที่พบบ่อยมากคือมีเพียงการอ้างอิงเดียวกับสตริง ในกรณีนี้ความแตกต่างทางความหมายไม่สำคัญและเป็นธรรมชาติที่ชอบ << เนื่องจากความเร็ว


10

เพราะมันเร็วกว่า / ไม่สร้างสำเนาของสตริง <-> ตัวรวบรวมขยะไม่จำเป็นต้องรัน


ในขณะที่คำตอบข้างต้นให้รายละเอียดเพิ่มเติมนี่เป็นเพียงคำตอบเดียวที่ให้คำตอบทั้งหมด กุญแจที่นี่ดูเหมือนจะอยู่ในความหมายของคำว่า "การสร้างสายอักขระ" ซึ่งหมายความว่าคุณไม่ต้องการหรือต้องการสตริงดั้งเดิม
Drew Verlee

คำตอบนี้ขึ้นอยู่กับสถานที่ตั้งที่ผิด: ทั้งการจัดสรรและการปล่อยวัตถุระยะสั้นนั้นฟรีใน GC ที่ทันสมัยครึ่งทาง มันเป็นอย่างน้อยเป็นอย่างรวดเร็วเป็นกองจัดสรรใน C และอย่างเร็วกว่า/malloc freeนอกจากนี้การปรับใช้ Ruby ที่ทันสมัยกว่านี้บางอย่างอาจช่วยปรับการจัดสรรวัตถุให้เหมาะสมและการต่อสตริงให้สมบูรณ์ OTOH การกลายพันธุ์วัตถุนั้นแย่มากสำหรับประสิทธิภาพของ GC
Jörg W Mittag

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

แม้ว่าจะไม่ใช่คำตอบสำหรับคำถามของคุณโดยตรง แต่ทำไมถังบรรจุที่เต็มแล้วขึ้นมาเสมอเป็นหนึ่งในบทความทับทิมที่ฉันโปรดปราน นอกจากนี้ยังมีข้อมูลบางอย่างเกี่ยวกับสตริงที่เกี่ยวข้องกับการรวบรวมขยะ


ขอบคุณสำหรับเคล็ดลับ Michael! ฉันยังไม่ได้ไปไกลขนาดนั้นใน Ruby แต่มันจะมีประโยชน์ในอนาคต
erinbrown
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.