如何加快Java代码的速度?
我试图测试Java完成一项简单任务的速度:将一个巨大的文件读入内存,然后对数据执行一些无意义的计算。所有类型的优化都很重要。无论是以不同的方式重写代码还是使用不同的JVM,都会欺骗JIT 输入文件是一个5亿长的32位整数对列表,由逗号分隔。像这样: 444395023如何加快Java代码的速度?,java,optimization,benchmarking,compiler-optimization,micro-optimization,Java,Optimization,Benchmarking,Compiler Optimization,Micro Optimization,我试图测试Java完成一项简单任务的速度:将一个巨大的文件读入内存,然后对数据执行一些无意义的计算。所有类型的优化都很重要。无论是以不同的方式重写代码还是使用不同的JVM,都会欺骗JIT 输入文件是一个5亿长的32位整数对列表,由逗号分隔。像这样: 444395023 3314022257 此文件在我的计算机上占用5.5GB。程序使用的RAM不能超过8GB,只能使用单线程 package speedracer; import java.io.FileInputStream; import j
3314022257
此文件在我的计算机上占用5.5GB。程序使用的RAM不能超过8GB,只能使用单线程
package speedracer;
import java.io.FileInputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class Main
{
public static void main(String[] args)
{
int[] list = new int[1000000000];
long start1 = System.nanoTime();
parse(list);
long end1 = System.nanoTime();
System.out.println("Parsing took: " + (end1 - start1) / 1000000000.0);
int rs = 0;
long start2 = System.nanoTime();
for (int k = 0; k < list.length; k++) {
rs = calc(list[k++], list[k++], list[k++], list[k]);
}
long end2 = System.nanoTime();
System.out.println(rs);
System.out.println("Calculations took: " + (end2 - start2) / 1000000000.0);
}
public static int calc(final int a1, final int a2, final int b1, final int b2)
{
int c1 = (a1 + a2) ^ a2;
int c2 = (b1 - b2) << 4;
for (int z = 0; z < 100; z++) {
c1 ^= z + c2;
}
return c1;
}
public static void parse(int[] list)
{
FileChannel fc = null;
int i = 0;
MappedByteBuffer byteBuffer;
try {
fc = new FileInputStream("in.txt").getChannel();
long size = fc.size();
long allocated = 0;
long allocate = 0;
while (size > allocated) {
if ((size - allocated) > Integer.MAX_VALUE) {
allocate = Integer.MAX_VALUE;
} else {
allocate = size - allocated;
}
byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, allocated, allocate);
byteBuffer.clear();
allocated += allocate;
int number = 0;
while (byteBuffer.hasRemaining()) {
char val = (char) byteBuffer.get();
if (val == '\n' || val == ',') {
list[i] = number;
number = 0;
i++;
} else {
number = number * 10 + (val - '0');
}
}
}
fc.close();
} catch (Exception e) {
System.err.println("Parsing error: " + e);
}
}
}
目前,我的解析结果是:26.50秒,计算结果是:11.27秒。我与一个类似的C++基准进行竞争,它在大致相同的时间内完成IO,但是计算只需要4.5s。我的主要目标是尽可能减少计算时间。有什么想法吗
更新:看来主要的速度提升可能来自所谓的。我能够找到一些提示,当前太阳的JIT只做“一些矢量化”,但我不能真正证实这一点。如果能找到一些JVM或JIT能够提供更好的自动矢量化优化支持,那就太好了。在服务器模式下使用Hotspot JVM,并确保。如果垃圾收集是测试的主要部分,那么还要给垃圾收集算法足够的时间,使其稳定下来。我一眼就看不到任何东西会让我认为它是…您是否尝试过“内联”parse()和calc(),即将所有代码放在main()中 如果在列表迭代中移动计算函数的几行,分数是多少?
我知道它不是很干净,但你会从调用堆栈中获益
[...]
for (int k = 0; k < list.length; k++) {
int a1 = list[k++];
int a2 = list[k++];
int b1 = list[k++];
int b2 = list[k];
int c1 = (a1 + a2) ^ a2;
int c2 = (b1 - b2) << 4;
for (int z = 0; z < 100; z++) {
c1 ^= z + c2;
}
rs = c1;
}
[…]
for(int k=0;k int c2=(b1-b2)MappedByteBuffer在I/O性能方面只贡献了约20%,这是一个巨大的内存开销——如果它导致交换,那么治疗效果比疾病更糟糕
我会在文件读取器周围使用BufferedReader,或者在文件读取器周围使用Scanner来获取整数,或者至少是Integer.parseInt(),这比您自己的基数转换代码更有可能被HotSpot预热。有趣的问题。:-)这可能更像是一个注释,因为我不会真正回答您的问题,但是注释框太长了
Java中的微观基准测试是很棘手的,因为JIT可能会疯狂地进行优化。但这段特定的代码欺骗了JIT,使其无法执行正常的优化
通常,此代码将在O(1)时间内运行,因为主循环对任何内容都没有影响:
for (int k = 0; k < list.length; k++) {
rs = calc(list[k++], list[k++], list[k++], list[k]);
}
不知何故,这增加了JIT太多的复杂性,以至于JIT无法确定所有这些代码最终都不会改变任何东西,并且可以优化原始循环
如果您将该特定语句更改为更无意义的内容,例如:
c1 = z + c2;
然后,JIT会处理这些问题并优化您的循环。试试看。:-)
我在本地尝试使用更小的数据集,使用“^=”版本计算耗时约1.6秒,而使用“=”版本则耗时0.007秒(换句话说,它优化了循环)
正如我所说,这不是一个真正的回答,但我认为这可能很有趣。首先,-O3
启用:
-finline-functions
-ftree-vectorize
除其他外
所以看起来它实际上可能是矢量化的
编辑:
这已经得到了证实。(参见评论)> C++版本确实是由编译器进行矢量化的。C++被禁用,C++版本的运行速度比java版本慢一些。
假设JIT不循环化,<强> java版本与C++版本的速度匹配可能是困难的/不可能的。<强> < /P>
现在,如果我是一个聪明的C/C++编译器,下面是我将如何安排该循环(在x64上):
intc1=(a1+a2)^a2;
int c2=(b1-b2)
我试图测试Java完成一项简单任务的速度:将一个巨大的文件读入内存,然后对数据执行一些无意义的计算
如果任务是做一个无意义的计算,那么最好的优化就是不做计算
package speedracer;
import java.io.FileInputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class Main
{
public static void main(String[] args)
{
int[] list = new int[1000000000];
long start1 = System.nanoTime();
parse(list);
long end1 = System.nanoTime();
System.out.println("Parsing took: " + (end1 - start1) / 1000000000.0);
int rs = 0;
long start2 = System.nanoTime();
for (int k = 0; k < list.length; k++) {
rs = calc(list[k++], list[k++], list[k++], list[k]);
}
long end2 = System.nanoTime();
System.out.println(rs);
System.out.println("Calculations took: " + (end2 - start2) / 1000000000.0);
}
public static int calc(final int a1, final int a2, final int b1, final int b2)
{
int c1 = (a1 + a2) ^ a2;
int c2 = (b1 - b2) << 4;
for (int z = 0; z < 100; z++) {
c1 ^= z + c2;
}
return c1;
}
public static void parse(int[] list)
{
FileChannel fc = null;
int i = 0;
MappedByteBuffer byteBuffer;
try {
fc = new FileInputStream("in.txt").getChannel();
long size = fc.size();
long allocated = 0;
long allocate = 0;
while (size > allocated) {
if ((size - allocated) > Integer.MAX_VALUE) {
allocate = Integer.MAX_VALUE;
} else {
allocate = size - allocated;
}
byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, allocated, allocate);
byteBuffer.clear();
allocated += allocate;
int number = 0;
while (byteBuffer.hasRemaining()) {
char val = (char) byteBuffer.get();
if (val == '\n' || val == ',') {
list[i] = number;
number = 0;
i++;
} else {
number = number * 10 + (val - '0');
}
}
}
fc.close();
} catch (Exception e) {
System.err.println("Parsing error: " + e);
}
}
}
如果你在这里真正想做的是找出是否有一种通用的技术可以使计算速度更快,那么我认为你找错了方向。没有这种技术。你在优化一个无意义的计算上所学到的东西不太可能应用于其他(希望是有意义的)计算
如果计算不是毫无意义的,并且目标是使整个程序运行得更快,那么您可能已经达到了优化浪费时间的地步
- 当前(Java)-26.50s+11.27s=~38秒
- 目标(C++)-~26.5s+4.50=~31秒
- 加速百分比-小于20%
对于一个40秒左右的计算来说,不到20%的加速率可能不值得这么做。让用户在这额外的7秒钟里摆弄拇指更便宜
java C++,Java C++ C++程序,它也告诉你一些有趣的事情。在这个场景中,无论你使用C++还是Java,相对的条款都没有多大的差别。程序的整体性能主要由C++和Java相媲美的一个阶段。在不同的计算机上,这很容易意味着不同的性能特征。您将得到一个带有calc参数的arrayoutofbounds异常。您过度分配了列表。此外,只需删除整个calc方法。它不会对结果或原始数据做任何事,也不会将结果存储在som中
-finline-functions
-ftree-vectorize
int c1 = (a1 + a2) ^ a2;
int c2 = (b1 - b2) << 4;
int tmp0 = c1;
int tmp1 = 0;
int tmp2 = 0;
int tmp3 = 0;
int z0 = 0;
int z1 = 1;
int z2 = 2;
int z3 = 3;
do{
tmp0 ^= z0 + c2;
tmp1 ^= z1 + c2;
tmp2 ^= z2 + c2;
tmp3 ^= z3 + c2;
z0 += 4;
z1 += 4;
z2 += 4;
z3 += 4;
}while (z0 < 100);
tmp0 ^= tmp1;
tmp2 ^= tmp3;
tmp0 ^= tmp2;
return tmp0;