BLAS มีประสิทธิภาพสูงสุดได้อย่างไร?


108

ด้วยความอยากรู้อยากเห็นฉันตัดสินใจที่จะเปรียบเทียบฟังก์ชันการคูณเมทริกซ์ของฉันเองเทียบกับการใช้งาน BLAS ... ฉันต้องบอกว่าอย่างน้อยก็ประหลาดใจกับผลลัพธ์:

การปรับใช้แบบกำหนดเองการทดลอง 10 ครั้งของการคูณเมทริกซ์ 1000x1000:

Took: 15.76542 seconds.

การใช้งาน BLAS การทดลอง 10 ครั้งของการคูณเมทริกซ์ 1000x1000:

Took: 1.32432 seconds.

นี่คือการใช้ตัวเลขทศนิยมตำแหน่งเดียวที่มีความแม่นยำสูง

การใช้งานของฉัน:

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
    if ( ADim2!=BDim1 )
        throw std::runtime_error("Error sizes off");

    memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
    int cc2,cc1,cr1;
    for ( cc2=0 ; cc2<BDim2 ; ++cc2 )
        for ( cc1=0 ; cc1<ADim2 ; ++cc1 )
            for ( cr1=0 ; cr1<ADim1 ; ++cr1 )
                C[cc2*ADim2+cr1] += A[cc1*ADim1+cr1]*B[cc2*BDim1+cc1];
}

ฉันมีสองคำถาม:

  1. เนื่องจากการคูณเมทริกซ์ - เมทริกซ์กล่าวว่า nxm * mxn ต้องการการคูณ n * n * m ดังนั้นในกรณีที่มีการดำเนินการมากกว่า 1,000 ^ 3 หรือ 1e9 เป็นไปได้อย่างไรที่โปรเซสเซอร์ 2.6Ghz ของฉันสำหรับ BLAS จะดำเนินการ 10 * 1e9 ใน 1.32 วินาที แม้ว่าการคูณจะเป็นการดำเนินการเดียวและไม่มีการดำเนินการใด ๆ อีก แต่ควรใช้เวลาประมาณ 4 วินาที
  2. เหตุใดการใช้งานของฉันจึงช้าลงมาก

17
BLAS ได้รับการปรับให้เหมาะสมขึ้นด้านหนึ่งและลงอีกด้านหนึ่งโดยผู้เชี่ยวชาญในสาขานั้น ๆ ฉันคิดว่ามันกำลังใช้ประโยชน์จากหน่วยจุดลอยตัว SIMD บนชิปของคุณและเล่นเทคนิคมากมายเพื่อปรับปรุงพฤติกรรมการแคชเช่นกัน ...
dmckee --- อดีตผู้ดูแลลูกแมว

3
คุณยังดำเนินการ 1E10 บนโปรเซสเซอร์ 2.63E9 รอบ / วินาทีใน 1.3 วินาทีได้อย่างไร?
DeusAduro

9
หน่วยประมวลผลหลายตัวซับท่อและ Single Instruction Multiple Data ((SIMD) ซึ่งหมายถึงการดำเนินการเดียวกันกับตัวถูกดำเนินการมากกว่าหนึ่งคู่ในเวลาเดียวกัน) คอมไพเลอร์บางตัวสามารถกำหนดเป้าหมายหน่วย SIMD บนชิปทั่วไปได้ แต่คุณต้องเปิดเครื่องอย่างชัดเจนเสมอและจะช่วยให้ทราบว่ามันทำงานอย่างไร ( en.wikipedia.org/wiki/SIMD ) การประกันการพลาดแคชเป็นส่วนที่ยาก
dmckee --- อดีตผู้ดูแลลูกแมว

13
อัสสัมชัญผิด มีอัลกอริทึมที่รู้จักกันดีกว่าโปรดดู Wikipedia
MSalters

2
@DeusAduro: ในคำตอบของฉันสำหรับวิธีการเขียนผลิตภัณฑ์เมทริกซ์เมทริกซ์ที่สามารถแข่งขันกับ Eigen ได้? ฉันโพสต์ตัวอย่างเล็กน้อยเกี่ยวกับวิธีการใช้ผลิตภัณฑ์เมทริกซ์เมทริกซ์ที่มีประสิทธิภาพแคช
Michael Lehn

คำตอบ:


141

จุดเริ่มต้นที่ดีคือหนังสือThe Science of Programming Matrix Computationsโดย Robert A. van de Geijn และ Enrique S. Quintana-Ortí มีเวอร์ชันดาวน์โหลดฟรี

BLAS แบ่งออกเป็นสามระดับ:

  • ระดับ 1 กำหนดชุดของฟังก์ชันพีชคณิตเชิงเส้นที่ทำงานบนเวกเตอร์เท่านั้น ฟังก์ชันเหล่านี้ได้รับประโยชน์จาก vectorization (เช่นจากการใช้ SSE)

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

  • ฟังก์ชันระดับ 3 เป็นการดำเนินการเช่นเดียวกับผลิตภัณฑ์เมทริกซ์ - เมทริกซ์ อีกครั้งคุณสามารถใช้งานได้ในแง่ของฟังก์ชัน Level2 แต่ฟังก์ชัน Level3 จะดำเนินการ O (N ^ 3) บนข้อมูล O (N ^ 2) ดังนั้นหากแพลตฟอร์มของคุณมีลำดับชั้นแคชแล้วคุณสามารถเพิ่มประสิทธิภาพการทำงานถ้าคุณให้การดำเนินงานเฉพาะที่เป็นแคช Optimized / แคชมิตร มีอธิบายไว้อย่างดีในหนังสือเล่มนี้ การเพิ่มประสิทธิภาพหลักของฟังก์ชั่น Level3 มาจากการเพิ่มประสิทธิภาพแคช การเพิ่มนี้สูงกว่าการเพิ่มครั้งที่สองจากการทำงานแบบขนานและการเพิ่มประสิทธิภาพฮาร์ดแวร์อื่น ๆ อย่างมาก

อย่างไรก็ตามการใช้งาน BLAS ประสิทธิภาพสูงส่วนใหญ่ (หรือทั้งหมด) จะไม่ถูกนำไปใช้ใน Fortran ATLAS ถูกนำไปใช้ใน C. GotoBLAS / OpenBLAS ถูกนำไปใช้ใน C และส่วนที่สำคัญด้านประสิทธิภาพใน Assembler เฉพาะการใช้งานอ้างอิงของ BLAS เท่านั้นที่ถูกนำไปใช้ใน Fortran อย่างไรก็ตามการใช้งาน BLAS ทั้งหมดนี้มีอินเทอร์เฟซ Fortran ที่สามารถเชื่อมโยงกับ LAPACK ได้ (LAPACK ได้รับประสิทธิภาพทั้งหมดจาก BLAS)

คอมไพเลอร์ที่ปรับให้เหมาะสมมีบทบาทเล็กน้อยในแง่นี้ (และสำหรับ GotoBLAS / OpenBLAS คอมไพเลอร์ไม่สำคัญเลย)

IMHO ไม่มีการใช้งาน BLAS ใช้อัลกอริทึมเช่น Coppersmith – Winograd algorithm หรือ Strassen algorithm ฉันไม่แน่ใจเกี่ยวกับเหตุผล แต่นี่คือการคาดเดาของฉัน:

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

แก้ไข / ปรับปรุง:

ใหม่และพื้นดินกระดาษทำลายสำหรับหัวข้อนี้เป็นเอกสาร BLIS พวกเขาเขียนได้ดีเป็นพิเศษ สำหรับการบรรยาย "Software Basics for High Performance Computing" ของฉันฉันใช้ผลิตภัณฑ์เมทริกซ์ - เมทริกซ์ตามกระดาษของพวกเขา อันที่จริงฉันใช้ผลิตภัณฑ์เมทริกซ์ - เมทริกซ์หลายรูปแบบ ตัวแปรที่ง่ายที่สุดเขียนด้วยภาษา C ล้วนและมีโค้ดน้อยกว่า 450 บรรทัด ตัวแปรอื่น ๆ ทั้งหมดเพียงแค่ปรับลูปให้เหมาะสมเท่านั้น

    for (l=0; l<MR*NR; ++l) {
        AB[l] = 0;
    }
    for (l=0; l<kc; ++l) {
        for (j=0; j<NR; ++j) {
            for (i=0; i<MR; ++i) {
                AB[i+j*MR] += A[i]*B[j];
            }
        }
        A += MR;
        B += NR;
    }

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

ulmBLAS: บทช่วยสอนเกี่ยวกับ GEMM (ผลิตภัณฑ์เมทริกซ์ - เมทริกซ์)

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

เกณฑ์มาตรฐานสุดท้ายอยู่ที่นี่ (เราเรียกว่าโครงการของเรา ulmBLAS):

เกณฑ์มาตรฐานสำหรับ ulmBLAS, BLIS, MKL, openBLAS และ Eigen

แก้ไข / ปรับปรุงอื่น ๆ :

ฉันยังเขียนบทช่วยสอนเกี่ยวกับวิธีใช้ BLAS สำหรับปัญหาพีชคณิตเชิงเส้นเชิงตัวเลขเช่นการแก้ระบบสมการเชิงเส้น:

LU Factorization ประสิทธิภาพสูง

(การแยกตัวประกอบ LU นี้เป็นตัวอย่างที่ Matlab ใช้ในการแก้ระบบสมการเชิงเส้น)

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

ได้เลย: Coding a Cache Optimized Parallel LU Factorization

PS: ฉันได้ทำการทดลองเกี่ยวกับการปรับปรุงประสิทธิภาพของ uBLAS ด้วย จริงๆแล้วมันค่อนข้างง่ายที่จะเพิ่มประสิทธิภาพ (ใช่เล่นคำ :)) ประสิทธิภาพของ uBLAS:

การทดลองใน uBLAS

นี่คือโครงการที่คล้ายกันกับBLAZE :

การทดลองใน BLAZE


3
ลิงก์ใหม่ไปยัง“ เกณฑ์มาตรฐานสำหรับ ulmBLAS, BLIS, MKL, openBLAS และ Eigen”: apfel.mathematik.uni-ulm.de/~lehn/ulmBLAS/#toc3
Ahmed Fasih

ปรากฎว่า ESSL ของ IBM ใช้อัลกอริทึม Strassen รูปแบบใหม่ - ibm.com/support/knowledgecenter/en/SSFHY8/essl_welcome.html
ben-albrecht

2
ลิงก์ส่วนใหญ่ตายไปแล้ว
Aurélien Pierre

ไฟล์ PDF ของ TSoPMC สามารถพบได้ในหน้าของผู้เขียนที่cs.utexas.edu/users/rvdg/tmp/TSoPMC.pdf
Alex Shpilkin

แม้ว่าอัลกอริทึม Coppersmith-Winograd จะมีความซับซ้อนของเวลาที่ดีบนกระดาษ แต่สัญกรณ์ Big O จะซ่อนค่าคงที่ที่มีขนาดใหญ่มากดังนั้นจึงเริ่มใช้งานได้สำหรับเมทริกซ์ขนาดใหญ่ที่น่าขันเท่านั้น
Nihar Karve

26

ก่อนอื่น BLAS เป็นเพียงอินเทอร์เฟซประมาณ 50 ฟังก์ชัน มีการใช้งานอินเทอร์เฟซที่แข่งขันกันมากมาย

ประการแรกฉันจะพูดถึงสิ่งที่ไม่เกี่ยวข้องกันมาก:

  • Fortran กับ C ไม่แตกต่างกัน
  • อัลกอริทึมเมทริกซ์ขั้นสูงเช่น Strassen การใช้งานอย่าใช้มันเนื่องจากไม่ช่วยในทางปฏิบัติ

การนำไปใช้งานส่วนใหญ่แบ่งการดำเนินการแต่ละอย่างออกเป็นเมทริกซ์ขนาดเล็กหรือการดำเนินการเวกเตอร์ด้วยวิธีที่ชัดเจนมากขึ้นหรือน้อยลง ตัวอย่างเช่นการคูณเมทริกซ์ขนาดใหญ่ 1000x1000 อาจแบ่งออกเป็นลำดับของการคูณเมทริกซ์ 50x50

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

  • คำแนะนำสไตล์ SIMD
  • ระดับคำสั่ง Parallelism
  • การรับรู้แคช

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

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

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


ATLAS ไม่ใช่การใช้งาน BLAS แบบโอเพนซอร์สที่ใช้บ่อยที่สุดอีกต่อไป OpenBLAS (ทางแยกของ GotoBLAS) และ BLIS (การปรับโครงสร้างของ GotoBLAS)
Robert van de Geijn

1
@ ulaff.net: นั่นอาจจะเป็น นี่เขียนเมื่อ 6 ปีที่แล้ว ฉันคิดว่าการติดตั้ง BLAS ที่เร็วที่สุดในปัจจุบัน (ใน Intel) คือ Intel MKL แต่ไม่ใช่โอเพ่นซอร์ส
Andrew Tomazos

14

ประการแรกมีอัลกอริทึมที่มีประสิทธิภาพสำหรับการคูณเมทริกซ์มากกว่าวิธีที่คุณใช้

ประการที่สอง CPU ของคุณสามารถทำคำสั่งได้มากกว่าหนึ่งคำสั่งในแต่ละครั้ง

CPU ของคุณจะรันคำสั่ง 3-4 คำสั่งต่อรอบและหากใช้หน่วย SIMD แต่ละคำสั่งจะประมวลผล 4 โฟลหรือ 2 คู่ (แน่นอนว่าตัวเลขนี้ไม่ถูกต้องเช่นกันเนื่องจาก CPU สามารถประมวลผลคำสั่ง SIMD ได้เพียงหนึ่งคำสั่งต่อรอบ)

ประการที่สามรหัสของคุณยังห่างไกลจากความเหมาะสม:

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

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

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

2
อย่างน้อยวงในที่นี่ก็หลีกเลี่ยงการบรรทุกแบบก้าวกระโดด ดูเหมือนว่านี่จะถูกเขียนขึ้นสำหรับหนึ่งเมทริกซ์ที่มีการเปลี่ยนภาพแล้ว นั่นเป็นเหตุผลว่าทำไมจึงมีขนาด "เพียง" หนึ่งลำดับที่ช้ากว่า BLAS! แต่ใช่มันยังคงเต้นแรงเนื่องจากไม่มีการปิดกั้นแคช แน่ใจหรือว่า Fortran จะช่วยได้มาก ฉันคิดว่าสิ่งที่คุณจะได้รับที่นี่คือrestrict(ไม่มีนามแฝง) เป็นค่าเริ่มต้นซึ่งแตกต่างจากใน C / C ++ (และน่าเสียดายที่ ISO C ++ ไม่มีrestrictคีย์เวิร์ดดังนั้นคุณต้องใช้__restrict__กับคอมไพเลอร์ที่ให้เป็นส่วนขยาย)
Peter Cordes

11

ฉันไม่รู้โดยละเอียดเกี่ยวกับการใช้งาน BLAS แต่มี alogorithms ที่มีประสิทธิภาพมากกว่าสำหรับการคูณเมทริกซ์ที่มีความซับซ้อนดีกว่า O (n3) หนึ่งที่รู้จักกันดีคือStrassen Algorithm


8
อัลกอริทึม Strassen ไม่ได้ใช้ในตัวเลขด้วยเหตุผลสองประการ: 1) ไม่เสถียร 2) คุณบันทึกการคำนวณบางอย่าง แต่มาพร้อมกับราคาที่คุณสามารถใช้ประโยชน์จากลำดับชั้นแคชได้ ในทางปฏิบัติคุณยังหลวมประสิทธิภาพ
Michael Lehn

4
สำหรับการนำไปใช้งานจริงของ Strassen Algorithm ที่สร้างขึ้นอย่างแน่นหนาจากซอร์สโค้ดของไลบรารี BLAS มีการเผยแพร่ล่าสุด: " Strassen Algorithm Reloaded " ใน SC16 ซึ่งมีประสิทธิภาพสูงกว่า BLAS แม้จะมีขนาดปัญหา 1000x1000 ก็ตาม
Jianyu Huang

4

อาร์กิวเมนต์ส่วนใหญ่ของคำถามที่สอง - แอสเซมเบลอร์การแบ่งออกเป็นบล็อกเป็นต้น (แต่ไม่น้อยกว่าอัลกอริทึม N ^ 3 พวกเขาได้รับการพัฒนามากเกินไป) - มีบทบาท แต่อัลกอริทึมที่มีความเร็วต่ำนั้นเกิดจากขนาดเมทริกซ์และการจัดเรียงของลูปที่ซ้อนกันสามอัน เมทริกซ์ของคุณมีขนาดใหญ่มากจนไม่พอดีกับหน่วยความจำแคชในครั้งเดียว คุณสามารถจัดเรียงลูปใหม่ให้มากที่สุดเท่าที่จะทำได้บนแถวในแคชวิธีนี้จะช่วยลดการรีเฟรชแคชได้อย่างมาก (การแบ่ง BTW ออกเป็นบล็อกเล็ก ๆ จะมีเอฟเฟกต์แบบอะนาล็อกซึ่งดีที่สุดหากจัดเรียงลูปเหนือบล็อกในลักษณะเดียวกัน) การใช้แบบจำลองสำหรับเมทริกซ์แบบสี่เหลี่ยมมีดังนี้ ในคอมพิวเตอร์ของฉันใช้เวลาประมาณ 1:10 เมื่อเทียบกับการใช้งานมาตรฐาน (ของคุณ) กล่าวอีกนัยหนึ่ง: อย่าตั้งโปรแกรมการคูณเมทริกซ์ตาม "

    void vector(int m, double ** a, double ** b, double ** c) {
      int i, j, k;
      for (i=0; i<m; i++) {
        double * ci = c[i];
        for (k=0; k<m; k++) ci[k] = 0.;
        for (j=0; j<m; j++) {
          double aij = a[i][j];
          double * bj = b[j];
          for (k=0; k<m; k++)  ci[k] += aij*bj[k];
        }
      }
    }

อีกหนึ่งข้อสังเกต: การใช้งานนี้ดียิ่งขึ้นบนคอมพิวเตอร์ของฉันมากกว่าการแทนที่ทั้งหมดด้วย cblas_dgemm ประจำ BLAS (ลองใช้กับคอมพิวเตอร์ของคุณ!) แต่เร็วกว่ามาก (1: 4) กำลังเรียก dgemm_ ของไลบรารี Fortran โดยตรง ฉันคิดว่ากิจวัตรนี้ไม่ใช่ Fortran แต่เป็นรหัสแอสเซมเบลอร์ (ฉันไม่รู้ว่ามีอะไรอยู่ในไลบรารีฉันไม่มีแหล่งที่มา) ฉันไม่ชัดเจนโดยสิ้นเชิงคือทำไม cblas_dgemm ไม่เร็วเท่าที่ฉันรู้มันเป็นเพียงกระดาษห่อหุ้มสำหรับ dgemm_


3

นี่คือความเร็วที่สมจริง สำหรับตัวอย่างของสิ่งที่สามารถทำได้ด้วยแอสเซมเบลอร์ SIMD ผ่านรหัส C ++ โปรดดูตัวอย่างฟังก์ชันเมทริกซ์ของ iPhone ซึ่งเร็วกว่าเวอร์ชัน C ถึง 8 เท่าและยังไม่ได้ประกอบแบบ "ปรับให้เหมาะสม" - ยังไม่มีการบุท่อและที่นั่น เป็นการดำเนินการกองซ้อนที่ไม่จำเป็น

นอกจากนี้โค้ดของคุณยังไม่ " จำกัด ถูกต้อง " - คอมไพลเลอร์รู้ได้อย่างไรว่าเมื่อแก้ไข C แล้วจะไม่แก้ไข A และ B


แน่ใจว่าคุณเรียกฟังก์ชันเช่น mmult (A ... , A ... , A); คุณจะไม่ได้รับผลลัพธ์ที่คาดหวังอย่างแน่นอน อีกครั้งแม้ว่าฉันจะไม่ได้พยายามเอาชนะ / นำ BLAS มาใช้ใหม่เพียงแค่ดูว่ามันเร็วแค่ไหนดังนั้นการตรวจสอบข้อผิดพลาดจึงไม่ได้อยู่ในใจเพียงแค่ฟังก์ชันพื้นฐานเท่านั้น
DeusAduro

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

@DeusAduro: นี่ไม่ใช่ข้อผิดพลาดในการตรวจสอบ - เป็นไปได้ว่าคอมไพเลอร์ไม่สามารถเพิ่มประสิทธิภาพการเข้าถึงอาร์เรย์ B [] ในลูปด้านในได้เนื่องจากอาจไม่สามารถเข้าใจได้ว่าพอยน์เตอร์ A และ C ไม่เคยใช้แทน B อาร์เรย์ หากมีนามแฝงเป็นไปได้ที่ค่าในอาร์เรย์ B จะเปลี่ยนแปลงในขณะที่วงในกำลังดำเนินการ การยกการเข้าถึงค่า B [] ออกจากวงในและวางไว้ในตัวแปรโลคัลอาจทำให้คอมไพเลอร์หลีกเลี่ยงการเข้าถึง B [] อย่างต่อเนื่อง
Michael Burr

1
อืมฉันจึงลองใช้คีย์เวิร์ด '__restrict' ก่อนใน VS 2008 โดยใช้กับ A, B และ C สิ่งนี้ไม่แสดงให้เห็นการเปลี่ยนแปลงในผลลัพธ์ อย่างไรก็ตามการย้ายการเข้าถึงไปยัง B จากวงในสุดไปยังวงนอกสุดจะช่วยเพิ่มเวลาได้ ~ 10%
DeusAduro

1
ขออภัยผมไม่แน่ใจว่าเกี่ยวกับ VC แต่ด้วย GCC -fstrict-aliasingคุณต้องเปิดใช้งาน นอกจากนี้ยังมีคำอธิบายที่ดีกว่าของ "จำกัด " ที่นี่: cellperformance.beyond3d.com/articles/2006/05/…
Justicle

2

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

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

https://en.wikipedia.org/wiki/Loop_nest_optimization


-24

ด้วยเหตุผลหลายประการ

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

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

ประการที่สามขึ้นอยู่กับการใช้งาน blas ที่คุณใช้ การใช้งานบางอย่างอาจเขียนในแอสเซมเบลอร์และปรับให้เหมาะสมสำหรับโปรเซสเซอร์เฉพาะที่คุณใช้ เวอร์ชัน netlib เขียนด้วยภาษา Fortran 77

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

ตัวอย่างเช่นคุณสามารถทำโค้ดของคุณใหม่ด้วยวิธีนี้

template<class ValT>
void mmult(const ValT* A, int ADim1, int ADim2, const ValT* B, int BDim1, int BDim2, ValT* C)
{
if ( ADim2!=BDim1 ) throw std::runtime_error("Error sizes off");

memset((void*)C,0,sizeof(ValT)*ADim1*BDim2);
int cc2,cc1,cr1, a1,a2,a3;
for ( cc2=0 ; cc2<BDim2 ; ++cc2 ) {
    a1 = cc2*ADim2;
    a3 = cc2*BDim1
    for ( cc1=0 ; cc1<ADim2 ; ++cc1 ) {
          a2=cc1*ADim1;
          ValT b = B[a3+cc1];
          for ( cr1=0 ; cr1<ADim1 ; ++cr1 ) {
                    C[a1+cr1] += A[a2+cr1]*b;
           }
     }
  }
} 

ลองดูฉันแน่ใจว่าคุณจะช่วยอะไรบางอย่างได้

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


36
คำตอบนี้ผิดอย่างสมบูรณ์ขออภัย การใช้งาน BLAS ไม่ได้เขียนเป็นภาษา Fortran โค้ดที่มีประสิทธิภาพ - สำคัญถูกเขียนขึ้นในแอสเซมบลีและรหัสที่พบมากที่สุดในปัจจุบันเขียนด้วย C ด้านบน นอกจากนี้ BLAS ยังระบุลำดับแถว / คอลัมน์เป็นส่วนหนึ่งของอินเทอร์เฟซและการนำไปใช้งานสามารถจัดการชุดค่าผสมใดก็ได้
Andrew Tomazos

10
ใช่คำตอบนี้ผิดอย่างสิ้นเชิง น่าเสียดายที่มันเต็มไปด้วยความไม่สมเหตุสมผลเช่นการอ้างสิทธิ์ BLAS เร็วกว่าเนื่องจาก Fortran การมีคะแนนบวก 20 (!) เป็นสิ่งที่ไม่ดี ตอนนี้ความไม่รู้สึกนี้ยิ่งแพร่กระจายออกไปอีกเพราะความนิยมของ Stackoverflow!
Michael Lehn

12
ฉันคิดว่าคุณกำลังสับสนกับการใช้งานอ้างอิงที่ไม่ได้เพิ่มประสิทธิภาพกับการใช้งานจริง การใช้งานอ้างอิงเป็นเพียงการระบุอินเทอร์เฟซและลักษณะการทำงานของไลบรารีและถูกเขียนใน Fortran ด้วยเหตุผลทางประวัติศาสตร์ ไม่ได้ใช้เพื่อการผลิต ในการผลิตคนใช้การปรับใช้ที่เหมาะสมที่สุดซึ่งแสดงลักษณะการทำงานเช่นเดียวกับการใช้งานอ้างอิง ฉันได้ศึกษาภายในของ ATLAS (ซึ่งอยู่หลัง Octave - Linux "MATLAB") ซึ่งฉันสามารถยืนยันได้ว่ามือแรกเขียนด้วย C / ASM เป็นการภายใน การนำไปใช้ในเชิงพาณิชย์เกือบจะแน่นอนเช่นกัน
Andrew Tomazos

5
@KyleKanos: ใช่นี่คือที่มาของ ATLAS: sourceforge.net/projects/math-atlas/files/Stable/3.10.1 เท่าที่ฉันรู้ว่าเป็นการใช้งาน BLAS แบบพกพาแบบโอเพนซอร์สที่ใช้บ่อยที่สุด มันเขียนใน C / ASM ผู้ผลิตซีพียูประสิทธิภาพสูงเช่น Intel ยังมีการใช้งาน BLAS ที่ปรับให้เหมาะกับชิปโดยเฉพาะ ฉันรับประกันว่าในส่วนระดับต่ำของไลบรารี Intels จะถูกเขียนใน (duuh) x86 assembly และฉันค่อนข้างมั่นใจว่าส่วนระดับกลางจะเขียนด้วย C หรือ C ++
Andrew Tomazos

9
@KyleKanos: คุณกำลังสับสน Netlib BLAS คือการนำไปใช้อ้างอิง การใช้งานอ้างอิงจะช้ากว่าการใช้งานที่ปรับให้เหมาะสมมาก (ดูการเปรียบเทียบประสิทธิภาพ ) เมื่อมีคนบอกว่าพวกเขากำลังใช้ netlib BLAS บนคลัสเตอร์นั่นไม่ได้หมายความว่าพวกเขากำลังใช้การอ้างอิง netlib จริงๆ นั่นจะเป็นเรื่องโง่ หมายความว่าพวกเขาใช้ lib ที่มีอินเทอร์เฟซเดียวกับ netlib blas
Andrew Tomazos
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.