1

私は C++ プログラミングの初心者です。すべての MatLab ソフトウェアを C++ に移行することによる利点を確認しようとしています。私は主に非線形の有限要素処理を行っているため、大規模に実行する必要がある操作の 1 つは、2 つのベクトルの外積です。Matlab と C++ で 2 つの実装をテストしましたが、C++ の方がはるかに高速なようです。C++ では、2 つの異なる実装が異なるタイミングを提供します。インテル MKL を使用しています。

コードは次のとおりです。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
#include <mkl.h>


void vprod( double vgr[3], double vg1[3], double vg2[3]);


int main() {

    double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
    int LC=1000000;
    int i,j,k;
    double tiempo=0.0, tinicial;

    //------------------------------------------------------------------------
    std::cout << "INLINE METHOD: " << std::endl;

    tinicial = dsecnd();
        for (i=0; i<LC; i++){   
        vr[0] = v1[1]*v2[2]-v1[2]*v2[1]; 
        vr[1] =-(v1[0]*v2[2]-v1[2]*v2[0]);
        vr[2] = v1[0]*v2[1]-v1[1]*v2[0];
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total: " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    std::cout << "FUNCTION METHOD: " << std::endl;

    tinicial = dsecnd();
        for (i=0; i<LC; i++){   
        vprod (vr,v1,v2);
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total: " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

    std::cin.ignore();
    return 0;

}


inline void vprod( double vgr[3], double vg1[3], double vg2[3]){
    vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1]; 
    vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
    vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];

}

私の質問は: なぜ最初の実装が 2 番目の実装よりも 3 倍高速なのですか? これは関数呼び出しのオーバーヘッドの結果ですか? ありがとう !!!

編集:コンパイラが定数ベクトルを使用したループの結果を「推測」するのを避けるために、コードを変更しました。@phonetagger が示したように、結果は大きく異なります。vprod 関数を使用せずに 28500 マイクロ秒、vprod関数を使用して 29000 マイクロ秒を得ました。この数値は、Ox 最適化を使用して取得されました。inline キーワードがオンの場合、最適化を変更しても比較には影響しませんが、数値は少し上がります。また、inline キーワードが使用されていない (最適化がオフになっている) 場合、タイミングは vprod 関数を使用しない場合は 32000、関数を使用する場合は 37000 です。そのため、関数呼び出しのオーバーヘッドは約 5000 マイクロ秒になる可能性があります。

新しいコードは次のとおりです。

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
#include <mkl.h>

//#include <mkl_lapack.h>

void vprod( double *vgr, int ploc, double *vg1, double *vg2);


int main() {

    int nv=1000000;
    int dim=3*nv;
    double *v1, *v2, *vr; // Declare Pointers
    int ploc, i;
    double tiempo=0.0, tinicial;

     v1 = new double [dim];  //Allocate block of memory
     v2 = new double [dim];
     vr = new double [dim];

// Fill vectors with something
    for (i = 0; i < dim; i++) {
        v1[i] =1.25 +  (double)(i+1);
        v2[i] =2.62+ 2*(double)(i+7);
    }



    //------------------------------------------------------------------------
    std::cout << "RUTINA CON CODIGO INLINE: \n" ;

    tinicial = dsecnd();
    ploc = 0; // ploc points to an intermediate location.
    for (i=0; i<nv; i++){   
        vr[ploc] = v1[ploc+1]*v2[ploc+2]-v1[ploc+2]*v2[ploc+1]; 
        vr[ploc+1] =-(v1[ploc]*v2[ploc+2]-v1[ploc+2]*v2[ploc]);
        vr[ploc+2] = v1[ploc]*v2[ploc+1]-v1[ploc+1]*v2[ploc];
        ploc +=3;
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total: " << tiempo << ".\n";
    std::cout << "Resultado: " << vr[0] << ".\n";

    delete v1,v2,vr;

v1 = new double [dim];  //Allocate block of memory
v2 = new double [dim];
vr = new double [dim];
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    std::cout << "RUTINA LLAMANDO A FUNCION: \n" ;

    ploc=0;
    tinicial = dsecnd();
        for (i=0; i<nv; i++){   
        vprod ( vr, ploc, v1, v2);
        ploc +=3;
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total: " << tiempo << ".\n";
    std::cout << "Resultado: " << vr[0] << ".\n";
    //------------------------------------------------------------------------

    std::cin.ignore();
    return 0;

}


inline void vprod( double *vgr, int ploc, double *vg1, double *vg2) {
        vgr[ploc]    =   vg1[ploc+1]*vg2[ploc+2]-vg1[ploc+2]*vg2[ploc+1]; 
        vgr[ploc+1]  = -(vg1[ploc]*vg2[ploc+2]-vg1[ploc+2]*vg2[ploc]);
        vgr[ploc+2]  =   vg1[ploc]*vg2[ploc+1]-vg1[ploc+1]*vg2[ploc];

}
4

2 に答える 2

4

どのコンパイラを使用しているかはわかりませんが (「MKL」はコンパイラ スイートですか?)、使用しているコンパイラに関係なく、最適化レベルはコードのパフォーマンスに劇的な影響を与えます。コーディングスタイルと、コードをより高速に実行するために「トリックを実行」しようとするかどうかによって異なります。多くの場合 (常にではありませんが)、コンパイラにトリックを実行させた方がよいため、コーディングのトリックを実行するよりも、効率的なアルゴリズムを作成することに集中します。

いずれにせよ、あなたのコードを私のシステムでさまざまな方法で実行しました。結果は以下のコード コメントに示されています...

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
//#include <mkl.h>

// My standin for dsecnd() since I don't have "mkl.h"...
#include <sys/time.h>
double dsecnd()
{
    struct timeval tv;
    if (gettimeofday(&tv,NULL))
    {
        fprintf(stderr,"\ngettimeofday() error\n\n");
        exit(1);
    }
    return tv.tv_sec*1000000 + tv.tv_usec; // ...returns MICROSECONDS
    //return tv.tv_sec + ((double)tv.tv_usec)/1000000; // ...returns SECONDS
}

//---------------------------------
// Uncomment one or both of these to test variations....
//#define USE_INLINE_KEYWORD
//#define DEFINE_vprod_AT_TOP
//
// Using g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-52) on an x86 machine...
//
//                                 microseconds          microseconds
//                               "hardcoded inline"   "via vprod() function"
//                                                     [i]=inlined, [-]=not
//                               ------------------   ----------------------
// inline keyword, at top
//      no optimization                 9501               17797 [-]
//      optimization -O1                   2   (see NOTE)      1 [i]
//      optimization -O2                   1                   1 [i]
//      optimization -O3                   0                   0 [i]
//
// no inline keyword, at top
//      no optimization                 9630               18203 [-]
//      optimization -O1                1257               10681 [-]
//      optimization -O2                1272               10694 [-]
//      optimization -O3                   0                   1 [i]
//
// inline keyword, at bottom
//      no optimization                 9763               18333 [-]
//      optimization -O1                   1                   0 [i]
//      optimization -O2                   2                   1 [i]
//      optimization -O3                   0                   0 [i]
//
// no inline keyword, at bottom
//      no optimization                 9900               18387 [-]
//      optimization -O1                1289               10714 [-]
//      optimization -O2                 795                6740 [-]
//      optimization -O3                   1                   0 [i]
//
// Note that in all cases, both results were reported as -213.458.
//
// NOTE: Especially since I'm using gettimeofday() instead of something
//       that returns process (CPU) time, all results may include some
//       time that the CPU spent processing other stuff, but even if
//       that weren't the case (i.e. even if I used a function that
//       returned only CPU time spent on this particular process), there
//       would still be the quantization error of +/-1 microsecond on
//       each end of the interval, meaning +/-2 microseconds overall.
//
/* My cut & paste "build & test script" to run on the Linux command prompt...

echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""
rm -f a.out; g++ so.cpp
echo ""; echo "No optimization:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O1 so.cpp
echo ""; echo "Optimization -O1:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O2 so.cpp
echo ""; echo "Optimization -O2:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O3 so.cpp
echo ""; echo "Optimization -O3:---------------"; objdump -d a.out | grep call | grep vprod; a.out

...if the "objdump -d a.out | grep call | grep vprod" command returns something
like "call   8048754 <_Z5vprodPdS_S_>", then I know that the call to vprod() is
NOT inlined, whereas if it returns nothing, I know the call WAS inlined.  There
is only one caller of vprod(), so the results can't be confusing.

*/
//
//---------------------------------

#ifdef DEFINE_vprod_AT_TOP
    #ifdef USE_INLINE_KEYWORD
        inline
    #endif
        void vprod( double vgr[3], double vg1[3], double vg2[3]){
        //void vprod( double *vgr, double *vg1, double *vg2){
            vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
            vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
            vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
        }
#else
    // Declare (prototype) the function only if NOT defining it at the top...
    void vprod( double vgr[3], double vg1[3], double vg2[3]);
#endif


int main() {

    double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
    int LC=1000000L;
    int i,j,k;
    double tiempo=0.0, tinicial;

    //------------------------------------------------------------------------
    std::cout << "INLINE METHOD: " << std::endl;
    tinicial = dsecnd();
    for (i=0; i<LC; i++){
        vr[0] = v1[1]*v2[2]-v1[2]*v2[1];
        vr[1] =-(v1[0]*v2[2]-v1[2]*v2[0]);
        vr[2] = v1[0]*v2[1]-v1[1]*v2[0];
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total:             " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    std::cout << "FUNCTION METHOD: " << std::endl;

    tinicial = dsecnd();
    for (i=0; i<LC; i++){
        vprod (vr,v1,v2);
    };

    tiempo = (dsecnd() - tinicial);
    std::cout << "Tiempo Total:             " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

//    std::cin.ignore();
    return 0;

}


#ifndef DEFINE_vprod_AT_TOP
    #ifdef USE_INLINE_KEYWORD
        inline
    #endif
        void vprod( double vgr[3], double vg1[3], double vg2[3]){
        //void vprod( double *vgr, double *vg1, double *vg2){
            vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
            vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
            vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
        }
#endif

現在、コンパイラが使用するコーディングのトリックは、最適化のレベルが上がるにつれて直線的にはなりません。コンパイラが実行するトリックは、さまざまな最適化レベルでオンになり、「inline」キーワードを使用するかどうかによって異なります。関数をインライン化する以外に、コンパイラが採用する可能性のあるさまざまな種類の最適化が存在する可能性があります (そして私の結果は存在することを示しています)。私が読んだように、「インライン」キーワードは実際には関数をインライン化したいというコンパイラへの提案にすぎず、おそらくインライン化された可能性のある関数をインライン化するかどうかを決定するためのしきい値を調整するだけであることに注意するのは興味深いことですとにかく、最適化がオンになっている場合。最適化をオフにすると、「inline」キーワードが使用されていても、関数はまったくインライン化されなかったようです。

于 2012-10-05T17:57:28.387 に答える
3

マーティン、あなたは絶対に正しいです(マーティンのコメントを参照してください...私の2012年10月5日17:57の回答の下の3番目のコメント)。はい、より高い最適化レベルでは、コンパイラーは、配列の入力値を認識していることを認識して、コンパイル時に計算全体、ループ、およびすべてを実行し、ループを完全に最適化できるようにしたようです。

テストコードを3つの別々のファイル(1つのヘッダーと2つのソースファイル)に再コーディングし、計算とループを別々の関数に分割して、コンパイラーが最適化で賢くなりすぎないようにしました。現在、ループをコンパイル時の計算に最適化することはできません。以下は私の新しい結果です。元の0から1000000のループの周りに別のループ(0から50)を追加し、次に50で割ったことに注意してください。これを行った理由は2つあります。これにより、今日の数値を以前の数値と比較できるようになり、不規則性も平均化されます。テストの途中でプロセスが入れ替わったため。dsecnd()は特定のプロセスのCPU時間のみを報告すると思うので、それはあなたにとって重要ではないかもしれません。

とにかく、これが私の新しい結果です......。

(はい、「インラインキーワードなし、最適化-O1」の奇妙な結果は、「インラインキーワードなし、最適化-O1」の奇妙な結果と同様に、繰り返し可能です。アセンブリを掘り下げませんでした。理由を確認してください。)

//========================================================================================
// File: so.h

void loop_inline( const int LC, double vgr[3], double vg1[3], double vg2[3]);
void loop_func( const int LC, double vgr[3], double vg1[3], double vg2[3]);

//---------------------------------
// Comment or uncomment to test both ways...
#define USE_INLINE_KEYWORD
//
// Using g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-52) on an x86 machine...
//
//                                 microseconds          microseconds
//                               "hardcoded inline"   "via vprod() function"
//                                                     [i]=inlined, [-]=not
//                               ------------------   ----------------------
// inline keyword
//      no optimization                11734               14598 [-]
//      optimization -O1                4617                4616 [i]
//      optimization -O2                7754                7838 [i]
//      optimization -O3                7777                7673 [i]
//
// no inline keyword
//      no optimization                11807               14602 [-]
//      optimization -O1                4651                7691 [-]
//      optimization -O2                7755                7383 [-]
//      optimization -O3                7921                7432 [-]
//
// Note that in all cases, both results were reported as -213.458.
//
/* My cut & paste "build & test script" to run on the Linux command prompt...

echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""; echo ""
rm -f a.out; g++ -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "No optimization:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O1 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O1:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O2 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O2:---------------"; objdump -d a.out | grep call | grep vprod; a.out
rm -f a.out; g++ -O3 -c so.cpp so2.cpp; g++ so.o so2.o;
echo ""; echo "Optimization -O3:---------------"; objdump -d a.out | grep call | grep vprod; a.out

...if the "objdump -d a.out | grep call | grep vprod" command returns something
like "call   8048754 <_Z5vprodPdS_S_>", then I know that the call to vprod() is
NOT inlined, whereas if it returns nothing, I know the call WAS inlined.

*/

//========================================================================================
// File: so.cpp

// Sorry so messy, I didn't bother to clean up the #includes.......
#include <stdint.h>
#include <inttypes.h>
#include <stddef.h> // for NULL
#include <stdlib.h> // for exit()
#include <stdio.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <iostream>
//#include <mkl.h>

#include "so.h"

// My standin for dsecnd() since I don't have "mkl.h"...
#include <sys/time.h>
double dsecnd()
{
    struct timeval tv;
    if (gettimeofday(&tv,NULL))
    {
        fprintf(stderr,"\ngettimeofday() error\n\n");
        exit(1);
    }
    return tv.tv_sec*1000000 + tv.tv_usec; // ...returns MICROSECONDS
    //return tv.tv_sec + ((double)tv.tv_usec)/1000000; // ...returns SECONDS
}

//---------------------------------

#ifndef USE_INLINE_KEYWORD
    // We're NOT using the 'inline' keyword, so define vprod() in this
    // file so it can't possibly be inlined where it's called (in the
    // other source file).
    void vprod( double vgr[3], double vg1[3], double vg2[3]){
    //void vprod( double *vgr, double *vg1, double *vg2){
        vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
        vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
        vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
    }
#endif

int main() {

    double v1[3]={1.22, 2.65, 3.65}, v2[3]={6.98, 98.159, 54.65}, vr[3];
    int LC=1000000L;
    int i, N=100;
    double tiempo=0.0, tinicial;

    //------------------------------------------------------------------------
    std::cout << "INLINE METHOD: " << std::endl;

    tinicial = dsecnd();

    for (i=0; i<N; ++i)
        loop_inline(LC,vr,v1,v2);

    tiempo = (dsecnd() - tinicial)/N;
    std::cout << "Tiempo Total:             " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

    //------------------------------------------------------------------------
    std::cout << "FUNCTION METHOD: " << std::endl;
    tinicial = dsecnd();

    for (i=0; i<N; ++i)
        loop_func(LC,vr,v1,v2);

    tiempo = (dsecnd() - tinicial)/N;
    std::cout << "Tiempo Total:             " << tiempo << std::endl;
    std::cout << "Resultado: " << vr[0] << std::endl;
    //------------------------------------------------------------------------

//    std::cin.ignore();
    return 0;
}

//========================================================================================
// File: so2.cpp

#include "so.h"

#ifdef USE_INLINE_KEYWORD
    inline void vprod( double vgr[3], double vg1[3], double vg2[3]){
    //void vprod( double *vgr, double *vg1, double *vg2){
        vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
        vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
        vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
    }
#else
    // Not using 'inline' keyword, so just declare (prototype) the
    // function here and define it in the other source file (so it
    // can't possibly be inlined).
    void vprod( double vgr[3], double vg1[3], double vg2[3]);
#endif

void loop_inline( const int LC, double vgr[3], double vg1[3], double vg2[3]){

    for (int i=0; i<LC; i++) {
        vgr[0] = vg1[1]*vg2[2]-vg1[2]*vg2[1];
        vgr[1] =-(vg1[0]*vg2[2]-vg1[2]*vg2[0]);
        vgr[2] = vg1[0]*vg2[1]-vg1[1]*vg2[0];
    }
}

void loop_func( const int LC, double vgr[3], double vg1[3], double vg2[3]){

    for (int i=0; i<LC; i++) {
        vprod (vgr,vg1,vg2);
    }
}
于 2012-10-09T15:13:46.213 に答える