การกระจายตัวของหน่วยความจำคืออะไร?


203

ฉันได้ยินคำว่า "การกระจายตัวของหน่วยความจำ" ใช้สองสามครั้งในบริบทของการจัดสรรหน่วยความจำแบบไดนามิก C ++ ฉันพบคำถามบางอย่างเกี่ยวกับวิธีจัดการกับการแตกแฟรกเมนต์ของหน่วยความจำ แต่ไม่พบคำถามโดยตรงที่เกี่ยวข้องกับมัน ดังนั้น:

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

นอกจากนี้:

  • ฉันได้ยินมาว่าการจัดสรรแบบไดนามิกมากสามารถเพิ่มการกระจายตัวของหน่วยความจำ มันเป็นเรื่องจริงเหรอ? ในบริบทของ C ++ ฉันเข้าใจทุกคอนเทนเนอร์มาตรฐาน (std :: string, std :: vector ฯลฯ ) ใช้การจัดสรรหน่วยความจำแบบไดนามิก หากสิ่งเหล่านี้ถูกใช้ตลอดโปรแกรม (โดยเฉพาะ std :: string) การกระจายตัวของหน่วยความจำมีแนวโน้มที่จะเป็นปัญหาหรือไม่?
  • การแตกแฟรกเมนต์หน่วยความจำจะถูกจัดการได้อย่างไรในแอปพลิเคชัน STL-heavy

1
คำตอบที่ดีมากมายขอบคุณทุกคน!
AshleysBrain

4
มีคำตอบที่ยอดเยี่ยมอยู่แล้ว แต่นี่คือภาพบางส่วนจากแอปพลิเคชันจริง (Firefox) ที่การกระจายตัวของหน่วยความจำเป็นปัญหาใหญ่: blog.pavlov.net/2007/11/10/memory-fragmentation
Marius Gedminas

2
@MariusGedminas ลิงก์ใช้งานไม่ได้อีกต่อไปนั่นคือเหตุผลว่าทำไมจึงเป็นเรื่องสำคัญที่จะต้องให้ข้อมูลสรุปโดยย่อพร้อมกับลิงค์หรือตอบคำถามพร้อมสรุปพร้อมลิงก์
katta

แน่นอน แต่มันผ่านมาครึ่งทศวรรษแล้ว
rsethc

3
ด้านล่างนี้เป็นตำแหน่งที่ปรับปรุงแล้วสำหรับลิงก์ที่โพสต์โดย Marius: pavlovdotnet.wordpress.com/2007/11/10/memory-fragmentation
TheGameiswar

คำตอบ:


312

ลองนึกภาพว่าคุณมีหน่วยความจำว่างขนาดใหญ่ (32 ไบต์):

----------------------------------
|                                |
----------------------------------

ตอนนี้จัดสรรบางส่วน (การจัดสรร 5 ครั้ง):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

ทีนี้ให้จัดสรรสี่ครั้งแรกฟรี แต่ไม่ใช่การจัดสรรที่ห้า:

----------------------------------
|              eeee              |
----------------------------------

ตอนนี้พยายามจัดสรร 16 ไบต์ อุ๊ปส์ฉันทำไม่ได้แม้ว่าจะมีเกือบสองเท่าที่ฟรีมาก

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

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

ในขณะที่หน่วยความจำเสมือน (มีขนาดใหญ่กว่า) อาจมีลักษณะเช่นนี้:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

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

กลยุทธ์เพื่อป้องกันการกระจายตัวของหน่วยความจำใน C ++ ทำงานโดยการจัดสรรวัตถุจากพื้นที่ต่าง ๆ ตามขนาดและ / หรืออายุการใช้งานที่คาดไว้ ดังนั้นหากคุณจะสร้างวัตถุจำนวนมากและทำลายพวกมันทั้งหมดในภายหลังให้จัดสรรพวกมันจากพูลหน่วยความจำ การจัดสรรอื่น ๆ ที่คุณทำระหว่างการจัดสรรจะไม่ได้มาจากพูลดังนั้นจะไม่อยู่ระหว่างการจัดสรรหน่วยความจำดังนั้นหน่วยความจำจะไม่ได้รับการแยกส่วน

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

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


1
ดังนั้นตัวละครแต่ละตัวคือไบต์? ซึ่งจะทำให้ "การขยายขนาดใหญ่" ของคุณ == 32 ไบต์ (ฉันเดา - ไม่นับ) :) เป็นตัวอย่างที่ดี แต่การกล่าวถึงหน่วยก่อนบรรทัดสุดท้ายจะเป็นประโยชน์ :)
jalf

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

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

1
"ไลบรารีมาตรฐานไม่เลวร้ายไปกว่าสิ่งอื่นใดที่จัดสรรความจำ" นั่นจะดีถ้าเป็นจริง แต่การใช้งานของแม่แบบ C ++ มาตรฐานเช่นสตริงและเวกเตอร์สามารถมีพฤติกรรมที่ไม่พึงประสงค์สูงเมื่อปรับขนาด ตัวอย่างเช่นใน Visual Studio รุ่นเก่า std :: string จะถูกปรับขนาดโดยทั่วไปโดย realloc 1.5 * current_size (เป็น 8 ไบต์ที่ใกล้ที่สุด) ดังนั้นถ้าคุณต่อท้ายสตริงคุณสามารถกำจัดฮีปได้ง่ายมากโดยเฉพาะในระบบฝังตัว การป้องกันที่ดีที่สุดคือการจองพื้นที่ที่คุณคาดว่าจะใช้เพื่อหลีกเลี่ยงการ reallocs ที่ซ่อนอยู่
locka

1
@ du369: หน่วยความจำเสมือนไม่ได้แยกส่วนอะไรที่ไม่ดีเท่าที่เป็นอยู่จริง ffffffffffffffffเป็นการจัดสรรที่ต่อเนื่องกันในหน่วยความจำเสมือน แต่ไม่มีการจัดสรรต่อเนื่องกันในหน่วยความจำกายภาพ หากคุณต้องการดูว่ามันมีการแยกส่วนเท่า ๆ กัน แต่พื้นที่เสมือนมีขนาดใหญ่กว่ามากคุณสามารถดูวิธีนั้นได้อย่างอิสระ จุดปฏิบัติที่สำคัญคือการใช้พื้นที่ที่อยู่เสมือนจำนวนมากมักเพียงพอที่จะสามารถแยกส่วนได้ดังนั้นจึงช่วยได้ทุกเมื่อที่อนุญาตให้ทำการจัดสรร 16 ไบต์
Steve Jessop

73

การกระจายตัวของหน่วยความจำคืออะไร?

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

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

ลองจินตนาการว่ากำแพงเป็นหน่วยความจำ (ฮีป) ของคุณและรูปภาพเป็นวัตถุ .. นั่นคือการกระจายตัวของหน่วยความจำ ..

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

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

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

วิธีทั่วไปที่ดีในการจัดการกับการแตกแฟรกเมนต์คืออะไร

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


10
+1 ฉันเพิ่งลบคำตอบที่เสนอเนื่องจากคำอุปมา "รูปภาพบนผนัง" ของคุณนั้นเป็นคำที่ดีชัดเจนชัดเจน
ctacke

ฉันต้องการมากขึ้นถ้าคุณเน้นความจริงที่ว่ารูปภาพจะต้องมีขนาดแตกต่างกัน มิฉะนั้นจะไม่มีการแยกส่วนเกิดขึ้น
Björn Pollex

1
น่าสนใจวันนี้ฐานข้อมูลหน่วยความจำหลักค่อนข้างใช้งานได้จริง ในบริบทนี้มันคุ้มค่าที่จะสังเกตว่าสำหรับ HDDs การอ่านบรรทัดต่อเนื่องจาก RAM นั้นเร็วกว่าหากมีการแยกส่วนข้อมูล
Björn Pollex

1
การเปรียบเทียบภาพที่ดีกับภาพบนผนัง แต่หน่วยความจำหลักไม่ใช่สองมิติ! ยังคงเป็นคำตอบที่ดีขอบคุณ
AshleysBrain

24

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

สมมติว่าเป็นตัวอย่างของเล่นง่ายๆที่คุณมีหน่วยความจำสิบไบต์:

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

ทีนี้เรามาจัดสรรบล็อคสามไบต์สามชื่อ A, B และ C:

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

ตอนนี้ยกเลิกการจัดสรรบล็อก B:

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

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

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

ในตัวอย่างนี้ 3 ไบต์ไม่ใช่ของเสียจำนวนมาก แต่ให้พิจารณากรณีทางพยาธิวิทยามากขึ้นโดยการจัดสรร AA สองไบต์เป็นสองตัวอย่างเช่นสิบเมกะไบต์ในหน่วยความจำและคุณต้องจัดสรรบล็อกขนาด 10 เมกะไบต์ + 1 ไบต์ คุณต้องขอให้ระบบปฏิบัติการของหน่วยความจำเสมือนมากกว่าสิบเมกะไบต์ทำเช่นนั้นแม้ว่าคุณจะมีเพียงหนึ่งไบต์ที่มีพื้นที่ว่างเพียงพอแล้วก็ตาม

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

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

แม้ว่าแน่นอนว่าคอนเทนเนอร์ STL จะไม่รวมหน่วยความจำระหว่างกันดังนั้นหากคุณกำลังจะสร้างตู้คอนเทนเนอร์ขนาดเล็กจำนวนมาก (แทนที่จะเป็นตู้คอนเทนเนอร์เล็ก ๆ ที่ได้รับการปรับขนาดบ่อยครั้ง) คุณอาจต้องกังวลเกี่ยวกับตัวคุณเองด้วยการป้องกันการกระจายตัว จะมีวัตถุขนาดเล็กที่สร้างขึ้นบ่อย ๆ STL หรือไม่


14
  • การกระจายตัวของหน่วยความจำคืออะไร?

การแตกแฟรกเมนต์หน่วยความจำเป็นปัญหาของหน่วยความจำที่ใช้ไม่ได้แม้ว่าจะมีอยู่ในทางทฤษฎี การแตกแฟรกเมนต์มีสองชนิด: การแตกแฟรกเมนต์ภายในคือหน่วยความจำที่จัดสรร แต่ไม่สามารถใช้งานได้ (เช่นเมื่อหน่วยความจำถูกจัดสรรใน 8 ไบต์ชิ้น แต่โปรแกรมซ้ำ ๆ ทำแบบเดี่ยว ๆ เมื่อต้องการเพียง 4 ไบต์) การแตกแฟรกเมนต์ภายนอกเป็นปัญหาของหน่วยความจำอิสระที่ถูกแบ่งออกเป็นชิ้นเล็ก ๆ จำนวนมากเพื่อให้การร้องขอการจัดสรรขนาดใหญ่ไม่สามารถพบได้แม้ว่าจะมีหน่วยความจำว่างโดยรวมเพียงพอ

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

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

  • วิธีทั่วไปที่ดีในการจัดการกับการแตกแฟรกเมนต์คืออะไร

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

  • Paul R. Wilson, Mark S. Johnstone, Michael Neely และ David Boles การจัดสรรพื้นที่เก็บข้อมูลแบบไดนามิก: การสำรวจและการทบทวนที่สำคัญ ในการดำเนินการของการประชุมเชิงปฏิบัติการระหว่างประเทศเกี่ยวกับการจัดการหน่วยความจำ 1995, Springer Verlag LNCS, 1995
  • Mark S.Johnstone, Paul R. Wilson ปัญหาการกระจายตัวของหน่วยความจำ: แก้ไขได้? ในประกาศ ACM SIG-PLAN เล่มที่ 34 ฉบับที่ 3 หน้า 26-36, 1999
  • MR Garey, RL Graham และ JD Ullman การวิเคราะห์ขั้นตอนวิธีการจัดสรรหน่วยความจำที่แย่ที่สุด ในการประชุมวิชาการ ACM ประจำปีครั้งที่สี่ในทฤษฎีการคำนวณ, 1972

9

อัปเดต:
Google TCMalloc: การแคชเธรด Malloc
พบว่ามันค่อนข้างดีในการจัดการการแยกส่วนในกระบวนการที่ใช้เวลานาน


ฉันได้พัฒนาแอปพลิเคชันเซิร์ฟเวอร์ที่มีปัญหาเกี่ยวกับการกระจายตัวของหน่วยความจำบน HP-UX 11.23 / 11.31 ia64

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

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

วิธีการปรับปรุงสถานการณ์ของฉันคือสิ่งนี้ หลังจากฉันทำการวิเคราะห์บางอย่างกับ HP-UX gdb ฉันเห็นว่าปัญหาหน่วยความจำเกิดจากข้อเท็จจริงที่ว่าฉันใช้std::vectorสำหรับเก็บข้อมูลบางประเภทจากฐานข้อมูล std::vectorต้องการให้ข้อมูลของมันต้องถูกเก็บไว้ในบล็อกเดียว std::vectorฉันได้ไม่กี่ภาชนะซึ่งเป็นไปตาม ภาชนะเหล่านี้ถูกสร้างขึ้นใหม่อย่างสม่ำเสมอ มักจะมีสถานการณ์เมื่อมีการเพิ่มระเบียนใหม่ลงในฐานข้อมูลและหลังจากนั้นสร้างคอนเทนเนอร์ใหม่ และเนื่องจากคอนเทนเนอร์ที่สร้างขึ้นใหม่มีขนาดใหญ่กว่าจึงไม่เหมาะสมกับบล็อกของหน่วยความจำว่างและรันไทม์จึงขอบล็อกที่ใหญ่กว่าใหม่จากระบบปฏิบัติการ เป็นผลแม้ว่าจะไม่มีหน่วยความจำรั่วปริมาณการใช้หน่วยความจำของกระบวนการเพิ่มขึ้น ฉันปรับปรุงสถานการณ์เมื่อฉันเปลี่ยนคอนเทนเนอร์ แทนที่จะstd::vectorเริ่มใช้std::deque ซึ่งมีวิธีการจัดสรรหน่วยความจำสำหรับข้อมูลที่แตกต่างกัน

ฉันรู้ว่าวิธีหนึ่งในการหลีกเลี่ยงการกระจายตัวของหน่วยความจำใน HP-UX คือการใช้ Small Block Allocator หรือใช้ MallocNextGen บน RedHat Linux ตัวจัดสรรเริ่มต้นดูเหมือนว่าจะจัดการการจัดสรรบล็อกขนาดเล็กได้เป็นอย่างดี ใน Windows มีLow-fragmentation Heapและมันอยู่ที่ปัญหาของการจัดสรรขนาดเล็กจำนวนมาก

ความเข้าใจของฉันคือว่าในใบสมัคร STL- หนักคุณต้องระบุปัญหาก่อน จัดสรรหน่วยความจำ (เหมือนใน libc) จริงจัดการกับปัญหาที่เกิดขึ้นของจำนวนมากของการจัดสรรขนาดเล็กซึ่งเป็นเรื่องปกติสำหรับการstd::string(ตัวอย่างเช่นในการประยุกต์ใช้เซิร์ฟเวอร์ของฉันมีจำนวนมากของสตริง STL แต่ที่ผมเห็นจากการทำงานของinfo heapพวกเขาจะไม่ก่อให้เกิดปัญหาใด ๆ ) ความประทับใจของฉันคือคุณต้องหลีกเลี่ยงการจัดสรรบ่อยครั้งมาก น่าเสียดายที่มีบางสถานการณ์ที่คุณไม่สามารถหลีกเลี่ยงได้และต้องเปลี่ยนรหัสของคุณ std::dequeขณะที่ผมบอกว่าในกรณีของฉันฉันปรับปรุงสถานการณ์เมื่อเปลี่ยนไป หากคุณระบุการแยกส่วนความจำของคุณอาจเป็นไปได้ที่จะพูดคุยเกี่ยวกับมันอย่างแม่นยำมากขึ้น


6

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

obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)

ตอนนี้เมื่อวางobj2จำหน่ายคุณมีหน่วยความจำที่ไม่ได้ใช้ 120kb แต่คุณไม่สามารถจัดสรรบล็อกเต็มรูปแบบ 120kb ได้เนื่องจากหน่วยความจำมีการแยกส่วน

เทคนิคทั่วไปเพื่อหลีกเลี่ยงผลกระทบที่มีบัฟเฟอร์แหวนและสระว่ายน้ำวัตถุ ในบริบทของ STL วิธีการต่าง ๆstd::vector::reserve()สามารถช่วยได้


6

คำตอบโดยละเอียดมากเกี่ยวกับการกระจายตัวของหน่วยความจำสามารถพบได้ที่นี่

http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/

นี่คือสุดยอดของการตอบคำถามการกระจายตัวของหน่วยความจำ 11 ปีที่ฉันได้มอบให้กับคนที่ถามฉันเกี่ยวกับการกระจายตัวของหน่วยความจำที่ softwareverify.com


3

การกระจายตัวของหน่วยความจำคืออะไร?

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

แหล่งที่มาของการแตกแฟรกเมนต์อื่นที่หลีกเลี่ยงไม่ได้ แต่มีปัญหาน้อยกว่าคือในสถาปัตยกรรมส่วนใหญ่ที่อยู่หน่วยความจำจะต้องสอดคล้องกับขอบเขตของไบต์ 2, 4, 8 และอื่น ๆ (เช่นที่อยู่จะต้องเป็นทวีคูณของ 2, 4, 8 เป็นต้น) แม้ว่าคุณจะมีโครงสร้างเช่นที่มี 3 charฟิลด์โครงสร้างของคุณอาจมีขนาด 12 แทนที่จะเป็น 3 เนื่องจากความจริงที่ว่าแต่ละเขตข้อมูลถูกจัดชิดกับขอบเขต 4 ไบต์

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

คำตอบที่ชัดเจนคือคุณได้รับการยกเว้นหน่วยความจำ

เห็นได้ชัดว่าไม่มีวิธีพกพาที่ดีในการตรวจสอบการกระจายตัวของหน่วยความจำในแอพ C ++ ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม

วิธีทั่วไปที่ดีในการจัดการกับการแตกแฟรกเมนต์คืออะไร

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

ตัวจัดสรรแบบกำหนดเองอาจช่วยได้ด้วยการจัดการการจัดสรรวัตถุขนาดเล็กในหน่วยความจำขนาดใหญ่กว่าและนำสล็อตว่างกลับมาใช้ใหม่ภายในก้อนอันนั้น


3

นี้เป็นรุ่นที่ง่ายสุดสำหรับหุ่น

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

หากวัตถุที่ไม่ได้อยู่ในตอนท้ายของส่วนที่ใช้ในหน่วยความจำถูกลบไปหมายความว่าวัตถุนี้อยู่ระหว่างวัตถุอื่น 2 วัตถุมันจะสร้าง "หลุม"

นี่คือสิ่งที่เรียกว่าการกระจายตัวของ


2

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

ตอนนี้เมื่อหน่วยความจำมีการแยกส่วนสองสิ่งสามารถเกิดขึ้นได้:

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

1

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

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

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

สิ่งที่คุณควรจำไว้คือบนระบบเดสก์ท็อป 32 บิต x86 คุณมีหน่วยความจำ 2GB ทั้งหมดซึ่งแบ่งออกเป็น "หน้า" 4KB (ค่อนข้างแน่ใจว่าขนาดหน้าเท่ากันทุกระบบ x86) คุณจะต้องเรียกใช้การกระจายตัวของ omgwtfbbq บางอย่างเพื่อให้มีปัญหา การแตกแฟรกเมนต์เป็นปัญหาในอดีตเนื่องจากฮีปสมัยใหม่มีขนาดใหญ่เกินไปสำหรับแอพพลิเคชั่นส่วนใหญ่และมีความแพร่หลายของระบบที่สามารถทนต่อมันได้เช่นฮีปที่มีการจัดการ


0

รายการประเภทใดที่มีโอกาสประสบมากที่สุด

ดี (= น่ากลัว) ตัวอย่างสำหรับปัญหาที่เกี่ยวข้องกับการกระจายตัวของหน่วยความจำคือการพัฒนาและการเปิดตัวของ"ธาตุ: สงครามเวทมนตร์" , เกมคอมพิวเตอร์โดยStardock

เกมดังกล่าวสร้างขึ้นสำหรับหน่วยความจำ 32 บิต / 2GB และต้องเพิ่มประสิทธิภาพในการจัดการหน่วยความจำจำนวนมากเพื่อให้เกมทำงานได้ภายในหน่วยความจำ 2GB ในฐานะที่เป็น "การเพิ่มประสิทธิภาพ" นำไปสู่การจัดสรรอย่างต่อเนื่องและ de-จัดสรรเมื่อเวลาผ่านไปการกระจายตัวของหน่วยความจำกองเกิดขึ้นและทำให้เกมผิดพลาดทุก เวลา

มีการสัมภาษณ์ "เรื่องราวสงคราม"บน YouTube

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