แปลบทความที่จัดทำขึ้นสำหรับนักศึกษารายวิชา
ก่อนหน้านี้ ฉันได้พูดคุยเกี่ยวกับวิธีทดสอบและเปิดใช้งาน Hugepages บน Linux
บทความนี้จะมีประโยชน์ก็ต่อเมื่อคุณมีสถานที่สำหรับใช้งาน Hugepages จริงๆ ฉันได้พบกับผู้คนมากมายที่ถูกหลอกโดยโอกาสที่ Hugepages จะปรับปรุงประสิทธิภาพการทำงานอย่างน่าอัศจรรย์ อย่างไรก็ตาม การเพจขนาดใหญ่เป็นหัวข้อที่ซับซ้อนและอาจลดประสิทธิภาพได้หากใช้ไม่ถูกต้อง
ส่วนที่ 1: การตรวจสอบว่ามีการเปิดใช้งาน hugepages บน Linux (Original ที่นี่ )
ปัญหา:
คุณต้องตรวจสอบว่า HugePages เปิดใช้งานอยู่ในระบบของคุณหรือไม่
การแก้ปัญหา:
มันค่อนข้างง่าย:
cat /sys/kernel/mm/transparent_hugepage/enabled
คุณจะได้รับสิ่งนี้:
always [madvise] never
คุณจะเห็นรายการตัวเลือกที่มี (เสมอ แมดวิส ไม่เคยเลย) และตัวเลือกที่ใช้งานอยู่ในปัจจุบันจะอยู่ในวงเล็บ (โดยค่าเริ่มต้น แมดไวซ์).
แมดไวซ์ หมายความว่า transparent hugepages
เปิดใช้งานเฉพาะพื้นที่หน่วยความจำที่ร้องขอการใช้เพจขนาดใหญ่อย่างชัดเจน
เสมอ หมายความว่า transparent hugepages
เปิดใช้งานเสมอสำหรับกระบวนการทั้งหมด ซึ่งมักจะช่วยปรับปรุงประสิทธิภาพ แต่หากคุณมีกรณีการใช้งานที่กระบวนการจำนวนมากใช้หน่วยความจำจำนวนเล็กน้อย โหลดหน่วยความจำโดยรวมก็อาจเพิ่มขึ้นได้อย่างมาก
ไม่เคย หมายความว่า transparent hugepages
จะไม่รวมอยู่แม้ว่าจะร้องขอโดยใช้ madvise ก็ตาม หากต้องการข้อมูลเพิ่มเติม โปรดติดต่อ
วิธีเปลี่ยนค่าเริ่มต้น
ตัวเลือก 1: เปลี่ยนแปลงโดยตรง sysfs
(หลังจากรีบูตพารามิเตอร์จะกลับสู่ค่าเริ่มต้น):
echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabled
ตัวเลือก 2: เปลี่ยนค่าเริ่มต้นของระบบโดยการคอมไพล์เคอร์เนลใหม่ด้วยการกำหนดค่าที่แก้ไข (แนะนำให้ใช้ตัวเลือกนี้เฉพาะในกรณีที่คุณใช้เคอร์เนลแบบกำหนดเอง):
- หากต้องการตั้งค่าเป็นค่าเริ่มต้นเสมอ ให้ใช้:
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y # Comment out CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y
- หากต้องการตั้งค่า madvise เป็นค่าเริ่มต้น ให้ใช้:
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y # Comment out CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
ส่วนที่ 2: ข้อดีและข้อเสียของ HugePages
เราจะพยายามคัดเลือกอธิบายข้อดี ข้อเสีย และข้อผิดพลาดที่เป็นไปได้ของการใช้ Hugepages เนื่องจากบทความที่มีความซับซ้อนทางเทคโนโลยีและอวดรู้น่าจะเข้าใจได้ยากสำหรับผู้ที่ถูกหลอกให้คิดว่า Hugepages เป็นยาครอบจักรวาล ฉันจะยอมเสียสละความแม่นยำเพื่อความเรียบง่าย โปรดจำไว้ว่าหัวข้อต่างๆ มากมายมีความซับซ้อนและทำให้ง่ายขึ้นมาก
โปรดทราบว่าเรากำลังพูดถึงระบบ x64 แบบ 86 บิตที่ใช้ Linux และฉันเพียงแต่สันนิษฐานว่าระบบรองรับหน้าขนาดใหญ่แบบโปร่งใส (เนื่องจากไม่มีข้อเสียที่หน้าใหญ่จะไม่ถูกเขียนทับ) เช่นเดียวกับในกรณีของ Linux สมัยใหม่เกือบทุกรุ่น สิ่งแวดล้อม.
ฉันจะแนบคำอธิบายทางเทคนิคเพิ่มเติมในลิงก์ด้านล่าง
หน่วยความจำเสมือน
หากคุณเป็นโปรแกรมเมอร์ C++ คุณจะรู้ว่าวัตถุในหน่วยความจำมีที่อยู่เฉพาะ (ค่าตัวชี้)
อย่างไรก็ตาม ที่อยู่เหล่านี้ไม่จำเป็นต้องสะท้อนถึงที่อยู่จริงในหน่วยความจำ (ที่อยู่ RAM) แสดงถึงที่อยู่ในหน่วยความจำเสมือน โปรเซสเซอร์มีโมดูล MMU (หน่วยการจัดการหน่วยความจำ) พิเศษที่ช่วยให้เคอร์เนลจับคู่หน่วยความจำเสมือนกับตำแหน่งทางกายภาพ
วิธีนี้มีข้อดีหลายประการ แต่ข้อดีที่สำคัญที่สุดคือ:
- ประสิทธิภาพ (ด้วยเหตุผลหลายประการ);
- การแยกโปรแกรม กล่าวคือ ไม่มีโปรแกรมใดสามารถอ่านจากหน่วยความจำของโปรแกรมอื่นได้
เพจคืออะไร?
หน่วยความจำเสมือนแบ่งออกเป็นหน้าต่างๆ แต่ละหน้าชี้ไปที่หน่วยความจำกายภาพเฉพาะ อาจชี้ไปที่พื้นที่ใน RAM หรืออาจชี้ไปยังที่อยู่ที่กำหนดให้กับอุปกรณ์ทางกายภาพ เช่น การ์ดแสดงผล
หน้าส่วนใหญ่ที่คุณจัดการชี้ไปที่ RAM หรือถูกสลับ ซึ่งหมายความว่าหน้าเหล่านั้นจะถูกจัดเก็บไว้ในฮาร์ดไดรฟ์หรือ SSD ของคุณ เคอร์เนลจัดการเค้าโครงทางกายภาพของแต่ละหน้า หากมีการเข้าถึงเพจปลอม เคอร์เนลจะหยุดเธรดที่พยายามเข้าถึงหน่วยความจำ อ่านเพจจากฮาร์ดไดรฟ์/SSD ลงใน RAM จากนั้นจึงดำเนินการเธรดต่อไป
กระบวนการนี้มีความโปร่งใสในการสตรีม ซึ่งหมายความว่าไม่จำเป็นต้องอ่านโดยตรงจาก HDD/SSD ขนาดของเพจปกติคือ 4096 ไบต์ ขนาด Hugepages คือ 2 เมกะไบต์
บัฟเฟอร์ที่เกี่ยวข้องกับการแปล (TLB)
เมื่อโปรแกรมเข้าถึงเพจของหน่วยความจำ CPU จะต้องรู้ว่าเพจฟิสิคัลใดที่จะอ่านข้อมูลจาก (นั่นคือ มีการแมปที่อยู่เสมือน)
เคอร์เนลมีโครงสร้างข้อมูล (ตารางเพจ) ที่มีข้อมูลทั้งหมดเกี่ยวกับเพจที่กำลังใช้งาน เมื่อใช้โครงสร้างข้อมูลนี้ คุณสามารถจับคู่ที่อยู่เสมือนกับที่อยู่ทางกายภาพได้
อย่างไรก็ตาม ตารางเพจค่อนข้างซับซ้อนและช้า ดังนั้นเราจึงไม่สามารถวิเคราะห์โครงสร้างข้อมูลทั้งหมดทุกครั้งที่กระบวนการเข้าถึงหน่วยความจำได้
โชคดีที่โปรเซสเซอร์ของเรามี TLB ที่แคชการจับคู่ระหว่างที่อยู่เสมือนและที่อยู่จริง ซึ่งหมายความว่าแม้ว่าเราจำเป็นต้องแยกวิเคราะห์ตารางเพจในความพยายามเข้าถึงครั้งแรก แต่การเข้าถึงเพจในภายหลังทั้งหมดสามารถจัดการได้ใน TLB ช่วยให้ดำเนินการได้อย่างรวดเร็ว
เนื่องจากมันถูกนำไปใช้เป็นอุปกรณ์ทางกายภาพ (ซึ่งทำให้รวดเร็วตั้งแต่แรก) ความจุจึงมีจำกัด ดังนั้น หากคุณต้องการเข้าถึงเพจเพิ่มเติม TLB จะไม่สามารถจัดเก็บการแมปสำหรับเพจทั้งหมดได้ ทำให้โปรแกรมของคุณทำงานช้าลงมาก
Hugepages มาช่วยเหลือ
แล้วเราจะทำอย่างไรเพื่อหลีกเลี่ยงการล้นของ TLB? (เราถือว่าโปรแกรมยังคงต้องการหน่วยความจำจำนวนเท่าเดิม)
นี่คือที่มาของ Hugepages แทนที่จะเป็น 4096 ไบต์ที่ต้องใช้เพียงรายการ TLB รายการเดียว ตอนนี้รายการ TLB หนึ่งรายการสามารถชี้ไปที่ขนาดมหึมา 2 เมกะไบต์ได้ สมมติว่า TLB มี 512 รายการ หากไม่มี Hugepages เราก็สามารถจับคู่ได้:
4096 b⋅512=2 MB
แล้วเราจะเปรียบเทียบกับพวกเขาได้อย่างไร:
2 MB⋅512=1 GB
นี่คือเหตุผลว่าทำไม Hugepages ถึงยอดเยี่ยม พวกเขาสามารถปรับปรุงประสิทธิภาพการผลิตได้โดยไม่ต้องใช้ความพยายามมากนัก แต่มีข้อแม้ที่สำคัญอยู่ที่นี่
การปลอมแปลงเพจขนาดใหญ่
เคอร์เนลจะตรวจสอบความถี่ในการใช้งานแต่ละเพจหน่วยความจำโดยอัตโนมัติ หากมีหน่วยความจำกายภาพ (RAM) ไม่เพียงพอ เคอร์เนลจะย้ายเพจที่มีความสำคัญน้อยกว่า (ใช้งานน้อยกว่า) ไปยังฮาร์ดดิสก์เพื่อเพิ่ม RAM บางส่วนสำหรับเพจที่สำคัญกว่า
โดยหลักการแล้ว เช่นเดียวกับ Hugepages อย่างไรก็ตาม เคอร์เนลสามารถสลับได้เฉพาะทั้งหน้าเท่านั้น ไม่ใช่แต่ละไบต์
สมมติว่าเรามีโปรแกรมดังนี้:
char* mymemory = malloc(2*1024*1024); // Возьмем это за одну Hugepage!
// Заполним mymemory какими-либо данными
// Сделаем много других вещей,
// которые приведут к подмене страницы mymemory
// ...
// Запросим доступ только к первому байту
putchar(mymemory[0]);
ในกรณีนี้ เคอร์เนลจะต้องแทนที่ (อ่าน) ข้อมูลมากถึง 2 เมกะไบต์จากฮาร์ดไดรฟ์/SSD เพื่อให้คุณอ่านได้หนึ่งไบต์ สำหรับเพจปกติ จะต้องอ่านเพียง 4096 ไบต์จากฮาร์ดไดรฟ์/SSD
ดังนั้น หากเพจใหญ่ถูกแทนที่ คุณจะอ่านได้เร็วกว่าถ้าคุณต้องการเข้าถึงทั้งเพจเท่านั้น ซึ่งหมายความว่าหากคุณพยายามสุ่มเข้าถึงส่วนต่างๆ ของหน่วยความจำและอ่านข้อมูลได้เพียง XNUMX-XNUMX กิโลไบต์ คุณควรใช้หน้าปกติและไม่ต้องกังวลกับสิ่งอื่นใด
ในทางกลับกัน หากคุณต้องการเข้าถึงหน่วยความจำส่วนใหญ่ตามลำดับ หน้าขนาดใหญ่จะปรับปรุงประสิทธิภาพของคุณ อย่างไรก็ตาม คุณต้องทดสอบด้วยตัวเอง (ไม่ใช่ด้วยซอฟต์แวร์นามธรรม) และดูว่าอะไรทำงานได้เร็วกว่า
การจัดสรรในหน่วยความจำ
หากคุณเขียน C คุณจะรู้ว่าคุณสามารถขอหน่วยความจำจำนวนเล็กน้อย (หรือเกือบใหญ่โดยพลการ) จากฮีปโดยใช้ malloc()
. สมมติว่าคุณต้องการหน่วยความจำ 30 ไบต์:
char* mymemory = malloc(30);
สำหรับโปรแกรมเมอร์ อาจดูเหมือนว่าคุณกำลัง "ขอ" หน่วยความจำ 30 ไบต์จากระบบปฏิบัติการและส่งคืนพอยน์เตอร์ไปยังหน่วยความจำเสมือนบางส่วน แต่จริงๆแล้ว malloc ()
เป็นเพียงฟังก์ชัน C ที่เรียกใช้จากภายในฟังก์ชัน
อย่างไรก็ตาม การขอหน่วยความจำเพิ่มขึ้นเรื่อยๆ สำหรับการจัดสรรแต่ละครั้งไม่มีประสิทธิภาพ เป็นไปได้มากว่าหน่วยความจำบางส่วนได้รับการปลดปล่อยแล้ว (free())
และเราสามารถนำมันกลับมาใช้ใหม่ได้ malloc()
ใช้อัลกอริธึมที่ค่อนข้างซับซ้อนสำหรับการนำหน่วยความจำที่ว่างกลับมาใช้ใหม่
ในขณะเดียวกัน ทุกอย่างก็เกิดขึ้นโดยไม่มีใครสังเกตเห็น แล้วทำไมคุณถึงต้องกังวลด้วย? แต่เนื่องจากความท้าทาย free()
ไม่ได้หมายความว่าอย่างนั้น
มีสิ่งเช่นการกระจายตัวของหน่วยความจำ ในกรณีที่รุนแรง มีเซ็กเมนต์ฮีปที่ใช้เพียงไม่กี่ไบต์ ในขณะที่ทุกสิ่งที่อยู่ระหว่างนั้นถูกปล่อยว่าง (free())
.
โปรดทราบว่าการกระจายตัวของหน่วยความจำเป็นหัวข้อที่ซับซ้อนอย่างไม่น่าเชื่อ และแม้แต่การเปลี่ยนแปลงเล็กๆ น้อยๆ ในโปรแกรมก็อาจส่งผลกระทบอย่างมีนัยสำคัญได้ ในกรณีส่วนใหญ่ โปรแกรมจะไม่ทำให้เกิดการกระจายตัวของหน่วยความจำอย่างมีนัยสำคัญ แต่คุณควรทราบว่าหากมีปัญหาในการแตกแฟรกเมนต์ในบางพื้นที่ของฮีป หน้าขนาดใหญ่อาจทำให้สถานการณ์แย่ลงได้
การเลือกใช้งาน hugepages
หลังจากอ่านบทความนี้ คุณได้พิจารณาแล้วว่าส่วนใดของโปรแกรมของคุณจะได้ประโยชน์จากการใช้ Hugepages และส่วนใดที่ไม่สามารถทำได้ ดังนั้น hugepages ควรเปิดใช้งานเลยหรือไม่?
โชคดีที่คุณสามารถใช้ madvise()
เพื่อเปิดใช้งานการเพจขนาดใหญ่เฉพาะสำหรับพื้นที่หน่วยความจำที่จะมีประโยชน์เท่านั้น
ขั้นแรก ตรวจสอบว่าหน้าใหญ่กำลังทำงานในโหมด madvise() โดยใช้
จากนั้นใช้ madvise()
เพื่อบอกเคอร์เนลอย่างชัดเจนว่าจะใช้ hugepages ได้ที่ไหน
#include <sys/mman.h>
// Аллоцируйте большое количество памяти, которую будете использовать
size_t size = 256*1024*1024;
char* mymemory = malloc(size);
// Просто включите hugepages…
madvise(mymemory, size, MADV_HUGEPAGE);
// … и задайте следующее
madvise(mymemory, size, MADV_HUGEPAGE | MADV_SEQUENTIAL)
โปรดทราบว่าวิธีนี้เป็นเพียงคำแนะนำแก่เคอร์เนลเกี่ยวกับวิธีจัดการหน่วยความจำ นี่ไม่ได้หมายความว่าเคอร์เนลจะใช้เพจขนาดใหญ่สำหรับหน่วยความจำที่กำหนดโดยอัตโนมัติ
อ้างถึงเอกสารประกอบ madvise()
หัวข้อนี้มีช่วงการเรียนรู้ที่สูงชันอย่างไม่น่าเชื่อ ดังนั้นหากคุณตั้งใจจะทำได้ดีจริงๆ ให้เตรียมอ่านและทดสอบสักสองสามสัปดาห์ก่อนที่คุณจะคาดหวังผลลัพธ์ที่เป็นบวก
อ่านอะไร?
บทความ IBM OpenStack บน hugepages หน้าใหญ่แบบโปร่งใสเทียบกับหน้าใหญ่ที่ไม่โปร่งใส บทความวิกิพีเดียเกี่ยวกับ TLB เอกสารประกอบเคอร์เนล Linux บนหน้าขนาดใหญ่ที่โปร่งใส StackOverflow: Hugepages มีแนวโน้มที่จะเกิดการกระจายตัวของหน่วยความจำ บทความ Microsoft เกี่ยวกับการจัดการหน่วยความจำเสมือน
มีคำถาม? เขียนในความคิดเห็น!
ที่มา: will.com