大学で勉強していたときに、同等のプログラムでは、FortranコンパイラーがCコンパイラーよりも高速なコードを生成するという考えをよく耳にしました。
主な理由は次のとおりです:Cコンパイラはコードの行ごとに平均1,1プロセッサ命令を出力するのに対し、Fortranコンパイラは平均1,1のプロセッサ命令を出力します-私はしません正確な数値を覚えていますが、Cコンパイラは著しく多くのマシンコードを生成し、その結果、プログラムが遅くなるという考えがありました。
そのような比較はどの程度有効ですか? FortranコンパイラーがCコンパイラーよりも高速なプログラムを生成する、またはその逆であると言えるでしょうか。なぜこの違いがあるのでしょうか。
IIRCは、Fortranがより高速であると言われている主な理由の1つは ポインターエイリアシング がないことです。そのため、Cコンパイラーが使用できない最適化を使用できます。
FORTRANでは、関数の引数は相互にエイリアスしない可能性があり、コンパイラーは相互にエイリアスしないと想定します。これは優れた最適化を可能にし、高速言語としてのFORTRANの評判の1つの主要な理由です。 (エイリアスはFORTRAN関数内でも発生する可能性があることに注意してください。たとえば、Aが配列で、iとjが偶然同じ値を持つインデックスである場合、A [i]とA [j]は2つの異なる名前です。同じメモリ位置。幸いにも、ベース配列は同じ名前でなければならないため、インデックス分析を実行して、A [i]とA [j]がエイリアスできない場合を判別できます。)
しかし、私はここで他の人に同意します。コード行に対して生成されたアセンブラー命令の平均数を比較することは、まったくナンセンスです。たとえば、最新のx86コアは、同じレジスタにアクセスしない場合、2つの命令を並行して実行できます。したがって、(理論的には)命令の同じセットを並べ替えるだけで、パフォーマンスを100%向上させることができます。優れたコンパイラーは、多くの場合、より高速なコードを取得するためにmoreアセンブリー命令も生成します(ループのアンロール、インライン化など)。アセンブラー命令の総数は、コードの一部のパフォーマンスについてはほとんど意味がありません。
完全に無効な比較。
まず、@PéterTörökが指摘しているように、最初にFortranとCの同等のプログラムの行数を比較する必要がありますこれは、生成される行数の比較としても有効です。
第二に、コードの少ない行が常により高速なプログラムと同じであるとは限りません。すべての機械語命令が同じ実行するサイクル数をとるわけではありませんが、メモリアクセス、キャッシュなどの他の問題もあります。
その上、実行コードの数が少なくなるため、長いコードの実行はより速くなります(つまり、Line Count!= Executed Line Count)。
Danは正解です。長いプログラムは遅いプログラムを意味するものではありません。それは彼らが何をしているかに大きく依存します。
私はFortranの専門家ではありません。少し知っています。それらを比較すると、よく書かれたCは、Fortranよりも複雑なデータ構造と機能を備えたパフォーマンスではるかに優れていると思います。私がここで間違っている場合は誰かが私を修正してください(ただし、FortranはCよりも「低いレベル」にあると思います)。
もう1つ、一見したところ、コンパイラの方が高速かどうかを尋ねていると思いました。実際には、Fortranは一般に同じ量のコードでより速くコンパイルされると思いますが、結果として得られるプログラムとその実行方法は別の話になります。解析する方が簡単です。
この発言は、Cが幼少期にあった昔(70年代後半)に当てはまった可能性があり、Fortranはすべての主要メーカーによってサポートされ、高度に最適化されました。初期のFortransはIBMアーキテクチャに基づいていたため、アセンブリ命令ごとに1つのステートメントであった場合、算術演算のような単純なものでした。これは、3方向のジャンプがあったData GeneralやPrimeなどの古いマシンにも当てはまります。これは、3方向ジャンプがない最新の命令セットでは機能しません。
コードの行は、コードのステートメントと同じではありません。 Fortranの以前のバージョンでは、1行に1つのステートメントしか使用できませんでした。 Fortranの新しいバージョンでは、1行に複数のステートメントを使用できます。 Cは行ごとに複数のステートメントを持つことができます。 IntelのIVF(以前のCVF、MS Powerstation)やIntelのCなどのより高速なプロダクションコンパイラでは、実際には2つの間に違いはありません。これらのコンパイラは高度に最適化されています。
古いスタイルのFORTRANでは、配列の一部を関数で利用できるようにしたいプログラマは、配列全体への参照を、開始添え字と終了添え字または項目数を指定する1つ以上の整数値とともに渡す必要がありました。 。 Cは、これを単純化して、要素の数とともに関心のある部分の先頭へのポインターを渡すことを可能にします。直接的に言えば、これにより処理が速くなります(3つではなく2つ渡す)。ただし、間接的に、コンパイラーが実行できる最適化の種類を制限することで、物事が遅くなる可能性があります。
関数を考えてみましょう:
void diff(float dest[], float src1[], float src2[], int n)
{
for (int i=0; i<n; i++)
dest[i] = src1[i] - src2[i];
}
コンパイラーは、各ポインターが配列の開始を識別することを知っていれば、配列の要素に並列に、または任意の順序で作用するコードを生成できます。 ]はsrc1 [y]やsrc2 [y]には影響しません。たとえば、一部のシステムでは、コンパイラは次のコードと同等のコードを生成することでメリットを得られます。
void dif(float dest[], float src1[], float src2[], int n)
{
int i=0;
float t1a,t1b,t2a,t2b,tsa,tsb;
if (n > 2)
{
n-=4;
t1a = src1[n+3]; t1b = src2[n+3]; t1b=src2[n+2]; t2b = src2[n+2];
do
{
tsa = t1a-t2a;
t1a = src1[n+1]; t2a = src2[n+1];
tsb = t2b-t2b;
dest[n+3] = tsa;
t1b = src1[n]; t2b = src2[n];
n-=2;
dest[n+4] = tsb;
} while(n >= 0);
... add some extra code to handle cleanup
}
else
... add some extra code to handle small values of n
}
値をロードまたは計算するすべての操作には、その操作とその値を使用する次の操作の間に少なくとも1つの操作があることに注意してください。一部のプロセッサーは、このような条件が満たされると、さまざまな操作の処理をオーバーラップして、パフォーマンスを向上させることができます。ただし、Cコンパイラーは、コードにポインターが渡されないことを知る方法がないため、共通配列の部分的に-重複領域へのポインターであるため、Cコンパイラーは上記の変換を行うことができません。 。ただし、同等のコードが与えられたFORTRANコンパイラは、そのような変換を行うことができ、実際に行いました。
Cプログラマーは、ループを展開して隣接するパスの操作をオーバーラップするコードを明示的に書き込むことで同等のパフォーマンスを達成しようとする可能性がありますが、そのようなコードは、コンパイラーがそれらを「こぼす」必要があるほど多くの自動変数を使用した場合、パフォーマンスを簡単に低下させる可能性がありますメモリ。 FORTRANコンパイラーのオプティマイザーは、特定のシナリオでどの形式のインターリーブが最適なパフォーマンスをもたらすかについてプログラマー以上のことを知っている可能性が高く、そのような決定は多くの場合、そのようなコンパイラーに任せるのが最善です。 C99はrestrict
修飾子を追加してCの状況を多少改善しようとしましたが、dest[]
がsrc1[]
とsrc2[]
の両方とは別の配列である場合にのみ使用できます。すべてのdest
がsrc1
およびsrc2
から切り離されている場合を処理するために、プログラマーがループの個別のバージョンを追加した場合、src1[]
およびdest
は等しいとsrc2
は互いに素であり、src2[]
とdest[]
は互いに素であり、src1
は互いに素であり、3つの配列すべてが同等でした。対照的に、FORTRANは、同じソースコードと同じマシンコードを使用して、4つのケースすべてを問題なく処理できます。
その一部は、FORTRANコンパイラーがいくつかのタイプの計算を非常に高速に実行するように設計されていることだと思います。これが、人々がFORTRANを使用する理由の1つです。