[英]C++ and Java performance
这个问题只是推测性的。
我在C ++中有以下实现:
using namespace std;
void testvector(int x)
{
vector<string> v;
char aux[20];
int a = x * 2000;
int z = a + 2000;
string s("X-");
for (int i = a; i < z; i++)
{
sprintf(aux, "%d", i);
v.push_back(s + aux);
}
}
int main()
{
for (int i = 0; i < 10000; i++)
{
if (i % 1000 == 0) cout << i << endl;
testvector(i);
}
}
在我的框中,这个程序大约执行。 12秒; 令人惊讶的是,我在Java中使用类似的实现[使用String和ArrayList],它的运行速度比我的C ++应用程序快很多(大约2秒)。
我知道Java HotSpot在转换为native时会执行很多优化,但我认为如果这样的性能可以用Java完成,它也可以用C ++实现......
那么,你认为应该在上面的程序中修改,或者我不知道,在使用的库中或在内存分配器中,在这个东西中达到类似的性能? (写这些东西的实际代码可能会很长,因此,讨论它会很棒)...
谢谢。
你必须小心性能测试,因为它很容易欺骗自己或者不喜欢比较。
但是,我已经看到了将C#与C ++进行比较的类似结果,并且有许多着名的博客文章关于当遇到这种证据时本机编码器的惊讶。 基本上,一个好的现代代压缩GC非常适合大量的小型分配。
在C ++的默认分配器中,每个块都被视为相同,因此分配和释放的平均成本相当高。 在分代GC中,所有块都非常非常便宜地分配(几乎和堆栈分配一样便宜),如果它们变得短暂,那么它们的清理也非常便宜。
这就是为什么与更现代的语言相比,C ++的“快速性能” - 在很大程度上 - 是神话。 您必须先将C ++程序从所有识别中调整好,然后才能与同等天真编写的C#或Java程序的性能竞争。
您的所有程序都以1000的步长打印数字0..9000。对testvector()
的调用什么都不做,可以消除。 我怀疑你的JVM注意到了这一点,并且本质上是在优化整个功能。
只需注释掉对testvector()
的调用,就可以在C ++版本中实现类似的效果!
嗯,这是一个非常无用的测试,只测量小对象的分配。 也就是说,简单的改变使我的运行时间从大约15秒减少到大约4秒。 新版本:
typedef vector<string, boost::pool_allocator<string> > str_vector;
void testvector(int x, str_vector::iterator it, str_vector::iterator end)
{
char aux[25] = "X-";
int a = x * 2000;
for (; it != end; ++a)
{
sprintf(aux+2, "%d", a);
*it++ = aux;
}
}
int main(int argc, char** argv)
{
str_vector v(2000);
for (int i = 0; i < 10000; i++)
{
if (i % 1000 == 0) cout << i << endl;
testvector(i, v.begin(), v.begin()+2000);
}
return 0;
}
real 0m4.089s
user 0m3.686s
sys 0m0.000s
Java版本有时间:
real 0m2.923s
user 0m2.490s
sys 0m0.063s
(这是我原始程序的直接java端口,除了它将ArrayList作为参数传递以减少无用的分配)。
总而言之,Java上的小分配速度更快,内存管理在C ++中更麻烦一些。 但我们已经知道了:)
Hotspot优化了代码中的热点。 通常,执行10000次的任何事情都会尝试优化。
对于此代码,在5次迭代之后,它将尝试优化内部循环,将字符串添加到向量。 它将做的更多优化将包括逃避分析方法中的变量。 向量是一个局部变量,永远不会逃避本地上下文,它很可能会删除方法中的所有代码并将其转换为无操作。 要对此进行测试,请尝试从该方法返回结果。 即使这样,也要小心对结果做一些有意义的事情 - 例如,只要得到它的长度就可以优化,因为horpsot可以看到结果总是与循环中的迭代次数相同。
所有这些都指向动态编译器(如热点)的关键优势 - 使用运行时分析,您可以优化在运行时实际执行的操作并消除冗余代码。 毕竟,自定义C ++内存分配器的效率并不重要 - 不执行任何代码总是会更快。
在我的框中,这个程序大约执行。 12秒; 令人惊讶的是,我在Java中使用类似的实现[使用String和ArrayList],它的运行速度比我的C ++应用程序快很多(大约2秒)。
我不能重现那个结果。
为了解释Alex提到的优化,我修改了代码,以便Java和C ++代码在testvector
方法的末尾打印出v
向量的最后结果。
现在,C ++代码(使用-O3
编译)的运行速度与您的一样快(12秒)。 Java代码(直截了当,使用ArrayList
而不是Vector
虽然我怀疑这会影响性能,这要归功于转义分析)大约需要两倍的时间。
所以这个结果绝不显著我没有做大量的测试。 它只是表明让这些测试完全错误是多么容易,以及单个测试对真实性能的评价很少。
仅供记录,测试是在以下配置上运行的:
$ uname -ms
Darwin i386
$ java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03-226)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02-92, mixed mode)
$ g++ --version
i686-apple-darwin9-g++-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5490)
如果你使用Vector::reserve
在循环之前为v
z
元素保留空间应该会有所帮助(但是同样的事情也应该加速这段代码的java等价物)。
为了说明为什么C ++和java的性能不同,看两者的来源都很重要,我可以在C ++中看到一些性能问题,对于一些人来说,看看你是否在java中做同样的事情会很有用(例如通过std :: endl刷新输出流,你是否调用System.out.flush()或只是附加一个'\\ n',如果后来你刚刚给了java一个明显的优势)?
你在这里真正想要测量的是什么? 将整数放入向量中?
您可以首先将空间预先分配到具有已知向量大小的向量中:
代替:
void testvector(int x)
{
vector<string> v;
int a = x * 2000;
int z = a + 2000;
string s("X-");
for (int i = a; i < z; i++)
v.push_back(i);
}
尝试:
void testvector(int x)
{
int a = x * 2000;
int z = a + 2000;
string s("X-");
vector<string> v(z);
for (int i = a; i < z; i++)
v.push_back(i);
}
在你的内循环中,你将ints推入一个字符串向量。 如果你只是在机器代码级别单步执行,我敢打赌你发现很多时间用于分配和格式化字符串,然后一段时间进入回退(更不用说你释放时的释放)矢量)。
根据开发人员对人们合理想要做的事情的理解,这可能会在运行时库实现之间轻松变化。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.