คำถามนี้เป็นส่วนขยายของการสนทนาสองครั้งที่เกิดขึ้นเมื่อเร็ว ๆ นี้ในการตอบกลับไปยัง " C ++ vs Fortran สำหรับ HPC " และมันเป็นความท้าทายมากกว่าคำถาม ...
หนึ่งในข้อโต้แย้งที่ได้ยินบ่อยที่สุดในความโปรดปรานของ Fortran คือคอมไพเลอร์ดีกว่า เนื่องจากคอมไพล์เลอร์ C / Fortran ส่วนใหญ่ใช้แบ็คเอนด์เดียวกันรหัสที่สร้างขึ้นสำหรับโปรแกรมที่เทียบเท่ากันทางความหมายในทั้งสองภาษาจึงควรเหมือนกัน เราอาจโต้แย้งได้ว่า C / Fortran นั้นคอมไพเลอร์เพื่อเพิ่มประสิทธิภาพมากขึ้น / น้อยลง
ดังนั้นฉันจึงตัดสินใจลองทดสอบง่ายๆ: ฉันได้รับสำเนาของdaxpy.fและdaxpy.cและรวบรวมพวกเขาด้วย gfortran / gcc
ตอนนี้ daxpy.c เป็นเพียงการแปล f2c ของ daxpy.f (โค้ดที่สร้างขึ้นโดยอัตโนมัติน่าเกลียดเหมือน heck) ดังนั้นฉันจึงเอาโค้ดนั้นและทำความสะอาดมันเล็กน้อย (พบ daxpy_c) ซึ่งโดยทั่วไปหมายถึงการเขียนลูปด้านในสุดเป็น
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
สุดท้ายฉันเขียนมันอีกครั้ง (ป้อน daxpy_cvec) โดยใช้ไวยากรณ์เวกเตอร์ของ gcc:
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
โปรดทราบว่าฉันใช้เวกเตอร์ที่มีความยาว 2 (นั่นคือทั้งหมดที่ SSE2 อนุญาต) และฉันจะประมวลผลเวกเตอร์สองตัวพร้อมกัน นี่เป็นเพราะในสถาปัตยกรรมจำนวนมากเราอาจมีหน่วยการคูณมากกว่าที่เรามีองค์ประกอบแบบเวกเตอร์
รหัสทั้งหมดได้รับการคอมไพล์โดยใช้ gfortran / gcc เวอร์ชั่น 4.5 พร้อมกับแฟล็ก "-O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing" บนแล็ปท็อปของฉัน (Intel Core i5 CPU, M560, 2.67GHz) ฉันได้ผลลัพธ์ต่อไปนี้:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
ดังนั้นรหัส Fortran ดั้งเดิมใช้เวลามากกว่า 8.1 วินาทีการแปลอัตโนมัติจะใช้เวลา 10.5 วินาทีการใช้งาน C แบบไร้เดียงสาทำใน 7.9 และรหัส vectorized อย่างชัดเจนทำใน 5.6 น้อยกว่าเล็กน้อย
นั่นคือ Fortran ช้ากว่าการใช้ C ไร้เดียงสาเล็กน้อยและช้ากว่าการปรับใช้ C เวกเตอร์ 50%
ดังนั้นนี่คือคำถาม: ฉันเป็นโปรแกรมเมอร์ C พื้นเมืองและฉันค่อนข้างมั่นใจว่าฉันทำงานได้ดีกับรหัสนั้น แต่รหัส Fortran ถูกสัมผัสครั้งสุดท้ายในปี 1993 และอาจล้าสมัยไปเล็กน้อย เนื่องจากฉันรู้สึกไม่สบายใจในการเขียนโปรแกรม Fortran เหมือนกับที่คนอื่น ๆ อาจทำได้ทุกคนสามารถทำงานได้ดีกว่านั่นคือสามารถแข่งขันได้มากกว่าเมื่อเทียบกับรุ่น C สองรุ่นใด ๆ
ทุกคนสามารถลองทดสอบนี้ด้วย icc / ifort ได้หรือไม่? ไวยากรณ์ของเวกเตอร์อาจไม่ทำงาน แต่ฉันอยากรู้ว่ารุ่น C ไร้เดียงสาทำงานอย่างไร กันไปสำหรับใครกับ xlc / xlf นอนอยู่รอบ ๆ
ฉันได้อัปโหลดแหล่งที่มาและ Makefile ที่นี่ หากต้องการกำหนดเวลาที่แม่นยำให้ตั้ง CPU_TPS ใน test.c เป็นจำนวน Hz บน CPU ของคุณ หากคุณพบว่าการปรับปรุงใด ๆ ของรุ่นใด ๆ โปรดโพสต์ไว้ที่นี่!
ปรับปรุง:
ฉันได้เพิ่มรหัสทดสอบของ stali ลงในไฟล์ออนไลน์และเสริมด้วยเวอร์ชัน C ฉันปรับเปลี่ยนโปรแกรมให้ทำ 1'000'000 ลูปบนเวกเตอร์ที่มีความยาว 10'000 ให้สอดคล้องกับการทดสอบก่อนหน้า (และเนื่องจากเครื่องของฉันไม่สามารถจัดสรรเวกเตอร์ที่มีความยาว 1'000'000'000 เหมือนในต้นฉบับของ stali รหัส). เนื่องจากตัวเลขมีขนาดเล็กลงเล็กน้อยฉันจึงใช้ตัวเลือก-par-threshold:50
เพื่อทำให้คอมไพเลอร์มีแนวโน้มที่จะขนานกันมากขึ้น เวอร์ชัน icc / ifort ที่ใช้คือ 12.1.2 20111128 และผลลัพธ์มีดังนี้
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
โดยสรุปแล้วผลลัพธ์นั้นสำหรับวัตถุประสงค์ในทางปฏิบัติทั้งหมดเหมือนกันสำหรับทั้งรุ่น C และ Fortran และรหัสทั้งคู่ขนานกันโดยอัตโนมัติ โปรดทราบว่าเวลาที่รวดเร็วเมื่อเทียบกับการทดสอบก่อนหน้านี้เกิดจากการใช้เลขทศนิยมที่มีความแม่นยำเดียว!
ปรับปรุง:
ถึงแม้ว่าผมจะไม่ชอบที่ภาระการพิสูจน์ที่เกิดขึ้นที่นี่ผมได้อีกครั้งรหัส STALI ของตัวอย่างคูณเมทริกซ์ใน C และเพิ่มไปยังไฟล์บนเว็บ นี่คือผลลัพธ์ของการวนรอบ tripple สำหรับหนึ่งและสอง CPU:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
โปรดทราบว่าcpu_time
ใน Fortran วัดเวลา CPU และไม่ใช่เวลานาฬิกาแขวนดังนั้นฉันจึงปิดการโทรtime
เพื่อเปรียบเทียบพวกเขาสำหรับ 2 CPU ไม่มีความแตกต่างที่แท้จริงระหว่างผลลัพธ์ยกเว้นว่ารุ่น C ทำงานได้ดีกว่าในสองคอร์เล็กน้อย
ตอนนี้สำหรับmatmul
คำสั่งแน่นอนเฉพาะใน Fortran เนื่องจากไม่สามารถใช้งานได้ใน C:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
ว้าว. มันแย่มากจริงๆ ใครสามารถค้นพบสิ่งที่ฉันทำผิดหรืออธิบายได้ว่าทำไมสิ่งที่แท้จริงนี้ยังคงเป็นสิ่งที่ดี?
ฉันไม่ได้เพิ่มการdgemm
เรียกไปยังเกณฑ์มาตรฐานเนื่องจากเป็นการเรียกไลบรารีไปยังฟังก์ชันเดียวกันใน Intel MKL
สำหรับการทดสอบในอนาคตใครสามารถแนะนำตัวอย่างที่ทราบว่าช้ากว่าใน C มากกว่าใน Fortran หรือไม่?
ปรับปรุง
เพื่อยืนยันการเรียกร้องของ stali ว่าmatmul
intrinsic คือ "คำสั่งของ magnitue" เร็วกว่าผลิตภัณฑ์เมทริกซ์ที่ชัดเจนในเมทริกซ์ที่มีขนาดเล็กฉันปรับเปลี่ยนรหัสของตัวเองเพื่อคูณเมทริกซ์ขนาด 100x100 โดยใช้ทั้งสองวิธีละ 10,000 ครั้ง ผลลัพธ์บน CPU หนึ่งและสองมีดังนี้:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
ปรับปรุง
Grisu นั้นถูกต้องในการชี้ให้เห็นว่าโดยไม่ต้องปรับให้เหมาะสม gcc จะแปลงการดำเนินการในจำนวนที่ซับซ้อนเป็นการเรียกฟังก์ชั่นของห้องสมุดในขณะที่ gfortran อินไลน์พวกเขาในคำแนะนำไม่กี่
คอมไพเลอร์ C จะสร้างโค๊ดรหัสเดียวกันหาก-fcx-limited-range
ตั้งค่าตัวเลือกไว้เช่นคอมไพเลอร์ได้รับคำสั่งให้ละเว้นค่าศักย์ไฟฟ้าสูง / ต่ำ - ต่ำในค่ากลาง ตัวเลือกนี้มีการตั้งค่าอย่างใดโดยค่าเริ่มต้นใน gfortran และอาจนำไปสู่ผลลัพธ์ที่ไม่ถูกต้อง การบังคับใช้-fno-cx-limited-range
ใน gfortran ไม่ได้เปลี่ยนแปลงอะไรเลย
ดังนั้นนี่จึงเป็นข้อโต้แย้งที่ต่อต้านการใช้ gfortran สำหรับการคำนวณเชิงตัวเลข: การดำเนินการกับค่าที่ซับซ้อนอาจเกิน / ต่ำกว่าการไหลแม้ว่าผลลัพธ์ที่ถูกต้องจะอยู่ในช่วงจุดลอยตัวก็ตาม นี่เป็นมาตรฐานของ Fortran ใน gcc หรือโดยทั่วไปแล้ว C99 ค่าเริ่มต้นคือการทำสิ่งต่าง ๆ อย่างเคร่งครัด (อ่านตามมาตรฐาน IEEE-754) เว้นแต่จะระบุไว้เป็นอย่างอื่น
คำเตือน:โปรดจำไว้ว่าคำถามหลักคือคอมไพเลอร์ของ Fortran ผลิตโค้ดได้ดีกว่าคอมไพเลอร์ C หรือไม่ นี่ไม่ใช่สถานที่สำหรับการอภิปรายเกี่ยวกับข้อดีทั่วไปของภาษาหนึ่งมากกว่าอีกภาษาหนึ่ง สิ่งที่ฉันสนใจจริง ๆ คือถ้าใครสามารถหาวิธีเกลี้ยกล่อม gfortran เพื่อสร้าง daxpy ที่มีประสิทธิภาพเทียบเท่ากับ C โดยใช้ vectorization อย่างชัดเจนเพราะนี่เป็นตัวอย่างของปัญหาที่ต้องพึ่งพาคอมไพเลอร์สำหรับการเพิ่มประสิทธิภาพ SIMD หรือ ในกรณีที่คอมไพเลอร์ Fortran ทำหน้าที่ C คู่กัน