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