รหัส“ เป็นมิตรกับแคช” คืออะไร?


738

ความแตกต่างระหว่าง " รหัสที่ไม่เป็นมิตรแคช " และ " รหัสที่เป็นมิตรกับแคช " คืออะไร?

ฉันจะแน่ใจได้อย่างไรว่าฉันเขียนโค้ดที่มีประสิทธิภาพแคช


28
นี่อาจเป็นคำใบ้แก่คุณ: stackoverflow.com/questions/9936132/…
Robert Martin

4
ยังต้องระวังขนาดของสายแคช บนโปรเซสเซอร์ที่ทันสมัยนั้นมักจะ 64 ไบต์
John Dibling

3
นี่เป็นอีกบทความที่ดีมาก หลักการนำไปใช้กับโปรแกรม C / C ++ บนระบบปฏิบัติการใด ๆ (Linux, MaxOS หรือ Windows): lwn.net/Articles/255364
paulsm4

4
คำถามที่เกี่ยวข้อง: stackoverflow.com/questions/8469427/…
แมตต์

คำตอบ:


965

รอบคัดเลือกโซน

บนคอมพิวเตอร์สมัยใหม่เฉพาะโครงสร้างหน่วยความจำระดับต่ำสุด (เรจิสเตอร์ ) เท่านั้นที่สามารถย้ายข้อมูลในรอบนาฬิกาเดียว อย่างไรก็ตามการลงทะเบียนมีราคาแพงมากและคอร์คอมพิวเตอร์ส่วนใหญ่มีการลงทะเบียนน้อยกว่าสองสามโหล (ไม่กี่ร้อยถึงอาจรวมเป็นพันไบต์ ) ที่ปลายอีกด้านหนึ่งของหน่วยความจำสเปกตรัม ( DRAM ) หน่วยความจำราคาถูกมาก (เช่นแท้จริงถูกกว่าล้านครั้ง ) แต่ใช้เวลาหลายร้อยรอบหลังจากขอให้ได้รับข้อมูล ในการลดช่องว่างนี้ระหว่างความรวดเร็วและแพงและซุปเปอร์ช้าและราคาถูกนั้นเป็นความทรงจำแคชชื่อ L1, L2, L3 ในการลดความเร็วและค่าใช้จ่าย แนวคิดคือรหัสการดำเนินการส่วนใหญ่จะตีชุดตัวแปรขนาดเล็กบ่อยครั้งและส่วนที่เหลือ (ชุดตัวแปรขนาดใหญ่กว่า) นาน ๆ ครั้ง หากโปรเซสเซอร์ไม่พบข้อมูลในแคช L1 แสดงว่าแคช L2 นั้นมีลักษณะ หากไม่ได้อยู่ที่นั่นแคช L3 และหากไม่มีหน่วยความจำหลัก แต่ละ "คิดถึง" เหล่านี้มีราคาแพงในเวลา

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

แคชเป็นหนึ่งในวิธีการหลักในการลดผลกระทบจากความล่าช้า ในการถอดความ Herb Sutter (ลิงก์ลิงก์ด้านล่าง): การเพิ่มแบนด์วิดท์เป็นเรื่องง่าย แต่เราไม่สามารถซื้อหน่วงเวลาได้

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

ในสถาปัตยกรรมคอมพิวเตอร์สมัยใหม่คอขวดของประสิทธิภาพกำลังทำให้ CPU ตาย (เช่นการเข้าถึง RAM หรือสูงกว่า) สิ่งนี้จะเลวร้ายลงเมื่อเวลาผ่านไป การเพิ่มขึ้นของความถี่โปรเซสเซอร์ในปัจจุบันไม่เกี่ยวข้องกับการเพิ่มประสิทธิภาพอีกต่อไป ปัญหาคือการเข้าถึงหน่วยความจำ ความพยายามในการออกแบบฮาร์ดแวร์ในซีพียูในปัจจุบันจึงมุ่งเน้นไปที่การเพิ่มประสิทธิภาพแคชการดึงข้อมูลล่วงหน้าท่อและการทำงานพร้อมกัน ยกตัวอย่างเช่นซีพียูสมัยใหม่ใช้จ่ายแคช 85% ของค่าตายและ 99% สำหรับการจัดเก็บ / ย้ายข้อมูล!

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

แนวคิดหลักสำหรับรหัสที่เป็นมิตรกับแคช

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

ลักษณะเฉพาะต่อไปนี้มีความสำคัญสูงในการปรับการแคชให้เหมาะสม:

  1. สถานที่ชั่วคราว : เมื่อเข้าถึงตำแหน่งหน่วยความจำที่กำหนดอาจเป็นไปได้ว่าจะมีการเข้าถึงตำแหน่งเดิมอีกครั้งในอนาคตอันใกล้ เป็นการดีที่ข้อมูลนี้จะยังคงถูกแคช ณ จุดนั้น
  2. พื้นที่เชิงพื้นที่ : นี่หมายถึงการวางข้อมูลที่เกี่ยวข้องไว้ใกล้กัน การแคชเกิดขึ้นในหลาย ๆ ระดับไม่ใช่เฉพาะในซีพียู ตัวอย่างเช่นเมื่อคุณอ่านจาก RAM โดยทั่วไปแล้วหน่วยความจำอันใหญ่กว่าจะถูกดึงมามากกว่าสิ่งที่ถูกถามเป็นพิเศษเพราะบ่อยครั้งที่โปรแกรมจะต้องใช้ข้อมูลนั้นในไม่ช้า แคช HDD ทำตามแนวความคิดเดียวกัน โดยเฉพาะสำหรับแคชของ CPU แนวคิดของบรรทัดแคชมีความสำคัญ

ใช้อย่างเหมาะสม ตู้คอนเทนเนอร์

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

ภาพประกอบที่ยอดเยี่ยมนี้มอบให้โดย Bjarne Stroustrup ในคลิป youtube นี้ (ขอบคุณ @ Mohammad Ali Baydoun สำหรับลิงก์!)

อย่าละเลยแคชในโครงสร้างข้อมูลและการออกแบบอัลกอริทึม

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

รู้และใช้ประโยชน์จากโครงสร้างข้อมูลโดยปริยาย

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

1 2
3 4

ในการเรียงลำดับแถวหลักจะถูกเก็บไว้ในหน่วยความจำ1 2 3 4ดังนี้ 1 3 2 4ในการสั่งซื้อคอลัมน์ที่สำคัญนี้จะถูกเก็บไว้เป็น เป็นเรื่องง่ายที่จะเห็นว่าการใช้งานที่ไม่ใช้ประโยชน์จากคำสั่งนี้จะประสบปัญหาแคช (หลีกเลี่ยงได้ง่าย!) แต่น่าเสียดายที่ฉันเห็นสิ่งเช่นนี้มากมักจะอยู่ในโดเมน (เรียนรู้ของเครื่อง) ของฉัน @MatteoItalia แสดงตัวอย่างนี้โดยละเอียดยิ่งขึ้นในคำตอบของเขา

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

เพื่อความง่ายสมมติว่าแคชประกอบด้วยบรรทัดแคชเดี่ยวซึ่งสามารถมีองค์ประกอบเมทริกซ์ได้ 2 รายการและเมื่อองค์ประกอบที่กำหนดถูกดึงมาจากหน่วยความจำส่วนถัดไปก็เช่นกัน สมมติว่าเราต้องการหาผลรวมเหนือองค์ประกอบทั้งหมดในตัวอย่าง 2x2 เมทริกซ์ด้านบน (เรียกว่าM):

ใช้ประโยชน์จากการสั่งซื้อ (เช่นการเปลี่ยนดัชนีคอลัมน์ก่อน ):

M[0][0] (memory) + M[0][1] (cached) + M[1][0] (memory) + M[1][1] (cached)
= 1 + 2 + 3 + 4
--> 2 cache hits, 2 memory accesses

ไม่ใช้ประโยชน์จากการจัดลำดับ (เช่นการเปลี่ยนดัชนีแถวก่อน ):

M[0][0] (memory) + M[1][0] (memory) + M[0][1] (memory) + M[1][1] (memory)
= 1 + 3 + 2 + 4
--> 0 cache hits, 4 memory accesses

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

หลีกเลี่ยงกิ่งที่ไม่สามารถคาดเดาได้

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

สิ่งนี้อธิบายได้ดีมากที่นี่ (ขอบคุณ @ 0x90 สำหรับลิงก์): เหตุใดการประมวลผลอาร์เรย์ที่เรียงลำดับจึงเร็วกว่าการประมวลผลอาร์เรย์ที่ไม่เรียงลำดับ

หลีกเลี่ยงฟังก์ชั่นเสมือนจริง

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

ปัญหาที่พบบ่อย

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

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


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

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

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

2
@ JackAidley ขอบคุณสำหรับการสรรเสริญ! เมื่อฉันเห็นจำนวนของความสนใจที่คำถามนี้ได้รับฉันคิดว่าหลายคนอาจสนใจคำอธิบายที่ค่อนข้างครอบคลุม ฉันดีใจที่มันมีประโยชน์
Marc Claesen

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

140

นอกจากคำตอบของ @Marc Claesen ฉันคิดว่าตัวอย่างคลาสสิกที่ให้คำแนะนำของรหัสแคชที่ไม่เป็นมิตรคือโค้ดที่สแกนอาร์เรย์ C สองมิติ (เช่นรูปภาพบิตแมป) แทนที่จะเป็นแถวที่ชาญฉลาด

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

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

และทั้งหมดที่ใช้ในการทำลายประสิทธิภาพคือไปจาก

// Cache-friendly version - processes pixels which are adjacent in memory
for(unsigned int y=0; y<height; ++y)
{
    for(unsigned int x=0; x<width; ++x)
    {
        ... image[y][x] ...
    }
}

ถึง

// Cache-unfriendly version - jumps around in memory for no good reason
for(unsigned int x=0; x<width; ++x)
{
    for(unsigned int y=0; y<height; ++y)
    {
        ... image[y][x] ...
    }
}

เอฟเฟกต์นี้ค่อนข้างน่าทึ่ง (มีหลายลำดับความเร็วในระบบ) ในระบบที่มีแคชขนาดเล็กและ / หรือทำงานกับอาร์เรย์ขนาดใหญ่ (เช่น 10+ ล้านพิกเซล 24 bpp ภาพในเครื่องปัจจุบัน); ด้วยเหตุนี้หากคุณต้องสแกนแนวตั้งหลาย ๆ ครั้งบ่อยครั้งจะเป็นการดีกว่าที่จะหมุนภาพ 90 องศาก่อนและทำการวิเคราะห์ต่าง ๆ ในภายหลังโดย จำกัด รหัสแคชที่ไม่เป็นมิตรกับการหมุน


เอ่อนั่นควรเป็น x <width หรือไม่
mowwwalker

13
เครื่องมือแก้ไขรูปภาพสมัยใหม่ใช้แผ่นกระเบื้องเป็นที่เก็บข้อมูลภายในเช่นบล็อคขนาด 64x64 พิกเซล นี่เป็นมิตรกับแคชมากขึ้นสำหรับการดำเนินงานในพื้นที่ (วางตบเบา ๆ รันฟิลเตอร์เบลอ) เนื่องจากพิกเซลที่อยู่ใกล้เคียงนั้นอยู่ใกล้หน่วยความจำทั้งสองทิศทางซึ่งส่วนใหญ่แล้ว
maxy

ฉันลองกำหนดเวลาเป็นตัวอย่างที่คล้ายกันในเครื่องของฉันและฉันพบว่าเวลานั้นเหมือนกัน มีคนอื่นลองจับเวลาดูไหม?
gsingh2011

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

1
@Gauthier: ใช่ตัวอย่างแรกคือสิ่งที่ดี; ฉันคิดว่าเมื่อฉันเขียนสิ่งนี้ฉันกำลังคิดตามแนวของ "ทั้งหมดที่ต้องใช้ [เพื่อทำลายประสิทธิภาพการทำงานของแอปพลิเคชันการทำงาน] คือไปจาก ... ไปยัง ... "
Matteo Italia

88

การเพิ่มประสิทธิภาพการใช้แคชส่วนใหญ่มาจากสองปัจจัย

สถานที่อ้างอิง

ปัจจัยแรก (ซึ่งคนอื่นได้พูดพาดพิงถึง) เป็นสถานที่ของการอ้างอิง สถานที่อ้างอิงมีสองมิติจริง ๆ : พื้นที่และเวลา

  • เกี่ยวกับอวกาศ

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

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

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

  • เวลา

มิติเวลาหมายความว่าเมื่อคุณทำการดำเนินการบางอย่างกับข้อมูลบางอย่างคุณต้องการ (มากที่สุด) เพื่อทำการดำเนินการทั้งหมดกับข้อมูลนั้นในครั้งเดียว

เนื่องจากคุณได้ติดแท็กนี้เป็น C ++ std::valarrayผมจะชี้ไปที่ตัวอย่างคลาสสิกของการออกแบบที่ค่อนข้างแคชไม่เป็นมิตร: valarrayoverloads ดำเนินการทางคณิตศาสตร์มากที่สุดเพื่อให้ฉันสามารถ (ตัวอย่าง) กล่าวa = b + c + d;(ที่a, b, cและdมี valarrays ทั้งหมด) จะทำอย่างไรนอกจากนี้องค์ประกอบที่ชาญฉลาดของอาร์เรย์เหล่านั้น

ปัญหานี้คือว่ามันผ่านอินพุตหนึ่งคู่ทำให้ผลลัพธ์ในชั่วคราวเดินผ่านอินพุตคู่อื่นและอื่น ๆ ด้วยข้อมูลจำนวนมากผลลัพธ์จากการคำนวณหนึ่งครั้งอาจหายไปจากแคชก่อนที่จะถูกใช้ในการคำนวณครั้งถัดไปดังนั้นเราจึงสิ้นสุดการอ่าน (และเขียน) ข้อมูลซ้ำ ๆ ก่อนที่เราจะได้ผลลัพธ์สุดท้าย หากองค์ประกอบของผลสุดท้ายแต่ละคนจะเป็นสิ่งที่ชอบ(a[n] + b[n]) * (c[n] + d[n]);โดยทั่วไปเราต้องการที่จะอ่านแต่ละa[n], b[n], c[n]และd[n]ครั้งเดียวทำคำนวณเขียนผลที่เพิ่มขึ้นnและทำซ้ำจนกว่าที่เรากำลังทำ 2

การแชร์สาย

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

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

ปัญหาน่าจะค่อนข้างชัดเจน: สำหรับแคชที่แมปโดยตรงตัวถูกดำเนินการสองตัวที่เกิดขึ้นกับแมปไปยังตำแหน่งแคชเดียวกันอาจทำให้เกิดพฤติกรรมที่ไม่ดี N-way set-associative cache เพิ่มจำนวนจาก 2 เป็น N + 1 การจัดระเบียบแคชให้เป็น "วิธี" มากขึ้นนั้นต้องใช้วงจรเพิ่มเติมและโดยทั่วไปจะทำงานช้าลงดังนั้น (ตัวอย่างเช่น) แคชที่เชื่อมโยงชุด 8192- ทางเป็นวิธีแก้ปัญหาที่ดีเช่นกัน

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

  • การแบ่งปันที่ผิดพลาด

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


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

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

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


1
ฉันชอบข้อมูลเพิ่มเติมในคำตอบของคุณโดยเฉพาะvalarrayตัวอย่าง
Marc Claesen

1
+1 ในที่สุด: คำอธิบายธรรมดาของชุดการเชื่อมโยง! แก้ไขเพิ่มเติม: นี่คือหนึ่งในคำตอบที่ให้ข้อมูลมากที่สุดเกี่ยวกับ SO ขอบคุณ.
วิศวกร

32

ยินดีต้อนรับสู่โลกของ Data Oriented Design มนต์ขั้นพื้นฐานคือการเรียงลำดับกำจัดสาขาแบทช์กำจัดvirtualสาย - ขั้นตอนทั้งหมดสู่ท้องถิ่นที่ดีกว่า

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


1
คุณหมายถึงอะไรโดยแบทช์หนึ่งอาจไม่เข้าใจ
0x90

5
การทำแบทช์: แทนที่จะทำหน่วยของการทำงานกับวัตถุหนึ่งชิ้นให้ดำเนินการกับชุดของวัตถุ
arul

การบล็อก AKA การบล็อกการลงทะเบียนการบล็อกแคช
0x90

1
การปิดกั้น / ไม่บล็อกมักหมายถึงการที่วัตถุทำงานในสภาพแวดล้อมที่เกิดขึ้นพร้อมกัน
arul

2
batching == vectorization
Amro

23

เพียงแค่กองพะเนิน: ตัวอย่างคลาสสิกของแคชที่ไม่เป็นมิตรกับรหัสที่เป็นมิตรกับแคชคือ "การบล็อกแคช" ของเมทริกซ์ทวีคูณ

Naive matrix ทวีคูณดูเหมือนว่า:

for(i=0;i<N;i++) {
   for(j=0;j<N;j++) {
      dest[i][j] = 0;
      for( k==;k<N;i++) {
         dest[i][j] += src1[i][k] * src2[k][j];
      }
   }
}

หากNมีขนาดใหญ่เช่นถ้าN * sizeof(elemType)มากกว่าแคชขนาดแล้วทุกครั้งที่เข้าถึงsrc2[k][j]จะหายไปจากแคช

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

int itemsPerCacheLine = CacheLineSize / sizeof(elemType);

for(i=0;i<N;i++) {
   for(j=0;j<N;j += itemsPerCacheLine ) {
      for(jj=0;jj<itemsPerCacheLine; jj+) {
         dest[i][j+jj] = 0;
      }
      for( k=0;k<N;k++) {
         for(jj=0;jj<itemsPerCacheLine; jj+) {
            dest[i][j+jj] += src1[i][k] * src2[k][j+jj];
         }
      }
   }
}

หากขนาดของแคชบรรทัดคือ 64 ไบต์และเราดำเนินการกับ 32 บิต (4 ไบต์) ลอยตัวมี 16 รายการต่อหนึ่งบรรทัดแคช และจำนวนแคชที่หายไปจากการแปลงแบบเรียบง่ายนี้จะลดลงประมาณ 16 เท่า

การแปลงที่ชื่นชอบใช้งานได้บนไทล์ 2D เพิ่มประสิทธิภาพสำหรับแคชหลาย ๆ ตัว (L1, L2, TLB) และอื่น ๆ

ผลลัพธ์บางส่วนของ "การบล็อกแคช" ของ googling:

http://stumptown.cc.gt.atl.ga.us/cse6230-hpcta-fa11/slides/11a-matmul-goto.pdf

http://software.intel.com/en-us/articles/cache-blocking-techniques

ภาพเคลื่อนไหววิดีโอที่ยอดเยี่ยมของอัลกอริธึมการบล็อกแคชที่ดีที่สุด

http://www.youtube.com/watch?v=IFWgwGMMrh0

การปูกระเบื้องวนรอบนั้นสัมพันธ์กันอย่างมาก:

http://en.wikipedia.org/wiki/Loop_tiling


7
ผู้ที่อ่านบทความนี้อาจสนใจบทความของฉันเกี่ยวกับการคูณเมทริกซ์ที่ฉันทดสอบอัลกอริธึมที่เป็นมิตรกับแคช "ikj" และอัลกอริทึม ijk ที่ไม่เป็นมิตรด้วยการคูณเมทริกซ์ 2000x2000 สองตัว
Martin Thoma

3
k==;ฉันหวังว่านี่เป็นตัวพิมพ์ผิดหรือเปล่า?
TrebledJ

13

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

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

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

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

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

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


4

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

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

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

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

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


2

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

สิ่งนี้สมเหตุสมผลในกรณีที่ระบบฐานข้อมูลจัดวางตำแหน่งของตารางเป็นเส้นตรงและจัดเก็บ มีวิธีพื้นฐานสองวิธีในการจัดเก็บสิ่งอันดับของตารางเช่นที่เก็บแถวและที่เก็บคอลัมน์ ในการจัดเก็บแถวเป็นชื่อแนะนำ tuples จะถูกเก็บไว้แถวที่ชาญฉลาด สมมติว่าตารางProductที่จัดเก็บชื่อนั้นมี 3 คุณลักษณะคือint32_t key, char name[56]และint32_t priceขนาดรวมของสิ่งอันดับคือ64ไบต์

เราสามารถจำลองการประมวลผลเคียวรีร้านค้าแถวขั้นพื้นฐานในหน่วยความจำหลักโดยการสร้างอาร์เรย์ของProductstruct ด้วยขนาด N โดยที่ N คือจำนวนแถวในตาราง เลย์เอาต์ของหน่วยความจำแบบนี้เรียกว่าอาเรย์ของ struct ดังนั้นโครงสร้างของผลิตภัณฑ์จึงเป็นเช่น:

struct Product
{
   int32_t key;
   char name[56];
   int32_t price'
}

/* create an array of structs */
Product* table = new Product[N];
/* now load this array of structs, from a file etc. */

ในทำนองเดียวกันเราสามารถจำลองการประมวลผลแบบสอบถามในคอลัมน์พื้นฐานในหน่วยความจำหลักได้โดยสร้างอาร์เรย์ขนาด 3 อาร์เรย์ซึ่งเป็นหนึ่งอาร์เรย์สำหรับแต่ละแอตทริบิวต์ของProductตาราง เลย์เอาต์ของหน่วยความจำดังกล่าวเรียกว่าโครงสร้างของอาร์เรย์ ดังนั้น 3 อาร์เรย์สำหรับแต่ละคุณลักษณะของผลิตภัณฑ์จึงเป็นเช่น:

/* create separate arrays for each attribute */
int32_t* key = new int32_t[N];
char* name = new char[56*N];
int32_t* price = new int32_t[N];
/* now load these arrays, from a file etc. */

ตอนนี้หลังจากโหลดทั้งอาร์เรย์ของ structs (Row Layout) และ 3 อาร์เรย์ที่แยกจากกัน (Layout Layout) เรามีที่เก็บแถวและที่เก็บคอลัมน์บนตารางของเรา Productมีอยู่ในหน่วยความจำของเรา

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

SELECT SUM(price)
FROM PRODUCT

สำหรับที่เก็บแถวเราสามารถแปลงแบบสอบถาม SQL ข้างต้นเป็น

int sum = 0;
for (int i=0; i<N; i++)
   sum = sum + table[i].price;

สำหรับที่เก็บคอลัมน์เราสามารถแปลงแบบสอบถาม SQL ข้างต้นเป็น

int sum = 0;
for (int i=0; i<N; i++)
   sum = sum + price[i];

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

สมมติว่าขนาดบรรทัดแคชคือ 64ไบต์

ในกรณีของเลย์เอาต์แถวเมื่ออ่านบรรทัดแคชมูลค่าราคาเพียง 1 (cacheline_size/product_struct_size = 64/64 = 1 ) tuple นั้นถูกอ่านเนื่องจากขนาด struct ของเราที่ 64 ไบต์และเต็มบรรทัดแคชทั้งหมดของเราดังนั้นสำหรับแคช tuple ทุกครั้งที่เกิดขึ้นในกรณี ของเค้าโครงแถว

ในกรณีของการจัดวางคอลัมน์เมื่ออ่านบรรทัดแคชมูลค่าราคา 16 (cacheline_size/price_int_size = 64/4 = 16 ) tuples จะถูกอ่านเนื่องจากค่าราคาต่อเนื่อง 16 ค่าที่เก็บไว้ในหน่วยความจำจะถูกนำเข้าไปในแคชดังนั้นสำหรับทุก ๆ สิบหกสิบอันดับ เค้าโครงคอลัมน์

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

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


1

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

สิ่งที่ขาดหายไปจากตัวอย่างด้านบนทั้งหมดเป็นเกณฑ์มาตรฐานที่วัดได้ มีตำนานเกี่ยวกับการแสดงมากมาย ถ้าคุณไม่วัดคุณก็ไม่รู้ อย่าทำให้รหัสของคุณซับซ้อนหากคุณไม่ได้ทำการปรับปรุง

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