ฉันสงสัยว่า Ulrich Drepper เป็นเท่าใดโปรแกรมเมอร์ทุกคนควรทราบเกี่ยวกับหน่วยความจำจากปี 2007 ยังคงใช้ได้ ฉันไม่สามารถหาเวอร์ชั่นที่ใหม่กว่า 1.0 หรือ errata ได้
ฉันสงสัยว่า Ulrich Drepper เป็นเท่าใดโปรแกรมเมอร์ทุกคนควรทราบเกี่ยวกับหน่วยความจำจากปี 2007 ยังคงใช้ได้ ฉันไม่สามารถหาเวอร์ชั่นที่ใหม่กว่า 1.0 หรือ errata ได้
คำตอบ:
เท่าที่ฉันจำได้ว่าเนื้อหาของ Drepper อธิบายถึงแนวคิดพื้นฐานเกี่ยวกับหน่วยความจำ: วิธีการทำงานของแคช CPU หน่วยความจำกายภาพและหน่วยความจำเสมือนคืออะไร อาจมีการอ้างอิง API ที่ล้าสมัยในบางตัวอย่าง แต่มันก็ไม่สำคัญ ที่จะไม่ส่งผลกระทบต่อความเกี่ยวข้องของแนวคิดพื้นฐาน
ดังนั้นหนังสือหรือบทความใด ๆ ที่อธิบายบางสิ่งพื้นฐานไม่สามารถเรียกว่าล้าสมัยได้ "สิ่งที่โปรแกรมเมอร์ทุกคนควรรู้เกี่ยวกับหน่วยความจำ" นั้นคุ้มค่าที่จะอ่าน แต่แน่นอนว่าฉันไม่คิดว่าเป็น "โปรแกรมเมอร์ทุกคน" เหมาะสำหรับระบบ / ฝังตัว / เคอร์เนลพวก
คู่มือในรูปแบบไฟล์ PDF ที่https://www.akkadia.org/drepper/cpumemory.pdf
โดยทั่วไปแล้วก็ยังยอดเยี่ยมและแนะนำเป็นอย่างยิ่ง (โดยฉันและฉันคิดว่าโดยผู้เชี่ยวชาญด้านการปรับแต่งประสิทธิภาพอื่น ๆ ) มันจะเจ๋งถ้า Ulrich (หรือคนอื่น ๆ ) เขียนอัปเดตในปี 2560 แต่นั่นจะเป็นงานจำนวนมาก (เช่นใช้มาตรฐานใหม่) ดูการปรับประสิทธิภาพ x86 อื่น ๆ และการปรับแต่ง SSE / asm (และ C / C ++) อื่น ๆ ในx86 แท็กวิกิพีเดีย (บทความของ Ulrich ไม่ได้เฉพาะเจาะจงกับ x86 แต่ส่วนใหญ่ (ทั้งหมด) มาตรฐานของเขาอยู่ในฮาร์ดแวร์ x86)
รายละเอียดฮาร์ดแวร์ระดับต่ำเกี่ยวกับการทำงานของ DRAM และแคชยังคงมีอยู่ DDR4 ใช้คำสั่งเดียวกับที่อธิบายไว้สำหรับ DDR1 / DDR2 (อ่าน / เขียนข้อมูลต่อเนื่อง) การปรับปรุง DDR3 / 4 ไม่ใช่การเปลี่ยนแปลงพื้นฐาน AFAIK ทุกสิ่งที่เป็นอิสระจากซุ้มประตูยังคงใช้งานทั่วไปเช่น AArch64 / ARM32
ดูที่ส่วนLatency Bound Platforms ของคำตอบนี้สำหรับรายละเอียดที่สำคัญเกี่ยวกับผลกระทบของหน่วยความจำ / L3 latency บนแบนด์วิดท์แบบเธรดเดียว: bandwidth <= max_concurrency / latency
และนี่คือคอขวดหลักสำหรับแบนด์วิดท์แบบเธรดเดี่ยวบนซีพียูหลายคอร์เช่น Xeon . แต่เดสก์ท็อป Skylake แบบ quad-core สามารถเข้าใกล้ DRAM แบนด์วิดท์สูงสุดด้วยเธรดเดียว ลิงค์นั้นมีข้อมูลที่ดีมากเกี่ยวกับร้านค้า NT และร้านค้าทั่วไปใน x86 เหตุใด Skylake จึงดีกว่า Broadwell-E มากสำหรับการส่งผ่านหน่วยความจำเธรดเดียว? เป็นบทสรุป
ดังนั้นข้อเสนอแนะของ Ulrich ในข้อ6.5.8 การใช้แบนด์วิดท์ทั้งหมดเกี่ยวกับการใช้หน่วยความจำระยะไกลบนโหนด NUMA อื่น ๆ รวมทั้งของคุณเองนั้นมีประสิทธิภาพในการตอบโต้บนฮาร์ดแวร์ที่ทันสมัยซึ่งหน่วยควบคุมหน่วยความจำมีแบนด์วิดท์มากกว่า อาจเป็นไปได้ว่าคุณสามารถจินตนาการถึงสถานการณ์ที่มีประโยชน์สุทธิในการใช้งานหลายเธรดหิวหน่วยความจำบนโหนด NUMA เดียวกันสำหรับการสื่อสารระหว่างเธรดที่มีความหน่วงแฝงต่ำ แต่การให้พวกเขาใช้หน่วยความจำระยะไกลสำหรับแบนด์วิดท์สูง แต่นี่เป็นสิ่งที่ค่อนข้างคลุมเครือโดยปกติจะแบ่งเธรดระหว่างโหนด NUMA และให้ใช้หน่วยความจำภายใน แบนด์วิดท์ต่อคอร์มีความอ่อนไหวต่อความล่าช้าเนื่องจากข้อ จำกัด การทำงานพร้อมกันสูงสุด (ดูด้านล่าง) แต่คอร์ทั้งหมดในซ็อกเก็ตเดียวมักจะอิ่มตัวมากกว่าตัวควบคุมหน่วยความจำในซ็อกเก็ตนั้น
สิ่งหนึ่งที่สำคัญที่มีการเปลี่ยนแปลงคือ prefetch ฮาร์ดแวร์มากดีกว่าใน Pentium 4และสามารถรับรู้รูปแบบการเข้าถึง strided ถึงก้าวที่ค่อนข้างใหญ่และหลายกระแสในครั้งเดียว (เช่นหนึ่งเดินหน้า / ถอยหลังต่อ 4k หน้า) คู่มือการปรับให้เหมาะสมของ Intelอธิบายรายละเอียดบางอย่างของ HW prefetchers ในระดับต่าง ๆ ของแคชสำหรับสถาปัตยกรรมแบบไมโคร - แซนดี้บริดจ์ Ivybridge และใหม่กว่ามีฮาร์ดแวร์ดึงข้อมูลในหน้าถัดไปแทนที่จะรอแคชที่พลาดในหน้าใหม่เพื่อเริ่มการทำงานอย่างรวดเร็ว ฉันคิดว่า AMD มีของที่คล้ายกันในคู่มือการปรับแต่ง ระวังว่าคู่มือของ Intel นั้นเต็มไปด้วยคำแนะนำเก่า ๆ ซึ่งบางคู่มือก็ดีสำหรับ P4 เท่านั้น ส่วนเฉพาะของ Sandybridge นั้นถูกต้องแน่นอนสำหรับ SnB แต่เช่นยกเลิกการเคลือบของ UOPs ไมโครหลอมรวมการเปลี่ยนแปลงใน HSW และคู่มือการใช้งานไม่ได้พูดถึงมัน
คำแนะนำปกติวันนี้คือการลบ prefetch SW ล่วงหน้าทั้งหมดออกจากรหัสเก่าและให้พิจารณากลับมาอีกครั้งเมื่อการทำโปรไฟล์แสดงว่าแคชหายไป การดึงข้อมูลทั้งสองด้านของขั้นตอนถัดไปของการค้นหาแบบไบนารียังคงช่วยได้ เช่นเมื่อคุณตัดสินใจว่าองค์ประกอบใดที่จะดูถัดไปให้ดึงองค์ประกอบ 1/4 และ 3/4 เพื่อให้สามารถโหลดพร้อมกับโหลด / ตรวจสอบกลางได้
คำแนะนำในการใช้เธรด prefetch ที่แยกต่างหาก (6.3.4) เป็นสิ่งที่ล้าสมัยโดยสิ้นเชิงฉันคิดว่าและทำได้ดีใน Pentium 4 เท่านั้น P4 มีการทำไฮเปอร์เธรด (แกนตรรกะ 2 แกนที่แชร์ฟิสิคัลคอร์) แต่ไม่เพียงพอ / หรือทรัพยากรการดำเนินการที่ไม่เป็นไปตามลำดับ) เพื่อรับปริมาณงานที่ใช้สองการคำนวณแบบเต็มบนแกนหลักเดียวกัน แต่ซีพียูสมัยใหม่ (Sandybridge-family และ Ryzen) นั้นมีเนื้อวัวมากขึ้นและควรรันเธรดจริงหรือไม่ใช้ไฮเปอร์เธรด (ปล่อยให้โลจิคัลคอร์ว่างอื่น ๆ ดังนั้นเธรดเดี่ยวจึงมีทรัพยากรเต็มรูปแบบแทนที่จะแยก ROB)
ซอฟแวร์ prefetch นั้น "เปราะ" อยู่เสมอ : ตัวเลขการปรับค่าเวทย์มนตร์ที่ถูกต้องเพื่อการเร่งความเร็วนั้นขึ้นอยู่กับรายละเอียดของฮาร์ดแวร์ เร็วเกินไปและมันถูกขับไล่ก่อนที่ความต้องการโหลด สายเกินไปและมันก็ไม่ได้ช่วยอะไร บทความบล็อกนี้แสดงรหัส + กราฟสำหรับการทดลองที่น่าสนใจในการใช้ SW prefetch บน Haswell สำหรับการดึงส่วนที่ไม่ต่อเนื่องของปัญหา ดูเพิ่มเติมวิธีการใช้คำแนะนำ prefetch อย่างถูกต้อง? . prefetch NT นั้นน่าสนใจ แต่ยิ่งเปราะกว่าเพราะการขับไล่ตั้งแต่ต้นจาก L1 หมายความว่าคุณต้องไปตลอดทางจนถึง L3 หรือ DRAM ไม่ใช่แค่ L2 หากคุณต้องการประสิทธิภาพที่ลดลงทุกครั้งและคุณสามารถปรับแต่งสำหรับเครื่องเฉพาะ SW prefetch คุ้มค่าที่จะมองหาการเข้าถึงตามลำดับอาจยังคงทำงานช้าลงหากคุณมีงาน ALU เพียงพอที่จะทำในขณะที่เข้าใกล้คอขวดของหน่วยความจำ
ขนาดบรรทัดแคชยังคงเป็น 64 ไบต์ (L1D การอ่าน / เขียนแบนด์วิดท์สูงมากและซีพียูสมัยใหม่สามารถโหลด 2 เวกเตอร์ต่อนาฬิกา + 1 ร้านค้าเวกเตอร์ถ้ามันฮิตใน L1D ดูแคชจะเร็วขนาดนั้นได้อย่างไร? ) ด้วย AVX512 ขนาดบรรทัด = ความกว้างของเวกเตอร์ เพื่อให้คุณสามารถโหลด / จัดเก็บแคชทั้งบรรทัดในคำสั่งเดียว ดังนั้นการโหลด / จัดเก็บที่ไม่ตรงแนวทุกครั้งจะข้ามขอบเขตแคช - ไลน์แทนที่จะเป็น 256b AVX1 / AVX2 ซึ่งมักจะไม่ทำให้ลูปช้าลงในอาเรย์ที่ไม่ได้อยู่ใน L1D
คำแนะนำในการโหลดแบบไม่ตรงแนวมีศูนย์โทษหากที่อยู่ถูกจัดตำแหน่งที่รันไทม์ แต่คอมไพเลอร์ (โดยเฉพาะอย่างยิ่ง gcc) ให้โค้ดที่ดีกว่าเมื่อทำการตรวจสอบอัตโนมัติหากพวกเขารู้เกี่ยวกับการรับรองการจัดตำแหน่งใด ๆ โดยทั่วไปแล้ว ops ที่ไม่ได้จัดแนวนั้นโดยทั่วไปจะเร็ว แต่การแบ่งหน้ายังคงเจ็บ (น้อยกว่าใน Skylake แต่มีความล่าช้าเพิ่มอีกเพียง ~ 11 รอบต่อรอบเทียบกับ 100 แต่ยังคงเป็นโทษต่อปริมาณงาน)
ตามที่ Ulrich คาดการณ์ไว้ทุกระบบมัลติซ็อกเก็ตคือ NUMA ในวันนี้: คอนโทรลเลอร์หน่วยความจำในตัวเป็นมาตรฐานเช่นไม่มี Northbridge ภายนอก แต่ SMP ไม่ได้หมายถึงซ็อกเก็ตหลายตัวอีกต่อไปเพราะ CPU แบบมัลติคอร์นั้นแพร่หลาย ซีพียู Intel จาก Nehalem ถึง Skylake ได้ใช้แคช L3 แบบรวมขนาดใหญ่เป็นแบ็คสต็อปสำหรับการเชื่อมโยงระหว่างคอร์ ซีพียูของ AMD นั้นแตกต่างกัน แต่ฉันยังไม่ชัดเจนกับรายละเอียด
Skylake-X (AVX512) ไม่มี L3 แบบรวมอีกต่อไป แต่ฉันคิดว่ายังคงมีไดเรกทอรีแท็กที่ช่วยให้ตรวจสอบสิ่งที่แคชได้ทุกที่บนชิป (และถ้าเป็นเช่นนั้น) โดยไม่ต้องส่งเสียงสอดแนมไปที่แกนทั้งหมด SKX ใช้ตาข่ายแทนที่จะเป็นริงบัสโดยทั่วไปแล้วความหน่วงแฝงที่แย่กว่า Xeons หลายคอร์ก่อนหน้านี้อย่างน่าเสียดาย
โดยทั่วไปคำแนะนำทั้งหมดเกี่ยวกับการปรับตำแหน่งหน่วยความจำยังคงมีผลอยู่เพียงแค่รายละเอียดว่าเกิดอะไรขึ้นเมื่อคุณไม่สามารถหลีกเลี่ยงแคชที่ผิดพลาดหรือความขัดแย้งต่างกันไป
6.4.2 Atomic ops : เบนช์มาร์กที่แสดง CAS-retry loop ซึ่งแย่กว่าฮาร์ดแวร์ที่กำหนดโดยอนุญาโตตุลาการ 4 เท่าlock add
อาจยังคงสะท้อนถึงกรณีการโต้แย้งสูงสุด แต่ในโปรแกรมแบบมัลติเธรดที่แท้จริงการซิงโครไนซ์จะถูกเก็บไว้ให้น้อยที่สุด (เพราะมีราคาแพง) ดังนั้นการแข่งขันจึงต่ำและวง CAS-retry มักจะประสบความสำเร็จโดยไม่ต้องลองใหม่
C ++ 11 std::atomic
fetch_add
จะคอมไพล์เป็น a lock add
(หรือlock xadd
ถ้าใช้ค่าส่งคืน) แต่อัลกอริทึมที่ใช้ CAS เพื่อทำสิ่งที่ไม่สามารถทำได้ด้วยlock
คำสั่ง ed โดยปกติแล้วจะไม่ใช่หายนะ การใช้ภาษา C ++ 11std::atomic
หรือ C11 stdatomic
แทนมรดก GCC __sync
สร้างอินหรือใหม่__atomic
สร้างอินถ้าคุณต้องการที่จะผสมเข้าถึงอะตอมและไม่ใช่อะตอมไปยังสถานที่เดียวกัน ...
8.1 DWCAS ( cmpxchg16b
) : คุณสามารถเกลี้ยกล่อม gcc เพื่อเปล่งมัน แต่ถ้าคุณต้องการโหลดวัตถุที่มีประสิทธิภาพเพียงครึ่งเดียวคุณต้องunion
แฮ็กที่น่าเกลียด: ฉันจะใช้ตัวนับ ABA กับ c ++ 11 CAS ได้อย่างไร? . (อย่าสับสนกับ DWCAS กับDCAS ของตำแหน่งหน่วยความจำ2 แห่งแยกกันการจำลองแบบปรมาณูแบบไม่มีล็อคของ DCAS เป็นไปไม่ได้กับ DWCAS แต่หน่วยความจำสำหรับธุรกรรม (เช่น x86 TSX) ทำให้เป็นไปได้)
8.2.4 หน่วยความจำของทรานแซคชัน : หลังจากการเริ่มต้นเท็จสองครั้ง (ปล่อยแล้วถูกปิดใช้งานโดยการอัปเดตไมโครโค้ดเนื่องจากข้อผิดพลาดที่ไม่ค่อยได้รับการเรียกใช้) Intel มีหน่วยความจำสำหรับการทำธุรกรรมใน Broadwell รุ่นและซีพียู Skylake ทั้งหมด การออกแบบยังคงเป็นสิ่งที่เดวิดมะยมอธิบาย Haswell มีวิธีล็อครูปวงรีเพื่อใช้เพิ่มความเร็วรหัสที่ใช้ (และสามารถถอยกลับได้) ล็อคปกติ (โดยเฉพาะอย่างยิ่งด้วยการล็อคครั้งเดียวสำหรับองค์ประกอบทั้งหมดของคอนเทนเนอร์ดังนั้นหลายเธรดในส่วนที่สำคัญเดียวกันมักจะไม่ชนกัน ) หรือเพื่อเขียนโค้ดที่รู้เกี่ยวกับการทำธุรกรรมโดยตรง
7.5 Hugepages : hugepagesแบบไม่ระบุชื่อโปร่งใสทำงานได้ดีบน Linux โดยไม่ต้องใช้ hugetlbfs ด้วยตนเอง ทำการจัดสรร> = 2MiB พร้อมการจัดตำแหน่ง 2MiB (เช่นposix_memalign
หรือaligned_alloc
ที่ไม่บังคับใช้ข้อกำหนด ISO C ++ 17 โง่ที่จะล้มเหลวเมื่อsize % alignment != 0
)
การจัดสรรแบบไม่ระบุชื่อแบบ 2MiB จะใช้ hugepages เป็นค่าเริ่มต้น เวิร์กโหลดบางตัว (เช่นที่ยังคงใช้การจัดสรรจำนวนมากหลังจากนั้นไม่นาน) อาจได้รับประโยชน์จาก
echo always >/sys/kernel/mm/transparent_hugepage/defrag
การทำให้เคอร์เนลจัดเรียงข้อมูลหน่วยความจำฟิสิคัลเมื่อใดก็ตามที่ต้องการแทนที่จะหลุดกลับไปเป็นหน้า 4k (ดูเอกสารเคอร์เนล ) อีกทางเลือกหนึ่งคือใช้madvise(MADV_HUGEPAGE)
หลังจากทำการจัดสรรขนาดใหญ่ (ควรยังคงมีการจัดตำแหน่ง 2MiB)
ภาคผนวก B: Oprofile : Linux perf
ได้แทนที่ส่วนใหญ่oprofile
แล้ว สำหรับเหตุการณ์ที่มีรายละเอียดที่เฉพาะเจาะจงเพื่อ microarchitectures บางอย่างใช้ocperf.py
กระดาษห่อ เช่น
ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out
สำหรับตัวอย่างของการใช้งานดูMOV ของ x86 จะเป็น "ฟรี" จริงหรือไม่? เหตุใดฉันจึงทำซ้ำไม่ได้เลย .
จากภาพรวมที่รวดเร็วของฉันมันดูค่อนข้างแม่นยำ สิ่งหนึ่งที่ควรสังเกตคือส่วนต่างระหว่างตัวควบคุมหน่วยความจำ "รวม" และ "ภายนอก" นับตั้งแต่มีการเปิดตัวซีพียู Intel line i7 ทุกตัวได้รวมเข้าด้วยกันและ AMD ได้ใช้คอนโทรลเลอร์หน่วยความจำในตัวนับตั้งแต่ชิป AMD64 เปิดตัวครั้งแรก
เนื่องจากบทความนี้ถูกเขียนขึ้นมีการเปลี่ยนแปลงไม่มากนักความเร็วได้สูงขึ้นตัวควบคุมหน่วยความจำมีความฉลาดมากขึ้น (i7 จะหน่วงเวลาการเขียนลงแรมจนกว่าจะรู้สึกเหมือนยอมรับการเปลี่ยนแปลง) แต่ไม่มีการเปลี่ยนแปลงมากมาย . อย่างน้อยก็ไม่มีทางที่ผู้พัฒนาซอฟต์แวร์จะใส่ใจ