ทำไมฉันถึงต้อง std :: ย้าย std :: shared_ptr?


148

ฉันได้อ่านซอร์สโค้ดของ Clangแล้วและฉันพบข้อมูลโค้ดนี้:

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = std::move(Value);
}

ทำไมฉันจะต้องการที่จะ?std::movestd::shared_ptr

มีจุดใดที่ถ่ายโอนความเป็นเจ้าของในทรัพยากรที่ใช้ร่วมกันหรือไม่

ทำไมฉันจะไม่ทำเช่นนี้แทน?

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = Value;
}

คำตอบ:


137

ผมคิดว่าสิ่งหนึ่งคำตอบอื่น ๆ ไม่ได้เน้นพอเป็นจุดของความเร็ว

std::shared_ptrจำนวนการอ้างอิงเป็นอะตอม การเพิ่มหรือลดจำนวนการอ้างอิงต้องอะตอมที่เพิ่มขึ้นหรือลดลง นี่คือร้อยครั้งช้ากว่าการเพิ่มขึ้น / ลดที่ไม่ใช่อะตอมไม่ใช่การพูดถึงว่าถ้าเราเพิ่มและลดจำนวนตัวนับเดียวกันเราจะจบลงด้วยจำนวนที่แน่นอนเสียเวลาและทรัพยากรในกระบวนการ

การย้ายshared_ptrแทนที่การคัดลอกทำให้เรา "ขโมย" จำนวนการอ้างอิงอะตอมมิกและเราทำให้โมฆะอีกshared_ptrอัน "ขโมย" จำนวนการอ้างอิงไม่ใช่อะตอมและเร็วกว่าการทำสำเนาเป็นร้อยเท่าshared_ptr(และทำให้การเพิ่มหรือลดการอ้างอิงอะตอมมิก )

โปรดทราบว่าเทคนิคนี้ใช้เพื่อการเพิ่มประสิทธิภาพอย่างแท้จริง การคัดลอก (ตามที่คุณแนะนำ) เป็นเพียงฟังก์ชั่นที่ดี


5
มันเร็วกว่าร้อยเท่าจริงหรือ คุณมีเกณฑ์มาตรฐานสำหรับสิ่งนี้หรือไม่?
xaviersjs

1
@xaviersjs การกำหนดต้องการการเพิ่มขึ้นของอะตอมตามด้วยการลดลงของอะตอมเมื่อค่าไม่อยู่ในขอบเขต การทำงานของอะตอมอาจใช้เวลาหลายร้อยรอบนาฬิกา ดังนั้นใช่มันช้ามากจริงๆ
Adisak

2
@Adisak เป็นครั้งแรกที่ฉันได้ยินการดึงและการเพิ่ม ( en.wikipedia.org/wiki/Fetch-and-add ) อาจใช้เวลาหลายร้อยรอบมากกว่าการเพิ่มขั้นพื้นฐาน คุณมีการอ้างอิงสำหรับสิ่งนั้นหรือไม่?
xaviersjs

2
@xaviersjs: stackoverflow.com/a/16132551/4238087ด้วยการดำเนินการลงทะเบียนไม่กี่รอบ 100's (100-300) รอบสำหรับอะตอมเหมาะกับการเรียกเก็บเงิน แม้ว่าการวัดมาจาก 2013 สิ่งนี้ยังคงเป็นจริงโดยเฉพาะอย่างยิ่งสำหรับระบบหลายซ็อกเก็ต NUMA
russianfool

1
บางครั้งคุณคิดว่าไม่มีเกลียวในรหัสของคุณ ... แต่แล้วห้องสมุดบางแห่งก็มาพร้อมกับซากปรักหักพังสำหรับคุณ ดีกว่าที่จะใช้การอ้างอิง const และ std :: ย้าย ... ถ้ามันชัดเจนและชัดเจนว่าคุณสามารถ .... กว่าพึ่งพาการนับการอ้างอิงตัวชี้
Erik Aronesty

123

โดยการใช้moveคุณหลีกเลี่ยงการเพิ่มและลดจำนวนการแบ่งปันทันที ซึ่งอาจช่วยให้คุณประหยัดค่าใช้จ่ายในการดำเนินการปรมาณู


1
มันไม่ได้เพิ่มประสิทธิภาพก่อนวัยอันควร?
YSC

11
@YSC ไม่ได้ถ้าใครก็ตามที่นำไปทดสอบที่นั่นจริง ๆ
OrangeDog

19
@YSC การเพิ่มประสิทธิภาพก่อนกำหนดเป็นสิ่งที่ไม่ดีหากมันทำให้การอ่านหรือบำรุงรักษาโค้ดยากขึ้น อันนี้ไม่มีอย่างน้อย IMO
Angew ไม่ภูมิใจใน SO

17
จริง นี่ไม่ใช่การเพิ่มประสิทธิภาพก่อนวัยอันควร มันเป็นวิธีที่สมเหตุสมผลในการเขียนฟังก์ชั่นนี้
การแข่งขัน Lightness ใน Orbit

60

ย้ายการดำเนินงาน (เช่นคอนสตรัคย้าย) ให้std::shared_ptrมีราคาถูกเช่นที่พวกเขาโดยทั่วไปเป็น"ตัวชี้ขโมย" (จากต้นทางไปยังปลายทาง; จะแม่นยำมากขึ้นบล็อกการควบคุมของรัฐทั้งหมดจะถูก "ขโมย" จากต้นทางไปยังปลายทางรวมทั้งข้อมูลจำนวนการอ้างอิง) .

แต่การคัดลอกการดำเนินการในการstd::shared_ptrเรียกใช้การเพิ่มจำนวนการอ้างอิงของอะตอม (เช่นไม่ใช่แค่++RefCountในRefCountสมาชิกข้อมูลจำนวนเต็มแต่เช่นการโทรInterlockedIncrementบน Windows) ซึ่งมีราคาแพงกว่าการขโมยพอยน์เตอร์ / รัฐ

ดังนั้นการวิเคราะห์การเปลี่ยนแปลงการนับการอ้างอิงของกรณีนี้ในรายละเอียด:

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

หากคุณผ่านspค่าจากนั้นคัดลอกภายในCompilerInstance::setInvocationวิธีการคุณมี:

  1. เมื่อเข้าสู่วิธีการที่shared_ptrมีพารามิเตอร์คัดลอกสร้าง: โทษนับอะตอม เพิ่มขึ้น
  2. ภายในร่างกายของวิธีการที่คุณคัดลอกshared_ptrพารามิเตอร์เข้าไปในข้อมูลสมาชิก: Ref นับอะตอม เพิ่มขึ้น
  3. เมื่อออกจากวิธีการ shared_ptrพารามิเตอร์ destructed: โทษนับอะตอม ลดลง

คุณมีการเพิ่มขึ้นสองครั้งของอะตอมและการลดลงของอะตอมหนึ่งครั้งรวมเป็น ดำเนินการปรมาณูสามครั้ง

แต่ถ้าคุณผ่าน shared_ptrพารามิเตอร์ตามค่าและจากนั้นstd::moveภายในเมธอด (ทำอย่างถูกต้องในรหัสของ Clang) คุณมี:

  1. เมื่อเข้าสู่วิธีการ shared_ptrมีพารามิเตอร์คัดลอกสร้าง: โทษนับอะตอม เพิ่มขึ้น
  2. ภายในร่างกายของวิธีนี้คุณ std::moveshared_ptrพารามิเตอร์เข้าไปในข้อมูลสมาชิก: ref นับไม่ไม่เปลี่ยนแปลง! คุณเป็นเพียงการขโมยพอยน์เตอร์ / รัฐ: ไม่มีการดำเนินการนับอะตอมที่มีราคาแพงที่เกี่ยวข้อง
  3. เมื่อออกจากวิธีการshared_ptrพารามิเตอร์จะถูกทำลาย; แต่เนื่องจากคุณย้ายเข้ามาในขั้นตอนที่ 2 ไม่มีสิ่งใดที่จะทำลายได้เนื่องจากshared_ptrพารามิเตอร์ไม่ได้ชี้ไปที่สิ่งใดอีกต่อไป ในกรณีนี้ไม่มีการลดลงของอะตอม

บรรทัดด้านล่าง: ในกรณีนี้คุณจะได้รับเพียงหนึ่งโทษนับเพิ่มอะตอมคือเพียงหนึ่งอะตอมการดำเนินงาน
อย่างที่คุณเห็นนี่เป็นดีกว่าการเพิ่มขึ้นสองครั้งของอะตอมบวกหนึ่งการลดลงของอะตอม (สำหรับการดำเนินการปรมาณูทั้งหมดสามครั้ง ) สำหรับกรณีการคัดลอก


1
ด้วยที่ควรสังเกต: ทำไมพวกเขาไม่เพียงแค่ผ่านการอ้างอิง const และหลีกเลี่ยง std ทั้งหมด :: ย้ายสิ่ง? เนื่องจาก pass-by-value ยังให้คุณส่งผ่านตัวชี้แบบ raw โดยตรงและจะมีเพียงหนึ่ง shared_ptr ที่สร้างขึ้น
Joseph Ireland

@JosephIreland เพราะคุณไม่สามารถย้ายการอ้างอิง const
Bruno Ferreira

2
@JosephIreland เพราะถ้าคุณเรียกว่าเป็นcompilerInstance.setInvocation(std::move(sp));แล้วจะไม่มีการเพิ่มขึ้น คุณสามารถรับพฤติกรรมเดียวกันได้โดยเพิ่มการโอเวอร์โหลดที่ใช้เวลาshared_ptr<>&&แต่ทำไมถึงซ้ำซ้อนเมื่อคุณไม่ต้องการ
วงล้อประหลาด

2
@BrunoFerreira ฉันตอบคำถามของฉันเอง คุณไม่จำเป็นต้องย้ายเพราะเป็นการอ้างอิงเพียงแค่คัดลอก ยังคงมีเพียงหนึ่งสำเนาแทนสอง เหตุผลที่พวกเขาไม่ทำอย่างนั้นเป็นเพราะมันจะไม่จำเป็นคัดลอกที่สร้างขึ้นใหม่ shared_ptrs เช่นจากหรือเป็นวงล้อที่กล่าวถึงsetInvocation(new CompilerInvocation) setInvocation(std::move(sp))ขออภัยถ้าความคิดเห็นแรกของฉันไม่ชัดเจนฉันโพสต์จริงโดยไม่ตั้งใจก่อนที่ฉันจะเขียนเสร็จและฉันตัดสินใจที่จะทิ้งมันไว้
Joseph Ireland

22

การคัดลอก a shared_ptrเกี่ยวข้องกับการคัดลอกตัวชี้วัตถุสถานะภายในและการเปลี่ยนจำนวนการอ้างอิง การย้ายมันเกี่ยวข้องกับการสลับพอยน์เตอร์ไปยังตัวนับการอ้างอิงภายในและวัตถุที่เป็นเจ้าของเท่านั้นดังนั้นจึงเร็วขึ้น


16

มีสองเหตุผลสำหรับการใช้ std :: move ในสถานการณ์นี้ คำตอบส่วนใหญ่ตอบปัญหาเรื่องความเร็ว แต่ไม่สนใจประเด็นสำคัญในการแสดงเจตนาของโค้ดให้ชัดเจนยิ่งขึ้น

สำหรับ std :: shared_ptr, std :: move อย่างไม่น่าเชื่อหมายถึงการโอนการเป็นเจ้าของของ pointee ในขณะที่การดำเนินการคัดลอกง่ายเพิ่มเจ้าของเพิ่มเติม แน่นอนว่าหากเจ้าของเดิมสละการเป็นเจ้าของ (เช่นโดยอนุญาตให้ std :: shared_ptr ถูกทำลาย) ในภายหลังการโอนการเป็นเจ้าของได้สำเร็จ

เมื่อคุณโอนความเป็นเจ้าของด้วย std :: move จะเห็นได้ชัดว่าเกิดอะไรขึ้น หากคุณใช้สำเนาปกติจะไม่ชัดเจนว่าการดำเนินการที่ต้องการเป็นการถ่ายโอนจนกว่าคุณจะตรวจสอบว่าเจ้าของเดิมยกเลิกการเป็นเจ้าของทันที ในฐานะโบนัสการใช้งานที่มีประสิทธิภาพมากขึ้นเป็นไปได้เนื่องจากการถ่ายโอนอะตอมของความเป็นเจ้าของสามารถหลีกเลี่ยงสถานะชั่วคราวที่จำนวนเจ้าของเพิ่มขึ้นหนึ่งราย (และผู้ดูแลมีการเปลี่ยนแปลงในจำนวนการอ้างอิง)


สิ่งที่ฉันกำลังมองหา ประหลาดใจที่คำตอบอื่น ๆ ละเลยความแตกต่างทางความหมายที่สำคัญนี้ พอยน์เตอร์อัจฉริยะเป็นข้อมูลเกี่ยวกับความเป็นเจ้าของ
qweruiop

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