Java与C ++中随机数生成实现的时差

时间:2013-07-25 17:42:17

标签: java c++ montecarlo

我在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 ++方面不是很有经验,所以原因很可能是编写错误的代码。

2 个答案:

答案 0 :(得分:1)

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

简而言之,在任何时间内,对于一个理解良好,调整良好的程序,C ++会更快。但是,由于资源有限,不断变化的需求和混合能力Java团队通常可以大幅超越C ++。

所有这一切,可能是C ++中的随机更好,但更昂贵。

答案 1 :(得分: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()的结果中手动计算所需发行版的方法更好。< / p>


好的,这里是如何从代码中消除内部循环并大大加快它的速度(在Java或C ++中)。

您的代码:

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

以特定概率选择两个值中的一个。据推测,你打算在10000中选出约107次的第一个值(虽然%使用rand()并不能完全满足你的要求。当您在循环中运行它并将结果汇​​总为:

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