C ช้ากว่าฟอร์แทรนในการยิงบรรทัดฐานสเปกตรัม (ใช้ gcc, intel และคอมไพเลอร์อื่น ๆ ) หรือไม่


13

ข้อสรุปที่นี่:

คอมไพเลอร์ของ Fortran นั้นดีกว่ามากแค่ไหน?

คือ gfortran และ gcc นั้นรวดเร็วสำหรับโค้ดง่าย ๆ ฉันอยากลองอะไรที่ซับซ้อนกว่านี้ ฉันเอาตัวอย่างการยิงบรรทัดฐานสเปกตรัม ฉันก่อนการคำนวณเมทริกซ์ 2 มิติ A (:, :) แล้วคำนวณเกณฑ์ปกติ (วิธีแก้ปัญหานี้ไม่ได้รับอนุญาตในการยิงที่ฉันคิดว่า) ฉันได้ใช้ Fortran และรุ่น C นี่คือรหัส:

https://github.com/certik/spectral_norm

รุ่น gfortran ที่เร็วที่สุดคือ spectral_norm2.f90 และ spectral_norm6.f90 (อันหนึ่งใช้ matmul ในตัวและ dot_product ของ Fortran อีกตัวหนึ่งใช้ฟังก์ชันทั้งสองนี้ในรหัส - โดยไม่มีความแตกต่างด้านความเร็ว) รหัส C / C ++ ที่เร็วที่สุดที่ฉันสามารถเขียนได้คือ spectral_norm7.cpp การจับเวลาของ git รุ่น 457d9d9 บนแล็ปท็อปของฉันคือ:

$ time ./spectral_norm6 5500
1.274224153

real    0m2.675s
user    0m2.520s
sys 0m0.132s


$ time ./spectral_norm7 5500
1.274224153

real    0m2.871s
user    0m2.724s
sys 0m0.124s

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

ใน Fortran ฉันผ่านอาร์เรย์ 2 มิติในขณะที่ใน CI ใช้อาร์เรย์ 1D อย่าลังเลที่จะใช้อาร์เรย์ 2 มิติหรือวิธีอื่นที่คุณเห็นว่าเหมาะสม

สำหรับคอมไพเลอร์ลองเปรียบเทียบ gcc กับ gfortran, icc vs ifort และอื่น ๆ (ไม่เหมือนกับหน้าจุดโทษซึ่งเปรียบเทียบ ifort กับ gcc.)

อัปเดต : ใช้รุ่น 179dae2 ซึ่งปรับปรุง matmul3 () ในเวอร์ชั่น C ของฉันตอนนี้มันเร็วพอ:

$ time ./spectral_norm6 5500
1.274224153

real    0m2.669s
user    0m2.500s
sys 0m0.144s

$ time ./spectral_norm7 5500
1.274224153

real    0m2.665s
user    0m2.472s
sys 0m0.168s

เวอร์ชัน vectorized ของ Pedro ด้านล่างนั้นเร็วกว่า:

$ time ./spectral_norm8 5500
1.274224153

real    0m2.523s
user    0m2.336s
sys 0m0.156s

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


5
ฉันไม่ได้อยู่ใกล้กับคอมไพเลอร์ในตอนนี้ แต่ให้พิจารณาเพิ่มคำหลัก จำกัด ลงในอาร์เรย์ของคุณ นามแฝงของพอยน์เตอร์มักจะเป็นความแตกต่างระหว่างการเรียกใช้ฟังก์ชัน Fortran และ C ในอาร์เรย์ นอกจากนี้ Fortran เก็บหน่วยความจำในลำดับคอลัมน์หลักและ C ในแถวหลัก
moyner

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

@ IRO-bot ฉันซ่อมมัน แจ้งให้เราทราบหากคุณเห็นว่าใช้ได้
OndČejČertík

1
ข้อสรุปจริง ๆ ของ "คอมไพเลอร์ Fortran ดีกว่าจริง ๆ " ไม่ได้ค่อนข้างถูกต้องในหัวข้อนั้น ฉันได้ลองเปรียบเทียบมาตรฐานของ Cray กับ GCC, PGI, CRAY และ Intel compilers และ 3 compilers Fortran นั้นเร็วกว่า C (b / w 5-40%) คอมไพเลอร์เครย์ผลิตรหัส Fortran / C ที่เร็วที่สุด แต่รหัส Fortran นั้นเร็วกว่า 40% ฉันจะโพสต์ผลลัพธ์โดยละเอียดเมื่อฉันได้รับเวลา Btw ทุกคนที่สามารถเข้าถึงเครื่อง Cray สามารถตรวจสอบมาตรฐาน เป็นแพลตฟอร์มที่ดีเนื่องจากมีคอมไพเลอร์ 4-5 รายการและแฟล็กที่เกี่ยวข้องมีการเข้าร่วมโดยอัตโนมัติโดยโปรแกรมห่อหุ้ม ftn / cc
STALI

ตรวจสอบด้วยด้วย pgf95 / pgcc (11.10) ในระบบ Opteron: # 1 และ # 2 เป็นวิธีที่เร็วที่สุด (เร็วกว่า ifort ประมาณ ~ 20%) จากนั้นเป็น # 6, # 8, # 7 (ตามลำดับ) pgf95 เร็วกว่า ifort สำหรับรหัส Fortran ทั้งหมดของคุณและ icpc นั้นเร็วกว่า pgcpp สำหรับ C ทั้งหมด - ฉันควรพูดถึงสิ่งนั้นสำหรับสิ่งที่ฉันมักจะพบ ifort ได้เร็วขึ้นแม้ในระบบ AMD เดียวกัน
laxxy

คำตอบ:


12

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

เพียงเพื่อเตือนผู้มาใหม่เกี่ยวกับสิ่งนี้คืออะไร: หลักฐานพื้นฐานในหัวข้อนี้คือ gcc / fortran และ icc / ifort ควรเนื่องจากพวกเขามี back-end เดียวกันตามลำดับสร้างรหัสที่เทียบเท่าสำหรับโปรแกรมเดียวกัน (semantically เหมือนกัน) โดยไม่คำนึงถึง มันอยู่ใน C หรือ Fortran คุณภาพของผลลัพธ์ขึ้นอยู่กับคุณภาพของการใช้งานที่เกี่ยวข้อง

ฉันเล่นด้วยรหัสเล็กน้อยและบนคอมพิวเตอร์ของฉัน (ThinkPad 201x, Intel Core i5 M560, 2.67 GHz) โดยใช้gcc4.6.1 และธงคอมไพเลอร์ต่อไปนี้:

GCCFLAGS= -O3 -g -Wall -msse2 -march=native -funroll-loops -ffast-math -fomit-frame-pointer -fstrict-aliasing

ฉันยังไปข้างหน้าและเขียนรหัส C ++ ภาษา SIMD-vectorized ของ C ++ spectral_norm_vec.c:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

/* Define the generic vector type macro. */  
#define vector(elcount, type)  __attribute__((vector_size((elcount)*sizeof(type)))) type

double Ac(int i, int j)
{
    return 1.0 / ((i+j) * (i+j+1)/2 + i+1);
}

double dot_product2(int n, double u[], double v[])
{
    double w;
    int i;
    union {
        vector(2,double) v;
        double d[2];
        } *vu = u, *vv = v, acc[2];

    /* Init some stuff. */
    acc[0].d[0] = 0.0; acc[0].d[1] = 0.0;
    acc[1].d[0] = 0.0; acc[1].d[1] = 0.0;

    /* Take in chunks of two by two doubles. */
    for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
        acc[0].v += vu[i].v * vv[i].v;
        acc[1].v += vu[i+1].v * vv[i+1].v;
        }
    w = acc[0].d[0] + acc[0].d[1] + acc[1].d[0] + acc[1].d[1];

    /* Catch leftovers (if any) */
    for ( i = n & ~3 ; i < n ; i++ )
        w += u[i] * v[i];

    return w;

}

void matmul2(int n, double v[], double A[], double u[])
{
    int i, j;
    union {
        vector(2,double) v;
        double d[2];
        } *vu = u, *vA, vi;

    bzero( u , sizeof(double) * n );

    for (i = 0; i < n; i++) {
        vi.d[0] = v[i];
        vi.d[1] = v[i];
        vA = &A[i*n];
        for ( j = 0 ; j < (n/2 & ~1) ; j += 2 ) {
            vu[j].v += vA[j].v * vi.v;
            vu[j+1].v += vA[j+1].v * vi.v;
            }
        for ( j = n & ~3 ; j < n ; j++ )
            u[j] += A[i*n+j] * v[i];
        }

}


void matmul3(int n, double A[], double v[], double u[])
{
    int i;

    for (i = 0; i < n; i++)
        u[i] = dot_product2( n , &A[i*n] , v );

}

void AvA(int n, double A[], double v[], double u[])
{
    double tmp[n] __attribute__ ((aligned (16)));
    matmul3(n, A, v, tmp);
    matmul2(n, tmp, A, u);
}


double spectral_game(int n)
{
    double *A;
    double u[n] __attribute__ ((aligned (16)));
    double v[n] __attribute__ ((aligned (16)));
    int i, j;

    /* Aligned allocation. */
    /* A = (double *)malloc(n*n*sizeof(double)); */
    if ( posix_memalign( (void **)&A , 4*sizeof(double) , sizeof(double) * n * n ) != 0 ) {
        printf( "spectral_game:%i: call to posix_memalign failed.\n" , __LINE__ );
        abort();
        }


    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            A[i*n+j] = Ac(i, j);
        }
    }


    for (i = 0; i < n; i++) {
        u[i] = 1.0;
    }
    for (i = 0; i < 10; i++) {
        AvA(n, A, u, v);
        AvA(n, A, v, u);
    }
    free(A);
    return sqrt(dot_product2(n, u, v) / dot_product2(n, v, v));
}

int main(int argc, char *argv[]) {
    int i, N = ((argc >= 2) ? atoi(argv[1]) : 2000);
    for ( i = 0 ; i < 10 ; i++ )
        printf("%.9f\n", spectral_game(N));
    return 0;
}

ทั้งสามเวอร์ชันรวบรวมด้วยแฟล็กเดียวกันและgccเวอร์ชันเดียวกัน โปรดทราบว่าฉันปิดการเรียกฟังก์ชั่นหลักเป็นวงจาก 0..9 เพื่อให้ได้เวลาที่แม่นยำยิ่งขึ้น

$ time ./spectral_norm6 5500
1.274224153
...
real    0m22.682s
user    0m21.113s
sys 0m1.500s

$ time ./spectral_norm7 5500
1.274224153
...
real    0m21.596s
user    0m20.373s
sys 0m1.132s

$ time ./spectral_norm_vec 5500
1.274224153
...
real    0m21.336s
user    0m19.821s
sys 0m1.444s

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

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

ในฐานะที่เป็นหมายเหตุด้านข้างแทนที่จะมัวเมาเกี่ยวกับการทำให้เป็นเวกเตอร์, การจัดตำแหน่งและการคอมไพเลอร์การปรับให้เหมาะสมที่ชัดเจนที่สุดคือการคำนวณการวนซ้ำสองสามครั้งแรกในคณิตศาสตร์ที่มีความแม่นยำเดียวจนกระทั่งเรามีผลลัพธ์ประมาณ 8 หลัก คำแนะนำที่มีความแม่นยำเพียงอย่างเดียวไม่เพียง แต่เร็วขึ้นเท่านั้น แต่จำนวนหน่วยความจำที่ต้องเลื่อนไปรอบ ๆ ก็ลดลงครึ่งหนึ่ง


ขอบคุณมากสำหรับเวลาของคุณ! ฉันหวังว่าคุณจะตอบกลับ :) ดังนั้นก่อนอื่นฉันได้ทำการอัพเดท Makefile เพื่อใช้แฟล็กของคุณ จากนั้นฉันก็ใส่รหัส C ของคุณเป็น spectral_norm8.c และอัปเดต README ฉันอัปเดตการกำหนดเวลาในเครื่องของฉัน ( github.com/certik/spectral_norm/wiki/Timings ) และอย่างที่คุณเห็นการตั้งค่าสถานะคอมไพเลอร์ไม่ได้ทำให้รุ่น C เร็วขึ้นบนเครื่องของฉัน (เช่น gfortran ยังคงชนะ) แต่ SIMD ของคุณเป็นแบบเวกเตอร์ รุ่นเต้น gfortran
OndřejČertík

@ OndřejČertík: เพิ่งออกจากความอยากรู้อยากเห็นสิ่งที่รุ่นของgcc/ gfortranที่คุณใช้? ในเธรดก่อนหน้าเวอร์ชันที่ต่างกันให้ผลลัพธ์ที่ต่างกันอย่างมีนัยสำคัญ
Pedro

ฉันใช้ 4.6.1-9ubuntu3 คุณมีสิทธิ์เข้าถึงคอมไพเลอร์ของ Intel หรือไม่? ประสบการณ์ของฉันกับ gfortran คือบางครั้งมันก็ไม่ได้สร้างรหัสที่ดีที่สุด IFort มักจะทำ
อองเดรเซอร์ติก

1
@ OndřejČertík: ตอนนี้ผลลัพธ์ดูสมเหตุสมผลมากขึ้น! ฉันมองข้ามว่าmatmul2ในเวอร์ชั่น Fortran นั้นมีความหมายเทียบเท่ากับmatmul3ในเวอร์ชั่น C ของฉัน ตอนนี้ทั้งสองรุ่นเหมือนกันจริง ๆ และgcc/ gfortran ควรให้ผลลัพธ์เหมือนกันทั้งสองอย่างเช่นไม่มีส่วนหน้า / ภาษาดีกว่าอีกรุ่นในกรณีนี้ gccเพียงแค่มีข้อได้เปรียบที่เราสามารถใช้ประโยชน์จากคำแนะนำแบบเวกเตอร์ที่เราควรเลือก
Pedro

1
@ cjordan1: ฉันเลือกที่จะใช้vector_sizeแอ็ตทริบิวต์เพื่อให้โค้ดเป็นอิสระจากแพลตฟอร์มเช่นใช้ไวยากรณ์นี้gccควรจะสามารถสร้างโค้ดเวกเตอร์สำหรับแพลตฟอร์มอื่น ๆ เช่นการใช้ AltiVec บนสถาปัตยกรรม IBM Power
Pedro

7

คำตอบของผู้ใช้ 389 ถูกลบไปแล้ว แต่ให้ฉันบอกว่าฉันอยู่ในค่ายของเขาอย่างเหนียวแน่น: ฉันไม่ได้เห็นสิ่งที่เราเรียนรู้โดยการเปรียบเทียบมาตรฐานไมโครในภาษาต่างๆ ไม่แปลกใจสำหรับฉันที่ C และ Fortran จะได้รับประสิทธิภาพเดียวกันในเกณฑ์มาตรฐานนี้มากแค่ไหนเพราะมันสั้นแค่ไหน แต่มาตรฐานก็น่าเบื่อเช่นกันเพราะสามารถเขียนได้ง่ายทั้งสองภาษาในสองสามบรรทัด จากมุมมองของซอฟต์แวร์นั่นไม่ใช่กรณีตัวแทน: เราควรใส่ใจเกี่ยวกับซอฟต์แวร์ที่มีโค้ด 10,000 หรือ 100,000 บรรทัดและคอมไพเลอร์ทำอย่างไร แน่นอนว่าในระดับนั้นจะพบสิ่งอื่นได้อย่างรวดเร็ว: ภาษานั้นต้องมี 10,000 บรรทัดในขณะที่ภาษา B ต้องการ 50,000 หรือวิธีอื่น ๆ ขึ้นอยู่กับว่าคุณต้องการทำอะไร และทันใดนั้นเอง

มันไม่สำคัญสำหรับฉันเลยว่าใบสมัครของฉันอาจเร็วกว่า 50% ถ้าฉันพัฒนาใน Fortran 77 ถ้ามันใช้เวลาแค่ 1 เดือนเพื่อให้มันทำงานอย่างถูกต้องในขณะที่มันใช้เวลา 3 เดือน ใน F77 ปัญหาของคำถามที่นี่คือมันมุ่งเน้นไปที่แง่มุม (แต่ละเมล็ด) ที่ไม่เกี่ยวข้องในทางปฏิบัติในมุมมองของฉัน


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

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

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

@ WolfgangBangerth: เหมือนกับที่ฉันพูดในความคิดเห็นแรกของฉันฉันเห็นด้วยกับคุณและกับผู้ใช้ 389 (Jonathan Dursi) ดังนั้นการถามฉันว่าคำถามนั้นไม่มีประโยชน์ ที่กล่าวว่าฉันจะเชิญทุกคนที่ไม่เชื่อว่าทางเลือกของภาษานั้น (ในภาษา C ++ / C / Fortran) เป็นสิ่งสำคัญสำหรับการปฏิบัติงานในโปรแกรมของพวกเขาที่จะตอบคำถามของคุณ น่าเศร้าที่ฉันสงสัยว่าการอภิปรายประเภทนี้อาจมีสำหรับคอมไพเลอร์เวอร์ชัน
Geoff Oxberry

@GeoffOxberry: ใช่และเห็นได้ชัดว่าไม่ได้หมายความว่าคุณต้องตอบคำถามนั้น
Wolfgang Bangerth

5

ปรากฎว่าฉันสามารถเขียนรหัส Python (ใช้ numpy เพื่อทำการดำเนินการ BLAS) เร็วกว่ารหัส Fortran ที่คอมไพล์ด้วยคอมไพเลอร์ gfortran ของระบบของฉัน

$ gfortran -o sn6a sn6a.f90 -O3 -march=native
    
    $ ./sn6a 5500
1.274224153
1.274224153
1.274224153
   1.9640001      sec per iteration

$ python ./foo1.py
1.27422415279
1.27422415279
1.27422415279
1.20618661245 sec per iteration

foo1.py:

import numpy
import scipy.linalg
import timeit

def specNormDot(A,n):
    u = numpy.ones(n)
    v = numpy.zeros(n)

    for i in xrange(10):
        v  = numpy.dot(numpy.dot(A,u),A)
        u  = numpy.dot(numpy.dot(A,v),A)

    print numpy.sqrt(numpy.vdot(u,v)/numpy.vdot(v,v))

    return

n = 5500

ii, jj = numpy.meshgrid(numpy.arange(1,n+1), numpy.arange(1,n+1))
A  = (1./((ii+jj-2.)*(ii+jj-1.)/2. + ii))

t = timeit.Timer("specNormDot(A,n)", "from __main__ import specNormDot,A,n")
ntries = 3

print t.timeit(ntries)/ntries, "sec per iteration"

และ sn6a.f90, spectral_norm6.f90 ที่ปรับเปลี่ยนเล็กน้อยมาก:

program spectral_norm6
! This uses spectral_norm3 as a starting point, but does not use the
! Fortrans
! builtin matmul and dotproduct (to make sure it does not call some
! optimized
! BLAS behind the scene).
implicit none

integer, parameter :: dp = kind(0d0)
real(dp), allocatable :: A(:, :), u(:), v(:)
integer :: i, j, n
character(len=6) :: argv
integer :: calc, iter
integer, parameter :: niters=3

call get_command_argument(1, argv)
read(argv, *) n

allocate(u(n), v(n), A(n, n))
do j = 1, n
    do i = 1, n
        A(i, j) = Ac(i, j)
    end do
end do

call tick(calc)

do iter=1,niters
    u = 1
    do i = 1, 10
        v = AvA(A, u)
        u = AvA(A, v)
    end do

    write(*, "(f0.9)") sqrt(dot_product2(u, v) / dot_product2(v, v))
enddo

print *, tock(calc)/niters, ' sec per iteration'

contains

pure real(dp) function Ac(i, j) result(r)
integer, intent(in) :: i, j
r = 1._dp / ((i+j-2) * (i+j-1)/2 + i)
end function

pure function matmul2(v, A) result(u)
! Calculates u = matmul(v, A), but much faster (in gfortran)
real(dp), intent(in) :: v(:), A(:, :)
real(dp) :: u(size(v))
integer :: i
do i = 1, size(v)
    u(i) = dot_product2(A(:, i), v)
end do
end function

pure real(dp) function dot_product2(u, v) result(w)
! Calculates w = dot_product(u, v)
real(dp), intent(in) :: u(:), v(:)
integer :: i
w = 0
do i = 1, size(u)
    w = w + u(i)*v(i)
end do
end function

pure function matmul3(A, v) result(u)
! Calculates u = matmul(v, A), but much faster (in gfortran)
real(dp), intent(in) :: v(:), A(:, :)
real(dp) :: u(size(v))
integer :: i, j
u = 0
do j = 1, size(v)
    do i = 1, size(v)
        u(i) = u(i) + A(i, j)*v(j)
    end do
end do
end function

pure function AvA(A, v) result(u)
! Calculates u = matmul2(matmul3(A, v), A)
! In gfortran, this function is sligthly faster than calling
! matmul2(matmul3(A, v), A) directly.
real(dp), intent(in) :: v(:), A(:, :)
real(dp) :: u(size(v))
u = matmul2(matmul3(A, v), A)
end function

subroutine tick(t)
    integer, intent(OUT) :: t

    call system_clock(t)
end subroutine tick

! returns time in seconds from now to time described by t 
real function tock(t)
    integer, intent(in) :: t
    integer :: now, clock_rate

    call system_clock(now,clock_rate)

    tock = real(now - t)/real(clock_rate)
end function tock
end program

1
ฉันพูดภาษาอังกฤษในแก้ม
Robert Harvey

-1 สำหรับการไม่ตอบคำถาม แต่ฉันคิดว่าคุณรู้แล้ว
Pedro

น่าสนใจคุณใช้ gfortran รุ่นใดและคุณทดสอบรหัส C ที่มีอยู่ในที่เก็บด้วยธงของ Pedro หรือไม่
Aron Ahmadia

1
ที่จริงแล้วฉันคิดว่ามันชัดเจนขึ้นแล้วโดยสมมติว่าคุณไม่ได้เหน็บแนม
Robert Harvey

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

3

ตรวจสอบเรื่องนี้ด้วยคอมไพเลอร์ของ Intel ด้วย 11.1 (-fast, implying -O3) และ 12.0 (-O2) ค่าที่เร็วที่สุดคือ 1,2,6,7 และ 8 (เช่นรหัส Fortran และ C ที่ "ง่ายที่สุด" และ hand-vectorized C) - สิ่งเหล่านี้แยกไม่ออกจากกันที่ ~ 1.5s การทดสอบ 3 และ 5 (โดยมีอาร์เรย์เป็นฟังก์ชัน) จะช้ากว่า # 4 ฉันไม่สามารถรวบรวมได้

โดยเฉพาะอย่างยิ่งถ้าคอมไพล์ด้วย 12.0 และ -O3 แทนที่จะเป็น -O2 รหัส Fortran 2 ("ง่ายที่สุด") 2 อันแรกจะชะลอความเร็วลงมาก (1.5 -> 10.2 วินาที) - นี่ไม่ใช่ครั้งแรกที่ฉันเห็นบางสิ่งเช่น สิ่งนี้ แต่นี่อาจเป็นตัวอย่างที่น่าทึ่งที่สุด หากนี่เป็นกรณีในรุ่นปัจจุบันฉันคิดว่าเป็นความคิดที่ดีที่จะรายงานต่อ Intel เนื่องจากมีบางอย่างผิดปกติอย่างชัดเจนกับการเพิ่มประสิทธิภาพในกรณีที่ค่อนข้างง่าย

มิฉะนั้นฉันก็เห็นด้วยกับโจนาธานว่านี่ไม่ใช่แบบฝึกหัดที่ให้ข้อมูล :)


ขอบคุณสำหรับการตรวจสอบ! สิ่งนี้เป็นการยืนยันประสบการณ์ของฉัน gfortran นั้นยังไม่โตเต็มที่เพราะด้วยเหตุผลบางอย่างการทำงานของ matmul นั้นช้า ดังนั้นข้อสรุปสำหรับฉันก็คือใช้ matmul และทำให้รหัส Fortran ง่ายขึ้น
OndřejČertík

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