1

Java でモンテカルロ シミュレーションを作成しています。これには、多数のランダムな整数の生成が含まれます。私の考えでは、乱数の生成にはネイティブ コードの方が高速なので、C++ でコードを記述し、JNI 経由で出力を返す必要があります。しかし、同じメソッドを C++ で書いたところ、実際には Java 版よりも実行に時間がかかりました。コードサンプルは次のとおりです。

Random rand = new Random();
int threshold = 5;
int[] composition = {10, 10, 10, 10, 10};
for (int j = 0; j < 100000000; j++) {
    rand.setSeed(System.nanoTime());
    double sum = 0;
    for (int i = 0; i < composition[0]; i++) sum += carbon(rand);
    for (int i = 0; i < composition[1]; i++) sum += hydrogen(rand);
    for (int i = 0; i < composition[2]; i++) sum += nitrogen(rand);
    for (int i = 0; i < composition[3]; i++) sum += oxygen(rand);
    for (int i = 0; i < composition[4]; i++) sum += sulfur(rand);
    if (sum < threshold) {}//execute some code
    else {}//execute some other code
}

C++ での同等のコード:

int threshold = 5;
int composition [5] = {10, 10, 10, 10, 10};
for (int i = 0; i < 100000000; i++)
{
    srand(time(0));
    double sum = 0;
    for (int i = 0; i < composition[0]; i++) sum += carbon();
    for (int i = 0; i < composition[1]; i++) sum += hydrogen();
    for (int i = 0; i < composition[2]; i++) sum += nitrogen();
    for (int i = 0; i < composition[3]; i++) sum += oxygen();
    for (int i = 0; i < composition[4]; i++) sum += sulfur();
    if (sum > threshold) {}
    else {}
}

すべての要素メソッド (炭素、水素など) は、乱数を生成して double を返すだけです。

実行時間は、Java コードで 77.471 秒、C++ で 121.777 秒です。

確かに、私は C++ の経験があまりないので、コードの書き方が悪いだけが原因である可能性があります。

4

2 に答える 2

1

パフォーマンスの問題は、、、、、および関数の本体carbon()hydrogen()あるnitrogen()と思われます。ランダムデータをどのように生成するかを示す必要があります。oxygen()sulfur()

または、コード内にある可能性がありif (sum < threshold) {} else {}ます。

結果が決定論的ではないようにシードを設定し続けたかった (真にランダムに近い)

の結果をtime(0)シードとして使用しているため、どちらの方法でも特にランダムな結果は得られません。

を使用する代わりに、ライブラリを見て、ニーズを満たすパフォーマンス/品質特性を持つエンジンを選択する必要がありますsrand()。実装がサポートしている場合は、 (シードを生成するため、またはエンジンとして)非決定論的なランダム データを取得することもできます。rand()<random>std::random_device

さらに、 の結果から必要な分布を手動で計算する平均的なプログラマーの方法よりも優れている可能性が高い、<random>事前に作成された分布を提供します。std::uniform_real_distribution<double>rand()


コードから内側のループをなくして大幅に高速化する方法を次に示します (Java または C++ の場合)。

あなたのコード:

double carbon() {
  if (rand() % 10000 < 107)
    return 13.0033548378;
  else
    return 12.0;
}

特定の確率で 2 つの値のいずれかを選択します。おそらく、最初の値が 10000 回中約 107 回選択されることを意図していたと思われます (ただし、with を使用%rand()ても、それは実現しません)。これをループで実行し、結果を次のように合計すると:

for (int i = 0; i < composition[0]; i++) sum += carbon();

基本的にsum += X*13.0033548378 + Y*12.0;、X は乱数がしきい値を下回った回数、Y は (trials-X) であることがわかります。たまたま、一連の試行を実行し、二項分布を使用して成功数を計算することをシミュレートでき、<random>たまたま二項分布が提供されます。

与えられた関数sum_trials()

std::minstd_rand0 eng; // global random engine

double sum_trials(int trials, double probability, double A, double B) {
  std::binomial_distribution<> dist(trials, probability);
  int successes = dist(eng);
  return successes*A + (trials-successes)*B;
}

carbon()ループを置き換えることができます:

sum += sum_trials(composition[0], 107.0/10000.0, 13.003354378, 12.0); // carbon trials

使用している実際の値はありませんが、ループ全体は次のようになります。

  for (int i = 0; i < 100000000; i++) {
     double sum = 0;
     sum += sum_trials(composition[0], 107.0/10000.0, 13.003354378, 12.0); // carbon trials
     sum += sum_trials(composition[1], 107.0/10000.0, 13.003354378, 12.0); // hydrogen trials
     sum += sum_trials(composition[2], 107.0/10000.0, 13.003354378, 12.0); // nitrogen trials
     sum += sum_trials(composition[3], 107.0/10000.0, 13.003354378, 12.0); // oxygen trials
     sum += sum_trials(composition[4], 107.0/10000.0, 13.003354378, 12.0); // sulfur trials

     if (sum > threshold) {
     } else {
     }
   }

ここで注意すべきことは、関数内で同じデータを使用して何度も分布を構築していることです。関数を関数オブジェクトに置き換えることでそれを抽出できsum_trials()ます。関数オブジェクトは、ループの前に一度適切なデータで構築し、ファンクターを繰り返し使用します。

struct sum_trials {
  std::binomial_distribution<> dist;
  double A; double B; int trials;

  sum_trials(int t, double p, double a, double b) : dist{t, p}, A{a}, B{b}, trials{t} {}

  double operator() () {
    int successes = dist(eng);
    return successes * A + (trials - successes) * B;
  }
};

int main() {
  int threshold = 5;
  int composition[5] = { 10, 10, 10, 10, 10 };

  sum_trials carbon   = { composition[0], 107.0/10000.0, 13.003354378, 12.0};
  sum_trials hydrogen = { composition[1], 107.0/10000.0, 13.003354378, 12.0};
  sum_trials nitrogen = { composition[2], 107.0/10000.0, 13.003354378, 12.0};
  sum_trials oxygen   = { composition[3], 107.0/10000.0, 13.003354378, 12.0};
  sum_trials sulfur   = { composition[4], 107.0/10000.0, 13.003354378, 12.0};


  for (int i = 0; i < 100000000; i++) {
     double sum = 0;

     sum += carbon();
     sum += hydrogen();
     sum += nitrogen();
     sum += oxygen();
     sum += sulfur();

     if (sum > threshold) {
     } else {
     }
   }
}

コードの元のバージョンは、私のシステムで約 1 分 30 秒かかりました。ここでの最後のバージョンは 11 秒かかります。


これは、2 つの binomial_distribution を使用して酸素の合計を生成するファンクターです。たぶん、他のディストリビューションの 1 つがこれを一発で実行できるかもしれませんが、私にはわかりません。

struct sum_trials2 {
  std::binomial_distribution<> d1;
  std::binomial_distribution<> d2;
  double A; double B; double C;
  int trials;
  double probabilty2;

  sum_trials2(int t, double p1, double p2, double a, double b, double c)
    : d1{t, p1}, A{a}, B{b}, C{c}, trials{t}, probability2{p2} {}

  double operator() () {
    int X = d1(eng);
    d2.param(std::binomial_distribution<>{trials-X, p2}.param());
    int Y = d2(eng);

    return X*A + Y*B + (trials-X-Y)*C;
  }
};

sum_trials2 oxygen{composition[3], 17.0/1000.0, (47.0-17.0)/(1000.0-17.0), 17.9999, 16.999, 15.999};

合計が を下回る確率を計算できれば、これをさらに高速化できますthreshold

int main() {
  std::minstd_rand0 eng;
  std::bernoulli_distribution dist(probability_sum_is_over_threshold);

  for (int i=0; i< 100000000; ++i) {
    if (dist(eng)) {
    } else {
    }
  }
}

他の要素の値が負になる可能性がない限り、合計が 5 を超える確率は 100% です。その場合、ランダム データを生成する必要さえありません。コードの 'if' ブランチを 100,000,000 回実行します。

int main() {
  for (int i=0; i< 100000000; ++i) {
    //execute some code
  }
}
于 2013-07-25T19:49:52.513 に答える
1

Java (実際には JIT) は一般に、何の役にも立たないコードの検出に非常に優れています。これは、JIT が実行時に静的コンパイラーでは判断できない情報を取得できるためです。最適化して取り除くことができるコードの場合、Java は実際には C++ よりも高速です。ただし、一般に、適切に調整された C++ プログラムは、Java のプログラムよりも高速です。

要するに、どのような時間が与えられても、よく理解され、よく調整されたプログラムでは、C++ の方が高速になります。ただし、リソースが限られている場合、要件が変化し、能力が混在するチームの Java は、多くの場合、C++ を大幅に上回るパフォーマンスを発揮します。

そうは言っても、C++ のランダムの方が優れている可能性がありますが、より高価です。

于 2013-07-25T19:37:21.607 に答える