Java在数值算法方面的性能表现

8

我对Java数值算法的性能很感兴趣,比如矩阵矩阵双精度乘法,使用最新的JIT机器与手动调整的SSE C++/汇编或Fortran相比如何。

我在网上搜索过,但大多数结果都来自将近10年前,我了解到Java从那时起已经有了很大的进步。

如果您有使用Java进行数值密集型应用程序的经验,可以分享一下您的经验。此外,Java在内核中的表现如何,其中循环相对较短且内存访问不太均匀,但仍在L1缓存的限制范围内?如果这样的内核连续执行多次,JVM能在运行时优化它吗?

谢谢


3
您最好自己测试一下,因为与C++的比较将会很困难,因为C++是最快的,任何比较都无法访问您特定的实现。 - James Black
1
来访问此页面的其他人请注意:此问题和大多数答案来自2009年。JVM现在比过去要好得多。 - eis
你可能想看看 ND4J,它支持 Java 的 n 维数组。http://nd4j.org/benchmarking.html - racknuf
9个回答

2
我已经用Java编写了一些较大且性能敏感的数值代码(通常是处理大量双精度数组)。
我发现对于快速数值计算,Java已经足够好了。特别是考虑到你通常不会受到CPU限制 - 对于大型数据集,内存延迟和缓存意识可能是你最大的问题。
但是,您仍然可以使用手动优化的C/C++代码来击败Java,利用特定的向量化指令等或高度定制的内存布局。因此,为了获得最快的代码,您可以考虑使用JNI从Java中调用以C/C++编写的核心算法。
个人而言,我发现创建本地代码依赖性通常比它的价值更麻烦,所以我倾向于坚持纯Java方法。

1

这里是Java与C++编程语言对比页面的链接,它将为您提供Java在多个计算密集型算法上的速度比较。它还将展示给您最高性能的Java代码是什么样子的。就这几个特定的基准测试而言,Java大部分情况下需要更长的时间来运行(但不超过2或3倍)。


我无法立即确定 - 这个比较是否忽略了预热时间。在达到巡航速度之前,Java仍需要大量的初始工作。 - Thorbjørn Ravn Andersen
如果您想编写一个启动、进行一些计算,然后关闭的程序,那么您可能不需要Java。但是,如果您的程序将运行几分钟,那么启动时间只是噪音。当然,一个替代方法就是启动一个Java进程并将其作为计算服务器 - 每次需要计算时,只需调用已经运行的实例即可。 - Peter Recore
@Thorbjørn Ravn Andersen - 1)阅读常见问题解答!2)注意程序运行的时间单位为秒而不是微秒!3)查看稳态近似值http://shootout.alioth.debian.org/u64q/benchmark.php?test=all&lang=javasteady&lang2=gpp&box=1 4)阅读有关Java的常见问题解答http://shootout.alioth.debian.org/u64q/faq.php#dynamic - igouy
@igouy 如果你将稳态与Java -server的结果进行比较,它们之间并没有太大的差异。 - Peter Recore
@Peter - 我知道,告诉Thorbjørn :-) [但也要检查FAQ中显示的测量值] - igouy

1
这是来自.NET方面的内容,但我90%确定Java也是如此。虽然JIT会在可能的情况下使用SSE指令,但它目前不会在处理例如矩阵乘法时自动向量化您的代码。手动使用编译器内置函数/内联汇编向量化的C++在这里肯定会更快。

1
Java中最薄弱的点之一是(本机)矩阵运算。这是由于Java矩阵的性质:
  • 您无法声明矩阵为矩形,即每行可以具有不同数量的列。

  • 严格来说,矩阵并不是“double(或int等)的矩阵”,而是一个数组的数组...。最大的区别在于,由于数组是Java对象,因此可以将相同的数组对象分配给多个行。

这两个属性使得许多标准矩阵优化对编译器来说不可能。
使用模拟单个长数组上的矩阵的Java库可能会获得更好的性能。但是,您需要为所有访问调用方法的开销。

2
我认为你的意思是不能声明一个二维数组为矩形。但你似乎在争论Java中最直接和简单的矩阵实现存在一些问题。为什么这会是唯一可能的实现方式?如果不是,那么关于“Java矩阵的本质”的陈述就没有多少意义了。那么像Colt这样的Java矩阵库呢? - Sean Owen
矩阵并不总是以那种方式表示。请查看java.awt.image.Kernel,其中有一个通过1D数组表示的矩阵示例。 - finnw
这类库的问题在于所有矩阵访问都是通过方法完成的。通常情况下,方法调用比数组访问慢,并且会阻止某些编译器优化。例如:for(int i=0; i<m; i++) { x = a[i]; ...}聪明的编译器可以在开头添加一个if语句来检查i m是否小于或等于a[]的长度,如果为真,则可以完全消除for循环中的所有边界检查(如果还可以确定m不会改变)。这也适用于嵌套的for循环,在矩阵操作中非常常见,因此可以节省大量检查。 - Carsten
使用一个长数组而不是多维数组的另一个问题是更难并行运行代码。如果我知道执行的两个部分访问数组中不同的行或列(例如嵌套循环),我知道它们不会互相干扰。对于编译器来说,要知道一个长数组上的操作是否具有这种特性要困难得多。 - Carsten

1

C++肯定会更快。您甚至可以拥有一些手动优化的库,其中包含每个主要CPU的汇编代码,以满足您的需求。这是最好的选择。

之后,如果需要,您可以使用JNI从Java中调用它。

Java不适合进行高性能算术计算。如果您依赖于这些计算,请选择一个适当的低级语言来实现它。或者,您可以使用低级语言编写性能特定的部分,然后使用JNI或其他IPC方法将其连接到Java前端。


0

建议您最好自己测试一下,因为性能会因您所做的具体操作而有所不同。我很难相信Shane C. Mason的回答,即Java的性能将与C++或Fortran的性能相同,因为即使对于某些科学计算算法,C++和Fortran也不能真正进行比较。

我编写了一个使用C++编写的计算流体力学代码,以及基本上翻译成Fortran的相同代码。我还不确定原因,但Fortran版本大约比C++版本快两倍。我猜想,像边界检查和垃圾收集等功能,Java的速度会比两者都慢,但在测试之前我无从得知。


你在C++代码中使用过restrict关键字吗? Fortran编译器不必保证内存指针没有别名,而C++编译器必须假设内存存在别名,除非另有说明。 你用的是哪个编译器?我用intrinsic在C++中编写了我的程序,Intel编译器比GCC快得多,我猜测Intel C++会更好地排序指令,因为汇编除了排序之外非常相似。 - Anycorn
我对别名问题有一些模糊的认识,但还没有完全理解。我还没有尝试过restrict,很遗憾我没有时间去研究它。我在Linux上使用icpc和ifort(都是Intel编译器)并开启了-O3优化选项。需要注意的是,我的观点并不是C++性能无法与Fortran相匹配,而是你需要比较实现方式以及语言本身。 - davidtbernal
Fortran的数值模型比C++更加宽松--默认情况下,它允许进行许多肮脏的数学优化,而这些只有在使用-ffast-math等选项时才能在C/C++中实现。有时这并不重要,但有时它会使你的结果不够准确。 - Stephen Canon

0

这取决于你在C++代码中做什么。

例如,你是否使用GPU?编辑我忘记了jogl,所以Java在这方面可以竞争。

如果你使用STM或共享内存并行化,那么Java无法竞争。关于并行矩阵乘法分析的链接:http://www.cs.utexas.edu/users/plapack/papers/ipps98/ipps98.html

你是否有足够的内存来进行计算,这样就不需要垃圾回收器,并且你是否调整了垃圾回收器以获得最佳性能?那么,Java可能会有竞争力。

你是否使用多核,而且C++是否经过优化以利用这种架构?那么Java将无法竞争。

你是否使用多台计算机连接在一起,那么Java将无法竞争。

如果你使用任何这些组合,那么它将取决于特定的实现。

Java 并不是为了与手工优化的 C++ 程序竞争而设计的,但是调整所需要的时间,是否进行了足够多的计算才会有所作用?Java 将能够在较少的工作量下提供一些合理的速度,但与仅使用 C++ 代码相比,改进并不大。

您可能希望看看是否有比 Haskell 或 Erlang 更好地针对这种类型的工作而设计的改进,例如可以替代 C++。


使用GPU?就是使用OpenGL吗?如果使用JOGL,Java可以很好地竞争。 - Thorbjørn Ravn Andersen
你说得对,我已经更正了我的答案,我忘记了你可以使用jogl来处理GPU工作。 - James Black

0

-4
Java使用即时编译器(JIT)将字节码转换为本机机器语言 - 因此,第一次运行代码块时速度会较慢,但一旦该段被“热身”,性能将是等效的。简而言之 - 数值性能相当不错。

1
JIT很好,但不能保证良好的数值性能。 - Thorbjørn Ravn Andersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接