1

次のエントリを 1 と前のエントリに設定して、配列を反復処理する次のベンチマークがあります。数が特定の上限を超えた場合は、エントリをゼロに設定して続行します。最後に、配列内のエントリを合計します。

質問 : PolyML のベンチマーク結果を改善するにはどうすればよいですか?

Ubuntu x86-64 での時間は次のとおりです。

polyml (using CFLAGS=O3) = 
1250034994

real    0m54.207s
user    0m52.604s
sys 0m0.792s

g++ (O3) = 
1250034994

real    0m4.628s
user    0m4.578s
sys 0m0.028s

mlton は C コード (5.2s) とほぼ同じ速度で実行できますが、最新バージョンの gcc を使用して Windows 7 でシームレスにビルドできる PolyML に特に興味があります。(MSYS / MSYS2 および mingw gcc コンパイラを使用した Windows 7 での polyML のビルド手順については、http://lists.inf.ed.ac.uk/pipermail/polyml/2015-August/001593.html を参照してください)

Windows 7 では、最新バージョンの gcc を使用して mlton の最新バージョンをビルドする際に問題が発生しました ( https://github.com/MLton/mlton/issues/61#issuecomment-50982499と同様の問題 ) 。

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

val size:int = 50000;
val loops:int = 30000;
val cap:int = 50000;

val data:int array = Array.array(size,0);


fun loop () = 
  let 
    fun loopI i = 
      if i = size then
        let val _ = () in
          Array.update(data,0,Array.sub(data,size-1));
          ()
        end
      else 
        let val previous = Array.sub(data,i-1) 
            val use = if previous > cap then 0 else previous in
          Array.update(data,i,use+1);
          loopI (i+1)
      end
  in loopI 1 end

fun benchmarkRun () = 
  let
    fun bench i = 
      if i = loops then ()
      else let val _ = () in 
             loop ();
             bench (i+1)
           end
  in bench 1 end

fun sum (i,value) =
  if i = size then value 
  else sum(i+1,value+Array.sub(data,i))

fun main () = let val _ = () in 
  benchmarkRun();
  print (Int.toString (sum (0,0)));
  print "\n"
  end

(*val _ = main ()*)

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

#include <iostream>
#include <vector>
using namespace std;

int size = 50000;
int loops = 30000;
int cap = 50000;

vector<int> data(size);

void loop(){
  int previous, use;
  for(int i=1; i<size; i++){
    previous = data[i-1];
    if(previous > cap){
      use = 0;
    }else{
      use = previous;
    }
    data[i] = use + 1;
  }
  data[0] = data[size-1];
}

void benchmarkRun(){
  for(int i=1; i<loops; i++){
    loop();
  }
}

int sum(){
  int res = 0;
  for(int i=0; i<size; i++){
    res += data[i];
  }
  return res;
}

int main(){
  benchmarkRun();
  cout<<sum()<<endl;
}
4

1 に答える 1

2

プログラムに問題はないと思います。私の経験では、mlton は、特に「C に似た」コードの場合、大幅な差をつけて最高のパフォーマンスを発揮する SML コンパイラです。

コンパイラがより良い仕事をするのに役立つかもしれない、別の方法で書くことができるいくつかの方法を次に示します。

Poly/ML が配列の各要素をボックス化している可能性があります。ボックス化とは、単に整数のフラットな配列を格納するのではなく、整数値を含むオブジェクトを割り当てることを意味します。これは非常にコストがかかります。より多くの割り当て、間接化、劣悪なキャッシュの局所性、およびより高価な GC があります。これはコンパイラにとって基本的なことですが、IntArray.array や Word32Array.array などの単相配列を使用すると、パフォーマンスが向上する可能性があります。これらは Basis のオプション部分です: http://sml-family.org/Basis/mono-array.html

境界チェックのために遅くなる場合があります。ループを反復するたびに、「サブ」呼び出しと「更新」呼び出しを実行します。それぞれが、配列のサイズに対して引数を (素朴に) チェックし、境界外の場合は分岐して例外をスローします。次の方法で、境界チェックによるペナルティを減らすことができる場合があります。

  • 入力インデックスと出力インデックスが境界内にあることを知ることができる Array.modifyi のような関数を使用します (「サブ」の料金を支払う必要があります)。
  • ArraySlice.foldli のような関数を使用して、前のセルの値を次の反復に渡すこともできます
  • 安全でない配列アクセスの使用 (Poly/ML がサポートしている場合、「安全でない」構造を探します)。

整数のオーバーフロー チェックが原因で、処理が遅くなる場合があります。ここでは、各加算の後、結果を表すことができないかどうかを確認し、分岐して例外をスローします。int の代わりに Word32.word などを使用すると、パフォーマンスが向上する場合があります。これをオフにするためのコンパイラ フラグもありますが、他の人のコードが言語のこの部分に依存している可能性があるため、これを行うのは非常に危険です。

これらの変換のほとんどは、コードの見栄えを悪くします。Array.sub で読み取る代わりに、前の要素の値を loopI 関数に渡すと、プログラムとそのパフォーマンスの両方が改善されると思います。あなたは通常、その価値を持っていました。

ただし、パフォーマンスが気になる場合は、mlton をお勧めします。私は mingw64 で x86_64 バイナリを使用しており、C コードへのリンクを含めて動作します。

于 2015-09-20T14:30:47.630 に答える