มีตัวอย่างที่ดีอยู่ที่นี่ แต่ฉันต้องการที่จะข้ามไปกับบางคนที่ไม่สามารถช่วยได้ตัน ในกรณีของฉันฉันเริ่มออกแบบโครงสร้างข้อมูลพร้อมกันที่ไม่เปลี่ยนรูปได้โดยหวังว่าจะสามารถรันโค้ดอย่างมั่นใจควบคู่ไปกับการอ่านและเขียนที่ทับซ้อนกันและไม่ต้องกังวลเกี่ยวกับสภาพการแข่งขัน มีการพูดคุยกัน John Carmack ให้แรงบันดาลใจแบบนั้นกับฉันที่เขาพูดถึงความคิดเช่นนั้น มันเป็นโครงสร้างพื้นฐานที่สวยงามและใช้งานได้ง่ายเช่นนี้:
แน่นอนว่ามีระฆังและนกหวีดอีกสองสามตัวเช่นสามารถลบองค์ประกอบในเวลาที่คงที่และปล่อยให้รูที่เรียกคืนได้และมีการปิดกั้นบล็อกหากพวกเขาว่างเปล่าและว่างเปล่าสำหรับอินสแตนซ์ที่ไม่เปลี่ยนรูป แต่โดยพื้นฐานแล้วในการปรับเปลี่ยนโครงสร้างคุณปรับเปลี่ยนเวอร์ชั่น "ชั่วคราว" และยอมรับการเปลี่ยนแปลงที่คุณทำไว้กับอะตอมเพื่อรับสำเนาที่ไม่เปลี่ยนรูปแบบใหม่ที่ไม่ได้สัมผัสสิ่งเก่าโดยมีเวอร์ชันใหม่สร้างเฉพาะสำเนาใหม่ของบล็อกที่ จะต้องไม่ซ้ำกันในขณะที่คัดลอกและอ้างอิงตื้น ๆ นับคนอื่น ๆ
แต่ผมไม่ได้พบว่ามันว่ามีประโยชน์สำหรับวัตถุประสงค์หลายเธรด ท้ายที่สุดก็ยังมีปัญหาทางความคิดที่ว่าระบบฟิสิกส์ใช้ฟิสิกส์พร้อมกันในขณะที่ผู้เล่นพยายามเคลื่อนย้ายองค์ประกอบรอบโลก คุณทำสำเนาข้อมูลแปลงที่แปลงไม่ได้อันไหนเครื่องเล่นแปลงหรือแปลงระบบฟิสิกส์? ดังนั้นฉันจึงไม่พบวิธีที่ดีและเรียบง่ายสำหรับปัญหาแนวคิดพื้นฐานนี้ยกเว้นมีโครงสร้างข้อมูลที่ไม่แน่นอนซึ่งเพิ่งล็อคในวิธีที่ชาญฉลาดและกีดกันการทับซ้อนกันอ่านและเขียนลงในส่วนเดียวกันของบัฟเฟอร์เพื่อหลีกเลี่ยงเธรดที่ค้างอยู่ นั่นคือสิ่งที่ John Carmack ดูเหมือนจะรู้วิธีแก้ไขในเกมของเขา อย่างน้อยเขาก็พูดถึงมันเหมือนว่าเขาเกือบจะได้เห็นวิธีแก้ปัญหาโดยไม่ต้องเปิดรถเวิร์ม ฉันไม่ได้ไปไกลถึงเขาในเรื่องนั้น ทั้งหมดที่ฉันเห็นคือคำถามการออกแบบที่ไม่มีที่สิ้นสุดหากฉันพยายามทำขนานทุกอย่างรอบ ๆ สิ่งที่ไม่เปลี่ยนแปลง ฉันหวังว่าฉันจะใช้เวลาหนึ่งวันเพื่อเก็บสมองของเขาเพราะความพยายามส่วนใหญ่ของฉันเริ่มต้นจากความคิดที่เขาขว้างออกไป
อย่างไรก็ตามฉันพบมูลค่ามหาศาลของโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบนี้ในพื้นที่อื่น ๆ ฉันยังใช้ตอนนี้เพื่อจัดเก็บรูปภาพที่แปลกจริง ๆ และทำการเข้าถึงแบบสุ่มต้องมีคำแนะนำเพิ่มเติม (เลื่อนขวาและ bitwise and
พร้อมกับเลเยอร์ของตัวชี้ทิศทาง) แต่ฉันจะครอบคลุมประโยชน์ด้านล่าง
เลิกทำการระบบ
หนึ่งในสถานที่ที่ฉันพบว่าได้รับประโยชน์มากที่สุดจากเรื่องนี้คือระบบเลิกทำ เลิกทำรหัสระบบเคยเป็นหนึ่งในสิ่งที่ผิดพลาดได้ง่ายที่สุดในพื้นที่ของฉัน (อุตสาหกรรมภาพ FX) และไม่เพียง แต่ในผลิตภัณฑ์ที่ฉันทำงาน แต่ในผลิตภัณฑ์คู่แข่ง (ระบบเลิกทำของพวกเขายังเป็นขุย) เพราะมีหลาย ๆ ชนิดของข้อมูลที่ต้องกังวลเกี่ยวกับการเลิกทำและทำซ้ำอย่างถูกต้อง (ระบบคุณสมบัติการเปลี่ยนแปลงข้อมูลตาข่ายการเปลี่ยนแปลง shader ที่ไม่ได้อิงตามคุณสมบัติเช่นการแลกเปลี่ยนกับการเปลี่ยนแปลงลำดับชั้นของฉากเช่นการเปลี่ยนผู้ปกครองของเด็กการเปลี่ยนแปลงภาพ / พื้นผิว ฯลฯ ฯลฯ ฯลฯ )
ดังนั้นจำนวนรหัสที่ต้องเลิกทำจึงมีขนาดใหญ่มากมักจะเป็นการแข่งขันกับจำนวนของรหัสที่ใช้ระบบซึ่งระบบเลิกทำต้องบันทึกการเปลี่ยนแปลงสถานะ ด้วยการพึ่งพาโครงสร้างข้อมูลนี้ฉันสามารถทำให้ระบบเลิกทำได้เป็นอย่างนี้:
on user operation:
copy entire application state to undo entry
perform operation
on undo/redo:
swap application state with undo entry
โดยปกติโค้ดข้างต้นจะไม่มีประสิทธิภาพมหาศาลเมื่อข้อมูลฉากของคุณครอบคลุมกิกะไบต์เพื่อคัดลอกทั้งหมด แต่โครงสร้างข้อมูลนี้จะคัดลอกเฉพาะสิ่งที่ไม่ได้เปลี่ยนไปเท่านั้นและทำให้ราคาถูกเพียงพอที่จะเก็บสำเนาที่ไม่เปลี่ยนรูปของสถานะแอปพลิเคชันทั้งหมด ดังนั้นตอนนี้ฉันสามารถใช้ระบบเลิกทำอย่างง่ายดายเช่นเดียวกับรหัสด้านบนและเพียงแค่มุ่งเน้นการใช้โครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบนี้เพื่อทำการคัดลอกส่วนที่ไม่เปลี่ยนแปลงของสถานะแอปพลิเคชันที่ถูกกว่าและถูกกว่า ตั้งแต่ฉันเริ่มใช้โครงสร้างข้อมูลนี้โครงการส่วนบุคคลทั้งหมดของฉันมีระบบเลิกทำที่ใช้รูปแบบเรียบง่ายนี้
ตอนนี้ยังมีค่าใช้จ่ายอยู่ที่นี่ ครั้งสุดท้ายที่ฉันวัดมันประมาณ 10 กิโลไบต์เพียงเพื่อคัดลอกสถานะแอปพลิเคชันทั้งหมดโดยไม่ทำการเปลี่ยนแปลงใด ๆ (ซึ่งเป็นอิสระจากความซับซ้อนของฉากเนื่องจากฉากถูกจัดเรียงในลำดับชั้นดังนั้นหากไม่มีอะไรใต้รากที่เปลี่ยนแปลงเพียงราก ถูกคัดลอกตื้นโดยไม่ต้องลงไปในเด็ก) ที่อยู่ห่างจาก 0 ไบต์เป็นสิ่งจำเป็นสำหรับระบบเลิกทำที่จัดเก็บเดลตาเท่านั้น แต่ที่ 10 กิโลไบต์ของค่าใช้จ่ายในการเลิกทำต่อการดำเนินการนั้นยังคงเป็นเพียงเมกะไบต์ต่อการดำเนินงานของผู้ใช้ 100 ราย รวมทั้งฉันยังสามารถสควอชที่ลงต่อไปในอนาคตหากจำเป็น
ข้อยกเว้นความปลอดภัย
ข้อยกเว้นด้านความปลอดภัยด้วยแอปพลิเคชันที่ซับซ้อนนั้นไม่สำคัญ อย่างไรก็ตามเมื่อสถานะแอปพลิเคชันของคุณไม่เปลี่ยนรูปและคุณเพียงแค่ใช้วัตถุชั่วคราวในการพยายามทำธุรกรรมการเปลี่ยนแปลงของอะตอมมันก็มีข้อยกเว้นที่ปลอดภัยโดยเนื้อแท้เพราะถ้าส่วนใดส่วนหนึ่งของรหัสพ่นรหัสชั่วคราวจะถูกโยนทิ้งไป . ดังนั้นสิ่งเล็กน้อยที่สุดสิ่งหนึ่งที่ยากที่สุดที่ฉันพบอยู่เสมอเพื่อให้ถูกต้องในรหัสฐาน C ++ ที่ซับซ้อน
บ่อยครั้งที่คนจำนวนมากมักใช้ทรัพยากรที่สอดคล้องกับ RAII ใน C ++ และคิดว่าเพียงพอที่จะเป็นข้อยกเว้นที่ปลอดภัย บ่อยครั้งที่มันไม่ได้เป็นเพราะฟังก์ชั่นโดยทั่วไปสามารถทำให้เกิดผลข้างเคียงไปยังรัฐนอกเหนือจากที่อยู่ในขอบเขต โดยทั่วไปคุณต้องเริ่มจัดการกับขอบเขตของขอบเขตและตรรกะการย้อนกลับที่ซับซ้อนในกรณีเหล่านั้น โครงสร้างข้อมูลนี้สร้างขึ้นบ่อยครั้งฉันไม่จำเป็นต้องกังวลเพราะฟังก์ชั่นไม่ได้ก่อให้เกิดผลข้างเคียง พวกเขากำลังส่งคืนสำเนาของสถานะแอปพลิเคชันที่เปลี่ยนรูปไม่ได้แทนการเปลี่ยนสถานะของแอปพลิเคชัน
การแก้ไขแบบไม่ทำลาย
การแก้ไขแบบไม่ทำลายนั้นเป็นการทำเลเยอร์ / สแต็ค / เชื่อมต่อเข้าด้วยกันโดยไม่ต้องสัมผัสข้อมูลของผู้ใช้เดิม (เพียงแค่ข้อมูลอินพุตและข้อมูลเอาต์พุตโดยไม่ต้องสัมผัสอินพุต) โดยทั่วไปแล้วมันเป็นเรื่องเล็กน้อยที่จะนำไปใช้กับแอปพลิเคชั่นภาพอย่างง่ายเช่น Photoshop และอาจไม่ได้รับประโยชน์มากนักจากโครงสร้างข้อมูลนี้เนื่องจากการดำเนินการหลายอย่างอาจต้องการเปลี่ยนทุกพิกเซลของภาพทั้งหมด
อย่างไรก็ตามด้วยการแก้ไขแบบ non-destructive mesh การดำเนินการจำนวนมากมักต้องการแปลงเฉพาะส่วนของ mesh การดำเนินการหนึ่งอาจต้องการย้ายจุดยอดที่นี่ อีกอันอาจต้องการแบ่งรูปหลายเหลี่ยมที่นั่น ที่นี่โครงสร้างข้อมูลที่เปลี่ยนไม่ได้ช่วยให้ตันในการหลีกเลี่ยงความต้องการที่จะต้องทำสำเนาทั้งหมดของตาข่ายทั้งหมดเพียงเพื่อส่งกลับตาข่ายรุ่นใหม่ที่มีการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ
การลดผลข้างเคียง
ด้วยโครงสร้างเหล่านี้อยู่ในมือมันทำให้การเขียนฟังก์ชั่นที่ง่ายที่สุดลดผลข้างเคียงโดยไม่ทำให้เกิดผลเสียอย่างมาก ฉันพบว่าตัวเองกำลังเขียนฟังก์ชั่นมากขึ้นเรื่อย ๆ ซึ่งเพียงแค่คืนค่าโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบทั้งหมดตามคุณค่าในวันนี้โดยไม่มีผลข้างเคียงที่เกิดขึ้นแม้ว่าจะดูสิ้นเปลืองก็ตาม
ตัวอย่างเช่นโดยทั่วไปสิ่งล่อใจที่จะแปลงกลุ่มตำแหน่งอาจเป็นการยอมรับเมทริกซ์และรายการของวัตถุและแปลงค่าเหล่านั้นในแบบที่ไม่แน่นอน วันนี้ฉันพบว่าตัวเองเพิ่งกลับรายการของวัตถุใหม่
เมื่อคุณมีฟังก์ชั่นอื่น ๆ เพิ่มเติมเช่นนี้ในระบบของคุณที่ไม่มีผลข้างเคียงแน่นอนทำให้ง่ายขึ้นที่จะให้เหตุผลเกี่ยวกับความถูกต้องของมันและทดสอบความถูกต้องของมัน
ประโยชน์ของการถ่ายเอกสารราคาถูก
ดังนั้นต่อไปนี้เป็นพื้นที่ที่ฉันพบว่ามีการใช้ประโยชน์จากโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปแบบมากที่สุด (หรือโครงสร้างข้อมูลถาวร) ฉันยังได้รับ overzealous เล็กน้อยในตอนแรกและสร้างแผนภูมิที่ไม่เปลี่ยนรูปแบบและรายการเชื่อมโยงที่ไม่เปลี่ยนรูปแบบและตารางแฮชไม่เปลี่ยนรูป แต่เมื่อเวลาผ่านไปฉันไม่ค่อยพบการใช้งานมากเท่าที่ควร ฉันพบว่าการใช้งานคอนเทนเนอร์อาเรย์ที่ไม่เปลี่ยนรูปแบบเหมือนกันมากที่สุดในแผนภาพข้างต้น
ฉันยังมีโค้ดจำนวนมากที่ทำงานกับ mutables (พบว่ามันเป็นความจำเป็นในทางปฏิบัติอย่างน้อยสำหรับโค้ดระดับต่ำ) แต่สถานะแอปพลิเคชันหลักคือลำดับชั้นที่ไม่เปลี่ยนรูปเจาะลึกลงไปจากฉากที่ไม่เปลี่ยนรูป ส่วนประกอบที่ถูกกว่าบางส่วนยังคงถูกคัดลอกไว้อย่างสมบูรณ์ แต่ส่วนประกอบที่มีราคาแพงที่สุดเช่นตาข่ายและรูปภาพใช้โครงสร้างที่เปลี่ยนแปลงไม่ได้เพื่อให้สามารถคัดลอกชิ้นส่วนราคาถูกเหล่านั้นได้เพียงบางส่วนเท่านั้นที่จำเป็นต้องเปลี่ยน
ConcurrentModificationException
ซึ่งมักจะเกิดจากเธรดเดียวกันการกลายคอลเลกชันในเธรดเดียวกันในเนื้อความของforeach
วนรอบคอลเลกชันเดียวกัน