คำถามประเภทนี้เกิดขึ้นอีกและควรตอบให้ชัดเจนยิ่งกว่า "MATLAB ใช้ไลบรารีที่ปรับให้เหมาะสมที่สุด" หรือ "MATLAB ใช้ MKL" เป็นครั้งเดียวใน Stack Overflow
ประวัติความเป็นมา:
การคูณเมทริกซ์ (ร่วมกับเมทริกซ์ - เวกเตอร์, การคูณเวกเตอร์ - เวกเตอร์และการสลายตัวเมทริกซ์จำนวนมาก) คือ (เป็น) ปัญหาที่สำคัญที่สุดในพีชคณิตเชิงเส้น วิศวกรได้แก้ไขปัญหาเหล่านี้กับคอมพิวเตอร์มาตั้งแต่ต้น
ฉันไม่ใช่ผู้เชี่ยวชาญเกี่ยวกับประวัติ แต่เห็นได้ชัดว่าทุกคนเพิ่งเขียนเวอร์ชัน FORTRAN ของเขาใหม่ด้วยลูปเรียบง่าย มาตรฐานบางอย่างก็มาพร้อมกับการระบุ "เมล็ด" (การปฏิบัติขั้นพื้นฐาน) ว่าปัญหาพีชคณิตเชิงเส้นส่วนใหญ่ที่จำเป็นเพื่อที่จะได้รับการแก้ไข การดำเนินงานขั้นพื้นฐานเหล่านี้ได้รับการมาตรฐานในข้อกำหนดที่เรียกว่า: โปรแกรมพีชคณิตเชิงเส้นพื้นฐาน (BLAS) วิศวกรสามารถเรียกรูทีน BLAS มาตรฐานเหล่านี้ที่ได้รับการทดสอบเป็นอย่างดีในโค้ดทำให้การทำงานง่ายขึ้นมาก
หน่าย:
BLAS วิวัฒนาการมาจากระดับ 1 (เวอร์ชันแรกที่กำหนดการดำเนินการสเกลาร์ - เวกเตอร์และเวกเตอร์ - เวกเตอร์) เป็นระดับ 2 (การดำเนินงานเวกเตอร์เมทริกซ์) ถึงระดับ 3 (การดำเนินการเมทริกซ์เมทริกซ์) และให้ "เมล็ด" มากขึ้น และอื่น ๆ ของการดำเนินงานพีชคณิตเชิงเส้นพื้นฐาน เดิม FORTRAN 77 การใช้งานยังคงมีอยู่ในเว็บไซต์ของ netlib
เพื่อประสิทธิภาพที่ดีขึ้น:
ดังนั้นในช่วงหลายปีที่ผ่านมา (โดยเฉพาะอย่างยิ่งระหว่างรุ่น BLAS ระดับ 1 และระดับ 2: ช่วงต้นยุค 80) ฮาร์ดแวร์เปลี่ยนไปเมื่อมีการปฏิบัติการของเวกเตอร์และลำดับชั้นแคช การวิวัฒนาการเหล่านี้ทำให้สามารถเพิ่มประสิทธิภาพของรูทีนย่อย BLAS ได้อย่างมาก จากนั้นผู้ค้าหลายรายก็มาพร้อมกับการใช้งานประจำของ BLAS ซึ่งมีประสิทธิภาพมากขึ้น
ฉันไม่รู้การใช้งานทางประวัติศาสตร์ทั้งหมด (ฉันไม่ได้เกิดหรือเป็นเด็ก) แต่มีสิ่งที่น่าสังเกตมากที่สุดสองอย่างในช่วงต้นยุค 2000: Intel MKL และ GotoBLAS Matlab ของคุณใช้ Intel MKL ซึ่งเป็น BLAS ที่ดีมากและได้รับการปรับปรุงและอธิบายประสิทธิภาพที่ยอดเยี่ยมที่คุณเห็น
รายละเอียดทางเทคนิคเกี่ยวกับการคูณเมทริกซ์:
เหตุใด Matlab (MKL) จึงรวดเร็วที่dgemm
(การคูณเมทริกซ์เมทริกซ์ทั่วไปความแม่นยำสองเท่า) ในแง่ง่าย: เพราะมันใช้ vectorization และการแคชข้อมูลที่ดี ในแง่ที่ซับซ้อนยิ่งขึ้น: ดูบทความที่จัดทำโดย Jonathan Moore
โดยทั่วไปเมื่อคุณทำการคูณในรหัส C ++ ที่คุณให้ไว้คุณจะไม่เป็นมิตรกับแคชเลย เนื่องจากฉันสงสัยว่าคุณสร้างอาร์เรย์ของพอยน์เตอร์ไปยังแถวอาเรย์การเข้าถึงในลูปภายในของคุณไปยังคอลัมน์ k-th ของ "matice2": matice2[m][k]
ช้ามาก แน่นอนเมื่อคุณเข้าถึงmatice2[0][k]
คุณจะต้องได้องค์ประกอบ k-th ของอาร์เรย์ 0 ของเมทริกซ์ของคุณ จากนั้นในการทำซ้ำครั้งถัดไปคุณต้องเข้าถึงmatice2[1][k]
ซึ่งเป็นองค์ประกอบ k-th ของอาร์เรย์อื่น (อาร์เรย์ 1) จากนั้นในการทำซ้ำครั้งต่อไปคุณเข้าถึงอีกอาร์เรย์หนึ่งและอื่น ๆ ... เนื่องจากเมทริกซ์ทั้งหมดmatice2
ไม่เหมาะกับแคชสูงสุด (มันมี8*1024*1024
ขนาดใหญ่เป็นไบต์) โปรแกรมจะต้องดึงองค์ประกอบที่ต้องการจากหน่วยความจำหลักเสียมาก เวลา.
หากคุณเพิ่งย้ายเมทริกซ์เพื่อที่การเข้าถึงจะอยู่ในหน่วยความจำที่อยู่ติดกันรหัสของคุณก็จะทำงานได้เร็วขึ้นมากเพราะตอนนี้คอมไพเลอร์สามารถโหลดทั้งแถวในแคชได้ในเวลาเดียวกัน ลองใช้รุ่นที่แก้ไขนี้:
timer.start();
float temp = 0;
//transpose matice2
for (int p = 0; p < rozmer; p++)
{
for (int q = 0; q < rozmer; q++)
{
tempmat[p][q] = matice2[q][p];
}
}
for(int j = 0; j < rozmer; j++)
{
for (int k = 0; k < rozmer; k++)
{
temp = 0;
for (int m = 0; m < rozmer; m++)
{
temp = temp + matice1[j][m] * tempmat[k][m];
}
matice3[j][k] = temp;
}
}
timer.stop();
ดังนั้นคุณจะเห็นได้ว่าเพียงแค่สถานที่แคชเพิ่มประสิทธิภาพรหัสของคุณค่อนข้างมาก ตอนนี้dgemm
การใช้งานจริงใช้ประโยชน์จากมันในระดับที่กว้างขวางมาก: พวกเขาทำการคูณบนบล็อกของเมทริกซ์ที่กำหนดโดยขนาดของ TLB (บัฟเฟอร์ lookaside การแปลเรื่องสั้นเรื่องยาว: สิ่งที่แคชได้อย่างมีประสิทธิภาพ) เพื่อให้สตรีมไปยังโปรเซสเซอร์ จำนวนข้อมูลที่สามารถดำเนินการได้อย่างแน่นอน อีกแง่มุมหนึ่งคือการทำให้เป็นเวกเตอร์โดยใช้คำแนะนำแบบเวกเตอร์ของโปรเซสเซอร์เพื่อการประมวลผลคำสั่งที่เหมาะสมซึ่งคุณไม่สามารถทำได้จากรหัส C ++ ข้ามแพลตฟอร์มของคุณ
ในที่สุดผู้คนที่อ้างว่าเป็นเพราะอัลกอริทึมของ Strassen หรือ Coppersmith – Winograd ผิดทั้งอัลกอริธึมเหล่านี้ไม่สามารถนำไปใช้ในทางปฏิบัติได้เนื่องจากการพิจารณาด้านฮาร์ดแวร์