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 {}
}

所有元素方法(碳、氢等)只生成一个随机数并返回一个双精度数。

Java 代码的运行时间为 77.471 秒,C++ 为 121.777 秒。

诚然,我在 C++ 方面不是很有经验,所以原因可能只是代码编写不当。

4

2 回答 2

1

Java(实际上是 JIT)通常非常擅长检测无用的代码。这是因为 JIT 可以在运行时获取静态编译器无法确定的信息。对于可以优化掉的代码,Java 实际上可以比 C++ 更快。然而,一般来说,经过良好调整的 C++ 程序比 Java 中的程序要快。

简而言之,给定任何时间,对于一个易于理解、调优的程序来说,C++ 会更快。然而,鉴于资源有限、需求变化和混合能力的团队,Java 通常可以在很大程度上胜过 C++。

综上所述,可能是 C++ 中的随机数更好,但成本更高。

于 2013-07-25T19:37:21.607 回答
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;
}

选择具有特定概率的两个值之一。大概您打算在 10000 次中选择第一个值约 107 次(尽管使用%withrand()并不能完全满足您的要求)。当您在循环中运行它并将结果求和时,如下所示:

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

你基本上会得到sum += X*13.0033548378 + Y*12.0;X 是随机数保持在阈值以下的次数,而 Y 是(试验-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 {
     }
   }
}

代码的原始版本花了我的系统大约一分 30 秒。这里的最后一个版本需要 11 秒。


这是一个使用两个 binomial_distributions 生成氧和的函子。也许其他发行版之一可以一次性做到这一点,但我不知道。

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 回答