Spektral norm atışında (gcc, intel ve diğer derleyiciler kullanılarak) C Fortran'dan daha mı yavaş?


13

Buradaki sonuç:

Fortran derleyicileri gerçekten ne kadar iyi?

gfortran ve gcc basit kod kadar hızlı olmasıdır. Bu yüzden daha karmaşık bir şey denemek istedim. Spektral norm atışları örneğini aldım. Önce 2D A matrisini (:, :) önceden hesaplar ve sonra normu hesaplarım. (Sanırım çatışmada bu çözüme izin verilmiyor.) Fortran ve C versiyonunu uyguladım. İşte kod:

https://github.com/certik/spectral_norm

En hızlı gfortran sürümleri spectral_norm2.f90 ve spectral_norm6.f90'dır (biri Fortran'ın yerleşik matmulunu ve dot_product'ı kullanır, diğeri kodda bu iki işlevi uygular - hızda hiçbir fark yoktur). Yazabildiğim en hızlı C / C ++ kodu spectral_norm7.cpp. Dizüstü bilgisayarımın git sürümü 457d9d9 itibariyle zamanlamalar:

$ 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

Yani gfortran'ın versiyonu biraz daha hızlı. Neden? Daha hızlı bir C uygulamasıyla bir çekme isteği gönderirseniz (veya yalnızca bir kodu yapıştırırsanız), depoyu güncelleyeceğim.

Fortran'da bir 2D dizi geçiyorum, CI'de 1D dizisi kullanıyorum. Bir 2D dizi veya uygun gördüğünüz başka bir yol kullanmaktan çekinmeyin.

Derleyicilere gelince, gcc vs gfortran, icc vs ifort ve benzerlerini karşılaştıralım. (İfort ile gcc'yi karşılaştıran çatışmadaki sayfanın aksine.)

Güncelleme : C sürümümdeki matmul3 () 'ü geliştiren 179dae2 sürümünü kullanarak, şu anda hızlılar:

$ 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

Pedro'nun aşağıdaki vektör versiyonu daha hızlı:

$ time ./spectral_norm8 5500
1.274224153

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

Son olarak, Intel derleyicileri için aşağıdaki laxxy raporları gibi, orada büyük bir fark yok gibi görünüyor ve en basit Fortran kodu (spectral_norm1) bile en hızlıları arasında.


5
Şu anda bir derleyicinin yakınında hiçbir yerde değilim, ancak dizilerinize restrict anahtar kelimesini eklemeyi düşünün. İşaretçilerin örtüşmesi genellikle dizilerdeki Fortran ve C işlev çağrıları arasındaki farktır. Ayrıca, Fortran hafızayı sütun-büyük sırada, C ise satır-büyük olarak saklar.
moyner

1
-1 Bu sorunun gövdesi uygulamalar hakkında konuşuyor, ancak başlık hangi dilin daha hızlı olduğunu soruyor? Bir dilin nasıl bir hız özelliği olabilir? Soru başlığını, sorunun metnini yansıtacak şekilde düzenlemelisiniz.
milancurcic

@ IRO-bot, düzelttim. Size iyi gelip gelmediğini bilmeme izin verin.
Ondřej Čertík

1
Aslında "Fortran derleyicileri gerçekten ne kadar iyi?" o iş parçacığında tam olarak doğru değil. Ben GCC, PGI, CRAY ve Intel derleyicileri ve 3 derleyici Fortran C (b / w% 5-40) daha hızlı olan bir Cray üzerinde test denemişti. Cray derleyicileri en hızlı Fortran / C kodunu üretti, ancak Fortran kodu% 40 daha hızlıydı. Zaman bulduğumda ayrıntılı sonuçlar yayınlayacağım. Ancak Cray makinelerine erişimi olan herkes karşılaştırmayı doğrulayabilir. İyi bir platform çünkü 4-5 derleyici mevcut ve ilgili bayraklar ftn / cc sarmalayıcısı tarafından otomatik olarak devreye giriyor.
stali

ayrıca bir Opteron sisteminde pgf95 / pgcc (11.10) ile kontrol edildi: # 1 ve # 2 en hızlı (iforttan ~% 20 daha hızlı), sonra # 6, # 8, # 7 (bu sırayla). pgf95, tüm fortran kodlarınız için ifort'tan daha hızlıydı ve icpc, tüm C için pgcpp'den daha hızlıydı - Öğelerim için, aynı AMD sisteminde bile genellikle ifort'u daha hızlı buluyorum.
laxxy

Yanıtlar:


12

Her şeyden önce, bu soruyu / meydan okumayı gönderdiğiniz için teşekkürler! Bir feragatname olarak, bazı Fortran deneyimine sahip yerel bir C programcısıyım ve en çok evde C hissediyorum, bu yüzden sadece C sürümünü geliştirmeye odaklanacağım. Tüm Fortran hack'lerini de gitmeye davet ediyorum!

Yeni gelenlere bunun ne hakkında olduğunu hatırlatmak için: Bu konudaki temel öngörü, gcc / fortran ve icc / ifort'un sırasıyla aynı arka uçlara sahip oldukları için, aynı (anlamsal olarak özdeş) program için, eşdeğeri olmayan bir kod için eşdeğer kod üretmeleri gerektiğiydi. C ya da Fortran'da. Sonucun kalitesi yalnızca ilgili uygulamaların kalitesine bağlıdır.

Kodla biraz oynadım gccve 4.6.1 ve aşağıdaki derleyici bayraklarını kullanarak bilgisayarımda (ThinkPad 201x, Intel Core i5 M560, 2.67 GHz) oynadım :

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

Ayrıca devam ettim ve C ++ kodunun SIMD-vektörize C dili sürümünü yazdım 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;
}

Her üç versiyon da aynı bayraklar ve aynı gccversiyonla derlenmiştir . Daha doğru zamanlamalar elde etmek için ana fonksiyon çağrısını 0..9'dan bir döngüde tamamladığımı unutmayın.

$ 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

"Daha iyi" derleyici bayrakları ile, C ++ sürümü Fortran sürümünden daha iyi performans gösterir ve elle kodlanmış vectorized döngüler sadece marjinal bir gelişme sağlar. C ++ sürümü için derleyiciye hızlı bir bakış, ana döngülerin de daha agresif bir şekilde açılsa da vektörleştirildiğini gösterir.

Ayrıca, tarafından oluşturulan montajcıya bir göz attım gfortranve işte büyük sürpriz: vektörleşme yok. En azından mimarimde bant genişliğinin sınırlı olmasının sadece çok yavaş olmasından kaynaklanıyorum. Matris çarpımlarının her biri için 230MB veri aktarılır ve bu da tüm önbellek seviyelerini hemen hemen değiştirir. Daha küçük bir giriş değeri kullanıyorsanız, örn.100 , performans farklılıkları önemli ölçüde artar.

Bir yan not olarak, vektörleştirme, hizalama ve derleyici bayrakları hakkında takıntılı olmak yerine, en belirgin optimizasyon, sonucun ~ 8 hanesi oluncaya kadar, tek duyarlıklı aritmetikteki ilk birkaç yinelemeyi hesaplamak olacaktır. Tek duyarlıklı talimatlar sadece daha hızlı olmakla kalmaz, aynı zamanda taşınması gereken bellek miktarı da yarıya iner.


Zaman ayırdığınız için çok teşekkürler! Cevap vermeni umuyordum. :) Bu yüzden önce bayraklarınızı kullanmak için Makefile'i güncelledim. Sonra C kodunuzu spectral_norm8.c olarak koydum ve README'yi güncelledim. Makinemdeki zamanlamaları güncelledim ( github.com/certik/spectral_norm/wiki/Timings ) ve görebileceğiniz gibi, derleyici bayrakları makinemdeki C sürümünü daha hızlı hale getirmedi (yani gfortran hala kazanıyor), ancak SIMD-vectorized sürümü gfortran yener.
Ondřej Čertík

@ OndřejČertík: Meraktan soruyorum, ne sürümü gcc/ gfortrankullanıyorsunuz? Önceki iş parçacıklarında, farklı sürümler önemli ölçüde farklı sonuçlar vermiştir.
Pedro

4.6.1-9ubuntu3 kullanıyorum. Intel derleyicilerine erişiminiz var mı? Gfortran ile yaşadığım deneyim, bazen (henüz) optimum kod üretmediğidir. IFort genellikle yapar.
Ondřej Čertík

1
@ OndřejČertík: Şimdi sonuçlar daha anlamlı! matmul2Fortran versiyonunda anlamsal olarak matmul3C versiyonumla eşdeğer olduğunu göz ardı etmiştim . İki sürümü gerçekten şimdi aynıdır ve bu nedenle gcc/ gfortran gerektiği için aynı sonuçlar üretmek, örneğin kimsenin ön uç / dil daha iyi diğerinden daha bu durumda olduğunu. gccsadece vektörel talimatlardan faydalanabileceğimiz avantajı vardır.
Pedro

1
@ cjordan1: vector_sizeKod platformundan bağımsız hale getirmek için özniteliği kullanmayı seçtim , yani bu sözdizimini gcckullanarak, IBM Power mimarisinde AltiVec kullanarak diğer platformlar için vektörleştirilmiş kod üretebilmelidir.
Pedro

7

user389'un yanıtı silindi, ancak kampında sıkı bir şekilde olduğumu belirtmeme izin verin: Farklı dillerde mikro ölçütleri karşılaştırarak öğrendiklerimizi göremiyorum. Ne kadar kısa olduğu göz önüne alındığında, C ve Fortran'ın bu karşılaştırmada hemen hemen aynı performansı alması benim için sürpriz değil. Ancak kıyaslama da sıkıcıdır, çünkü her iki dilde de birkaç düzine satırda kolayca yazılabilir. Yazılım açısından bakıldığında, bu temsili bir durum değildir: 10.000 veya 100.000 satır kod içeren yazılımlara ve derleyicilerin bu konuda nasıl çalıştığına dikkat etmeliyiz. Tabii ki, bu ölçekte, diğer şeyler de çabucak bulunacaktır: A dili 10.000 satır gerektirirken, B dili 50.000 gerektirir. Ya da ne yapmak istediğinize bağlı olarak tam tersi. Ve aniden '

Başka bir deyişle, Fortran 77'de geliştirdiğimde başvurumun% 50 daha hızlı olabilmesi benim için çok önemli değil, bunun yerine 3 ay sürecek doğru şekilde çalışmasını sağlamak sadece 1 ay sürecek F77. Buradaki soruyla ilgili sorun, benim görüşüme göre pratikte alakalı olmayan bir yön (bireysel çekirdekler) üzerine odaklanmasıdır.


Kabul. Değeri için, çok, çok küçük düzenlemeler (-3 karakter, +9 karakter) dışında, cevabının ana hissini kabul ettim. Bildiğim kadarıyla, C ++ / C / Fortran derleyici tartışması sadece bir kişi performans geliştirme için olası her yolu tükettiğinde önemlidir, bu yüzden insanların% 99,9'u için bu karşılaştırmalar önemli değildir. Tartışmayı özellikle aydınlatıcı bulmuyorum, ancak performans nedenleriyle C ve C ++ üzerinden Fortran'ı seçmeyi kanıtlayabilen en az bir kişiyi biliyorum, bu yüzden tamamen işe yaramaz diyemem.
Geoff Oxberry

4
Ana noktanıza katılıyorum, ama yine de aynı tartışmanın kullanılmasına rağmen , bir şekilde bir dili diğerinden "daha hızlı" yapan bir sihir olduğuna inanan bazı insanlar olduğu için bu tartışmanın yararlı olduğunu düşünüyorum. backends. Bu tartışmalara esas olarak bu efsaneyi ortadan kaldırmak için katkıda bulunuyorum. Metodolojiye gelince, "temsili bir durum" yoktur ve bence, matris-vektör çarpanları kadar basit bir şey almak, derleyicilere yapabileceklerini veya yapamayacaklarını göstermek için yeterli alan sağladığı için iyi bir şeydir.
Pedro

@GeoffOxberry: Elbette, az çok iyi ifade edilmiş ve gerekçeli nedenlerle her zaman bir dil yerine başka bir dil kullanan insanlar bulacaksınız. Benim sorum, ancak, yapılandırılmamış, uyarlanabilir bir sonlu eleman ağlarında görünen veri yapılarını kullanması Fortran'ın ne kadar hızlı olacağını olurdu. Bunun Fortran'da uygulanmasının garip olması gerçeğinin yanı sıra (bunu C ++ 'da uygulayan herkes STL'yi yoğun bir şekilde kullanır), Fortran sıkı döngüleri, birçok indirimi, çok sayıda ifs olmayan bu tür kodlar için gerçekten daha hızlı olurdu?
Wolfgang Bangerth

@WolfgangBangerth: İlk yorumumda söylediğim gibi, size ve user389'a (Jonathan Dursi) katılıyorum, bu yüzden bana bu soruyu sormak anlamsız. Ben herkesi davet ediyorum dedi vermez (C ++ / C / Fortran arasında) sorunuza cevap uygulanışında performans için önemli olan dilin bu seçimi inanıyoruz. Ne yazık ki, bu tür bir tartışmanın derleyici sürümleri için olabileceğinden şüpheleniyorum.
Geoff Oxberry

@GeoffOxberry: Evet ve açıkçası anlamına gelmediğini Eğer bu soruya cevap gerekiyordu.
Wolfgang Bangerth

5

Sistemimin gfortran derleyicisi ile derlenen Fortran kodundan daha hızlı bir Python kodu (BLAS işlemlerini yapmak için numpy kullanarak) yazabilirim.

$ 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"

ve sn6a.f90, çok hafif değiştirilmiş bir spektral_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
Dil yanakta, sanırım?
Robert Harvey

Soruyu cevaplamadığım için -1, ama bence bunu zaten biliyorsun.
Pedro

İlginç, hangi gfortran sürümünü kullandınız ve depodaki mevcut C kodunu Pedro'nun bayrakları ile test ettiniz mi?
Aron Ahmadia

1
Aslında, alaycı olmadığınızı varsayarak, şimdi daha net olduğunu düşünüyorum.
Robert Harvey

1
Bu yazı ve diğer soruların veya yazıların hiçbiri Aron tarafından düşüncelerine daha iyi uyacak şekilde düzenlenmediğinden, tüm mesele, tüm yazıların tam olarak bu tür "bu sonuçlar anlamsız" olarak etiketlenmesine rağmen uyarılar, sadece siliyorum.

3

Bunu Intel derleyicileri ile kontrol ettim. 11.1 (-hızlı, ima eden -O3) ve 12.0 (-O2) ile en hızlıları 1,2,6,7 ve 8'dir (yani "en basit" Fortran ve C kodları ve elle vektörize C) - bunlar birbirinden ~ 1.5s'de ayırt edilemez. Test 3 ve 5 (dizi fonksiyon olarak) daha yavaştır; # 4 Derleyemedim.

Oldukça belirgin bir şekilde, -O2 yerine 12.0 ve -O3 ile derleniyorsa, ilk 2 ("en basit") Fortran kodları A LOT'u yavaşlatır (1.5 -> 10.2 sn.) - ilk kez böyle bir şey görmüyorum ancak bu en dramatik örnek olabilir. Mevcut sürümde hala durum buysa, bu oldukça basit durumda optimizasyonlarında çok yanlış giden bir şey olduğu için bunu Intel'e bildirmenin iyi bir fikir olacağını düşünüyorum.

Aksi takdirde Jonathan'a bunun özellikle bilgilendirici bir egzersiz olmadığı konusunda katılıyorum :)


Kontrol ettiğiniz için teşekkürler! Bu benim tecrübemi teyit ediyor, gfortran henüz tam olarak olgun değil, çünkü bir sebepten dolayı matmul işlemi yavaş. Yani benim için sonuç sadece matmul kullanmak ve Fortran kodunu basit tutmaktır.
Ondřej Čertík

Öte yandan, gfortran otomatik olarak tüm matmul () çağrıları BLAS çağrıları (belki de dot_product (), emin değilim) dönüştürmek için bir komut satırı seçeneği olduğunu düşünüyorum. Asla denemedim.
laxxy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.