ในการเขียนโปรแกรมเชิงปฏิบัติการมีโครงสร้างข้อมูลส่วนใหญ่ที่ไม่เปลี่ยนรูปแบบนั้นต้องการการใช้งานหน่วยความจำมากขึ้นหรือไม่?


63

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


7
มันอาจหมายถึงว่า แต่โครงสร้างข้อมูลที่เปลี่ยนแปลงไม่ได้ส่วนใหญ่จะใช้ข้อมูลที่อยู่ภายใต้การเปลี่ยนแปลงนั้น Eric Lippert มีบล็อกที่ยอดเยี่ยมเกี่ยวกับการเปลี่ยนแปลงใน C #
Oded

3
ฉันจะดูที่โครงสร้างข้อมูลที่ทำงานได้อย่างหมดจดมันเป็นหนังสือที่ยอดเยี่ยมที่เขียนโดยคนเดียวกันที่เขียนไลบรารีคอนเทนเนอร์ Haskell ส่วนใหญ่ (แม้ว่าหนังสือเล่มนี้จะเป็น SML เป็นหลัก)
jozefg

1
คำตอบนี้เกี่ยวข้องกับเวลาทำงานแทนการใช้หน่วยความจำอาจน่าสนใจสำหรับคุณเช่น: stackoverflow.com/questions/1990464/…
9000

1
คุณอาจพบว่าสิ่งนี้น่าสนใจ: en.wikipedia.org/wiki/Static_single_assignment_form
Sean McSomething

คำตอบ:


35

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


24

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

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

list2 = prepend(42, list1) // list2 is now a list that contains 42 followed
                           // by the elements of list1. list1 is unchanged

นี่คือความต้องการหน่วยความจำเพิ่มเติมเป็นค่าคงที่ - prependเพื่อเป็นค่าใช้จ่ายของการเรียกรันไทม์ ทำไม? เพราะprependเพียงแค่สร้างเซลล์ใหม่ซึ่งมี42ทั้งศีรษะและlist1หาง ไม่จำเป็นต้องคัดลอกหรือวนซ้ำlist2เพื่อให้บรรลุเป้าหมายนี้ นั่นคือยกเว้นสำหรับหน่วยความจำที่จำเป็นต้องใช้ในการจัดเก็บ42, list2reuses list1หน่วยความจำเดียวกันที่ถูกใช้โดย เนื่องจากทั้งสองรายการไม่มีการเปลี่ยนแปลงการแบ่งปันนี้จึงปลอดภัยอย่างสมบูรณ์แบบ

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

สำหรับอาร์เรย์สถานการณ์จะแตกต่างกันเล็กน้อย นั่นเป็นเหตุผลว่าทำไมในหลาย ๆ ภาษาของ FP จึงไม่ได้ใช้อาร์เรย์ อย่างไรก็ตามหากคุณทำสิ่งที่ชอบarr2 = map(f, arr1)และarr1ไม่เคยใช้อีกหลังจากบรรทัดนี้เครื่องมือเพิ่มประสิทธิภาพสมาร์ทสามารถสร้างรหัสที่กลายพันธุ์arr1แทนที่จะสร้างอาร์เรย์ใหม่ (โดยไม่ส่งผลต่อพฤติกรรมของโปรแกรม) ในกรณีนั้นการแสดงจะเป็นภาษาที่จำเป็นแน่นอน


1
จากความสนใจการใช้ภาษาใดที่จะนำพื้นที่มาใช้ใหม่ตามที่อธิบายไว้ใกล้จะจบ

@delnan ที่มหาวิทยาลัยของฉันมีภาษาวิจัยที่เรียกว่า Qube ซึ่งทำเช่นนั้น แต่ฉันก็ไม่รู้ว่ามีภาษาที่ใช้แล้วทำแบบนี้หรือเปล่า อย่างไรก็ตามฟิวชั่นของ Haskell สามารถบรรลุผลเช่นเดียวกันในหลาย ๆ กรณี
sepp2k

7

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

ภาษาที่แตกต่างกันมีวิธีจัดการกับสิ่งนี้ที่แตกต่างกันและมีเทคนิคเล็กน้อยที่คนส่วนใหญ่ใช้

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

อีกอันหนึ่งคือการเลือกโครงสร้างข้อมูลชนิดต่าง ๆ โดยที่ arrays เป็นโครงสร้างข้อมูลแบบ go-to list ในภาษาที่จำเป็น (โดยทั่วไปจะถูกห่อไว้ในคอนเทนเนอร์การจัดสรรคืนแบบไดนามิกบางอย่างเช่นstd::vectorใน C ++) ภาษาที่ใช้งานมักจะชอบรายการที่เชื่อมโยง ด้วยรายการที่เชื่อมโยงการดำเนินการเสริม ('ข้อเสีย') สามารถนำรายการที่มีอยู่กลับมาใช้เป็นส่วนท้ายของรายการใหม่ดังนั้นสิ่งที่ได้รับการจัดสรรจริงๆคือส่วนหัวของรายการใหม่ กลยุทธ์ที่คล้ายกันมีอยู่สำหรับโครงสร้างข้อมูลประเภทอื่น - ชุดต้นไม้คุณตั้งชื่อมัน

แล้วมีการประเมินผลที่ขี้เกียจàà Haskell แนวคิดคือโครงสร้างข้อมูลที่คุณสร้างไม่ได้ถูกสร้างขึ้นอย่างสมบูรณ์ทันที แต่จะเก็บเป็น "thunks" แทน (คุณสามารถคิดว่านี่เป็นสูตรสำหรับสร้างมูลค่าเมื่อจำเป็น) เฉพาะเมื่อจำเป็นต้องใช้ค่า thunk จะได้รับการขยายเป็นค่าจริง ซึ่งหมายความว่าการจัดสรรหน่วยความจำสามารถถูกเลื่อนออกไปจนกว่าการประเมินผลจะมีความจำเป็นและ ณ จุดนั้นสามารถรวมกันหลายหน่วยในการจัดสรรหน่วยความจำเดียว


ว้าวหนึ่งคำตอบเล็ก ๆ และข้อมูล / ความเข้าใจมากมาย ขอบคุณ :)
Gerry

3

ฉันเพียงรู้เล็ก ๆ น้อย ๆ เกี่ยวกับ Clojure และเป็นโครงสร้างข้อมูลไม่เปลี่ยนรูป

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

กราฟิกเราสามารถแสดงสิ่งนี้:

(def my-list '(1 2 3))

    +---+      +---+      +---+
    | 1 | ---> | 2 | ---> | 3 |
    +---+      +---+      +---+

(def new-list (conj my-list 0))

              +-----------------------------+
    +---+     | +---+      +---+      +---+ |
    | 0 | --->| | 1 | ---> | 2 | ---> | 3 | |
    +---+     | +---+      +---+      +---+ |
              +-----------------------------+

2

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

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

สำหรับรายละเอียดเพิ่มเติมโปรดดูเช่นหน้าแรกสะอาดและบทความวิกิพีเดียนี้

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