ข้อดีของความหมายของ copy-on-write


10

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

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

เหตุผลที่ฉันถามคือฉันดูเหมือนจะไม่พบข้อดีของการมี COW เป็นพฤติกรรมเริ่มต้นโดยปริยาย ฉันใช้ Qt ซึ่งมีการใช้งาน COW สำหรับประเภทข้อมูลจำนวนมากซึ่งเกือบทั้งหมดมีพื้นที่จัดเก็บข้อมูลที่จัดสรรแบบไดนามิก แต่มันมีประโยชน์ต่อผู้ใช้อย่างไรจริงๆ?

ตัวอย่าง:

QString s("some text");
QString s1 = s; // now both s and s1 internally use the same resource

qDebug() << s1; // const operation, nothing changes
s1[o] = z; // s1 "detaches" from s, allocates new storage and modifies first character
           // s is still "some text"

เราจะชนะอะไรโดยใช้ COW ในตัวอย่างนี้

ถ้าหากเราตั้งใจที่จะทำคือการใช้การดำเนินงาน const, ซ้ำซ้อนอาจใช้งานได้ดีs1s

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

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

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

สำหรับฉันดูเหมือนว่ามีวิธีแก้ปัญหาที่มีประสิทธิภาพและอ่านได้มากกว่าไม่ว่าคุณจะต้องการหลีกเลี่ยงการทำสำเนาแบบลึกที่ไม่จำเป็นหรือคุณตั้งใจจะทำ ดังนั้นประโยชน์ในทางปฏิบัติจาก COW อยู่ที่ไหน ฉันคิดว่าจะต้องมีประโยชน์เพราะมันใช้ในกรอบที่ได้รับความนิยมและทรงพลัง

นอกจากนี้จากสิ่งที่ฉันได้อ่านตอนนี้ COW ถูกห้ามอย่างชัดเจนในไลบรารีมาตรฐาน C ++ ไม่รู้ว่า con ที่ฉันเห็นในนั้นมีบางอย่างเกี่ยวกับมันหรือไม่ แต่อย่างใดต้องมีเหตุผลสำหรับสิ่งนี้

คำตอบ:


15

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

ดังที่คุณกล่าวถึงคุณสามารถส่งวัตถุ const และในหลายกรณีที่เพียงพอ อย่างไรก็ตาม const เพียงรับประกันว่าผู้โทรไม่สามารถกลายพันธุ์ได้ (ยกเว้นพวกเขาconst_castแน่นอน) มันไม่จัดการกรณี multithreading และไม่จัดการกรณีที่มีการเรียกกลับ (ซึ่งอาจกลายพันธุ์วัตถุต้นฉบับ) การส่งผ่านวัตถุ COW ตามค่าจะทำให้ความท้าทายในการจัดการรายละเอียดเหล่านี้กับผู้พัฒนา API ไม่ใช่ผู้ใช้ API

กฎใหม่สำหรับ C + 11 ห้าม COW std::stringโดยเฉพาะ ตัววนซ้ำบนสตริงต้องไม่ถูกต้องหากบัฟเฟอร์สำรองถูกแยกออก หากมีการใช้ตัววนซ้ำเป็นchar*(ซึ่งตรงข้ามกับ a string*และดัชนี) ตัววนซ้ำนี้จะไม่ถูกต้องอีกต่อไป ชุมชน C ++ ต้องตัดสินใจว่าตัววนซ้ำสามารถทำให้ใช้งานไม่ได้และการตัดสินใจนั้นoperator[]ไม่ควรเป็นหนึ่งในกรณีเหล่านั้น operator[]บน a std::stringคืนchar&ซึ่งอาจมีการปรับเปลี่ยน ดังนั้นoperator[]จะต้องถอดสตริงออกซึ่งจะทำให้ตัววนซ้ำใช้ไม่ได้ นี่ถือว่าเป็นการค้าขายที่แย่และแตกต่างจากฟังก์ชั่นที่ชอบend()และcend()ไม่มีทางที่จะขอรุ่น const ที่มีจำนวนoperator[]น้อยนิดในการคัดเลือกนักแสดง ( ที่เกี่ยวข้อง )

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


การกลายพันธุ์สตริงเดียวกันในหลาย ๆ กระทู้ดูเหมือนจะเป็นการออกแบบที่แย่มากไม่ว่าคุณจะใช้ตัววนซ้ำหรือ[]ผู้ดำเนินการก็ตาม ดังนั้น COW เปิดใช้งานการออกแบบที่ไม่ดี - ซึ่งฟังดูไม่ค่อยมีประโยชน์ :) จุดในย่อหน้าสุดท้ายดูเหมือนจะใช้ได้ แต่ฉันเองไม่ใช่แฟนตัวยงของพฤติกรรมโดยปริยาย - คนมักจะรับมันแล้ว เป็นช่วงเวลาที่ยากลำบากในการหาสาเหตุที่รหัสไม่ทำงานตามที่คาดไว้และสงสัยจนกระทั่งพวกเขาคิดออกเพื่อตรวจสอบสิ่งที่ซ่อนอยู่หลังพฤติกรรมโดยนัย
dtech

สำหรับจุดที่ใช้const_castดูเหมือนว่ามันสามารถทำลาย COW ได้ง่ายพอ ๆ กับที่มันสามารถผ่านการอ้างอิง const ได้ ตัวอย่างเช่นQString::constData()ส่งคืนconst QChar *- const_castนั่นและ COW ยุบ - คุณจะกลายพันธุ์ข้อมูลของวัตถุต้นฉบับ
dtech

หากคุณสามารถส่งคืนข้อมูลจาก COW คุณต้องแยกออกก่อนที่จะทำเช่นนั้นหรือส่งคืนข้อมูลในรูปแบบที่ยังคงทราบ COW ( char*ไม่ทราบชัด) สำหรับพฤติกรรมโดยนัยฉันคิดว่าคุณถูกต้องมีปัญหากับมัน การออกแบบ API คือความสมดุลคงที่ระหว่างสองสุดขีด โดยนัยเกินไปและผู้คนเริ่มพึ่งพาพฤติกรรมพิเศษราวกับว่ามันเป็นส่วนหนึ่งของข้อมูลจำเพาะ ชัดเจนเกินไปและ API จะไม่สะดวกมากเกินไปเมื่อคุณเปิดเผยรายละเอียดพื้นฐานที่ไม่สำคัญและถูกเขียนลงในข้อมูลจำเพาะ API ของคุณทันที
Cort Ammon

ฉันเชื่อว่าstringคลาสนั้นมีพฤติกรรม COW เพราะผู้ออกแบบคอมไพเลอร์สังเกตว่าโค้ดขนาดใหญ่คัดลอกสตริงแทนที่จะใช้ const-reference หากพวกเขาเพิ่ม COW พวกเขาสามารถเพิ่มประสิทธิภาพกรณีนี้และทำให้ผู้คนมีความสุขมากขึ้น (และเป็นเรื่องถูกกฎหมายจนกระทั่งถึง C ++ 11) ฉันซาบซึ้งในตำแหน่งของพวกเขา: ในขณะที่ฉันผ่านสายของฉันเสมอโดยอ้างอิง const ฉันได้เห็นสิ่งที่ syntactic ขยะที่เพิ่ง detracts จากการอ่าน ฉันเกลียดการเขียนconst std::shared_ptr<const std::string>&เพื่อจับความหมายที่ถูกต้อง!
Cort Ammon

5

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

หากคุณมีวัตถุที่มีความปลอดภัยสูงเช่น Android และคุณต้องการคัดลอกและแทนที่แขนไซเบอร์เนติกก์ COW ดูเหมือนจะค่อนข้างสมเหตุสมผลในการรักษาไวยากรณ์ที่ไม่แน่นอนในขณะที่หลีกเลี่ยงการคัดลอกทั้ง Android เพียงเพื่อ ให้สำเนาแขนที่ไม่ซ้ำกัน การทำให้มันไม่เปลี่ยนรูปเพียงแค่เป็นโครงสร้างข้อมูลแบบถาวร ณ จุดนั้นอาจเหนือกว่า แต่การใช้ "บางส่วนของวัว" ที่ใช้กับชิ้นส่วน Android ส่วนบุคคลนั้นดูสมเหตุสมผลสำหรับกรณีเหล่านี้

ในกรณีเช่นนี้หุ่นยนต์สองชุดจะแบ่งปัน / อินสแตนซ์ลำตัวขาเท้าหัวคอไหล่กระดูกเชิงกรานและอื่น ๆ ข้อมูลเดียวที่จะแตกต่างกันระหว่างพวกเขาและไม่ใช้ร่วมกันคือแขนที่ทำ เป็นเอกลักษณ์สำหรับหุ่นยนต์ตัวที่สองในการเขียนทับแขน


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

ความถูกต้อง IMO ไม่ควร "ง่าย" เพื่อให้บรรลุไม่ใช่พฤติกรรมโดยปริยาย ตัวอย่างที่ดีของความถูกต้องคือความถูกต้องของ CONST เนื่องจากมีความชัดเจนและไม่มีที่ว่างสำหรับความคลุมเครือหรือผลข้างเคียงที่มองไม่เห็น การมีบางอย่างเช่น "ง่าย" และอัตโนมัติไม่เคยสร้างความเข้าใจในระดับพิเศษว่าสิ่งต่าง ๆ ทำงานอย่างไรซึ่งไม่เพียง แต่สำคัญต่อประสิทธิภาพโดยรวมเท่านั้น แต่ยังช่วยลดความเป็นไปได้ของพฤติกรรมที่ไม่พึงประสงค์ . ทุกสิ่งที่ทำให้เป็นไปได้โดยนัยกับ COW นั้นทำได้ง่ายเช่นกันและชัดเจนยิ่งขึ้น
dtech

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

@ ไดรเวอร์สิ่งที่เรามีคือสิ่งที่คล้ายกับภาษาการเขียนโปรแกรมที่มีกระบวนทัศน์ที่สำคัญยกเว้นความเรียบง่ายชนิดของการใช้ค่าความหมายและไม่มีความหมายประเภทอ้างอิง (อาจค่อนข้างคล้ายกับstd::vector<std::string>ก่อนที่เราจะมีemplace_backและย้ายความหมายใน C ++ 11) . แต่โดยพื้นฐานแล้วเรากำลังใช้อินสแตนซ์ ระบบโหนดอาจหรือไม่อาจแก้ไขข้อมูล เรามีสิ่งต่าง ๆ เช่นโหนดการส่งผ่านซึ่งไม่ได้ทำอะไรกับอินพุต แต่เพียงแค่เอาท์พุทสำเนา (พวกเขามีไว้สำหรับองค์กรผู้ใช้ของโปรแกรมของเขา) ในกรณีเหล่านี้ข้อมูลทั้งหมดจะถูกคัดลอกตื้นสำหรับประเภทที่ซับซ้อน ...

@ddriver การคัดลอกเมื่อเขียนของเรานั้นเป็นกระบวนการคัดลอกที่"สร้างอินสแตนซ์ที่ไม่ซ้ำกันโดยปริยาย" ทำให้ไม่สามารถแก้ไขต้นฉบับได้ หากวัตถุAถูกคัดลอกและไม่มีการทำอะไรกับวัตถุBก็เป็นสำเนาตื้น ๆ ราคาถูกสำหรับชนิดข้อมูลที่ซับซ้อนเช่นตาข่าย ตอนนี้ถ้าเราแก้ไขBข้อมูลที่เราแก้ไขBจะไม่ซ้ำกันผ่านทาง COW แต่Aไม่ถูกแตะต้อง (ยกเว้นบางส่วนของการอ้างอิงอะตอมมิก)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.