【发布时间】:2015-03-14 16:45:06
【问题描述】:
我有一个用 Java 8 编写的相当简单的爱好项目,它在其中一种操作模式中广泛使用重复的 Math.round() 调用。例如,一种这样的模式会产生 4 个线程并通过 ExecutorService 将 48 个可运行任务排入队列,每个任务都运行类似于以下代码块 2^31 次:
int3 = Math.round(float1 + float2);
int3 = Math.round(float1 * float2);
int3 = Math.round(float1 / float2);
实际情况并非如此(涉及数组和嵌套循环),但您明白了。无论如何,在 Java 8u40 之前,类似于上面的代码可以在 AMD A10-7700k 上在大约 13 秒内完成约 1030 亿个指令块的完整运行。使用 Java 8u40 大约需要 260 秒才能完成相同的操作。代码没有变化,什么都没有,只是 Java 更新。
有没有其他人注意到 Math.round() 变得越来越慢,尤其是在重复使用时?就好像 JVM 在它不再做之前做了某种优化。也许它在 8u40 之前使用 SIMD 而现在不是?
编辑:我已经完成了我在 MVCE 的第二次尝试。您可以在此处下载第一次尝试:
https://www.dropbox.com/s/rm2ftcv8y6ye1bi/MathRoundMVCE.zip?dl=0
第二次尝试如下。我的第一次尝试已经从这篇文章中删除,因为它被认为太长了,并且容易被 JVM 删除死代码优化(显然在 8u40 中发生的情况较少)。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MathRoundMVCE
{
static long grandtotal = 0;
static long sumtotal = 0;
static float[] float4 = new float[128];
static float[] float5 = new float[128];
static int[] int6 = new int[128];
static int[] int7 = new int[128];
static int[] int8 = new int[128];
static long[] longarray = new long[480];
final static int mil = 1000000;
public static void main(String[] args)
{
initmainarrays();
OmniCode omni = new OmniCode();
grandtotal = omni.runloops() / mil;
System.out.println("Total sum of operations is " + sumtotal);
System.out.println("Total execution time is " + grandtotal + " milliseconds");
}
public static long siftarray(long[] larray)
{
long topnum = 0;
long tempnum = 0;
for (short i = 0; i < larray.length; i++)
{
tempnum = larray[i];
if (tempnum > 0)
{
topnum += tempnum;
}
}
topnum = topnum / Runtime.getRuntime().availableProcessors();
return topnum;
}
public static void initmainarrays()
{
int k = 0;
do
{
float4[k] = (float)(Math.random() * 12) + 1f;
float5[k] = (float)(Math.random() * 12) + 1f;
int6[k] = 0;
k++;
}
while (k < 128);
}
}
class OmniCode extends Thread
{
volatile long totaltime = 0;
final int standard = 16777216;
final int warmup = 200000;
byte threads = 0;
public long runloops()
{
this.setPriority(MIN_PRIORITY);
threads = (byte)Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(threads);
for (short j = 0; j < 48; j++)
{
executor.execute(new RoundFloatToIntAlternate(warmup, (byte)j));
}
executor.shutdown();
while (!executor.isTerminated())
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
//Do nothing
}
}
executor = Executors.newFixedThreadPool(threads);
for (short j = 0; j < 48; j++)
{
executor.execute(new RoundFloatToIntAlternate(standard, (byte)j));
}
executor.shutdown();
while (!executor.isTerminated())
{
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
//Do nothing
}
}
totaltime = MathRoundMVCE.siftarray(MathRoundMVCE.longarray);
executor = null;
Runtime.getRuntime().gc();
return totaltime;
}
}
class RoundFloatToIntAlternate extends Thread
{
int i = 0;
int j = 0;
int int3 = 0;
int iterations = 0;
byte thread = 0;
public RoundFloatToIntAlternate(int cycles, byte threadnumber)
{
iterations = cycles;
thread = threadnumber;
}
public void run()
{
this.setPriority(9);
MathRoundMVCE.longarray[this.thread] = 0;
mainloop();
blankloop();
}
public void blankloop()
{
j = 0;
long timer = 0;
long totaltimer = 0;
do
{
timer = System.nanoTime();
i = 0;
do
{
i++;
}
while (i < 128);
totaltimer += System.nanoTime() - timer;
j++;
}
while (j < iterations);
MathRoundMVCE.longarray[this.thread] -= totaltimer;
}
public void mainloop()
{
j = 0;
long timer = 0;
long totaltimer = 0;
long localsum = 0;
int[] int6 = new int[128];
int[] int7 = new int[128];
int[] int8 = new int[128];
do
{
timer = System.nanoTime();
i = 0;
do
{
int6[i] = Math.round(MathRoundMVCE.float4[i] + MathRoundMVCE.float5[i]);
int7[i] = Math.round(MathRoundMVCE.float4[i] * MathRoundMVCE.float5[i]);
int8[i] = Math.round(MathRoundMVCE.float4[i] / MathRoundMVCE.float5[i]);
i++;
}
while (i < 128);
totaltimer += System.nanoTime() - timer;
for(short z = 0; z < 128; z++)
{
localsum += int6[z] + int7[z] + int8[z];
}
j++;
}
while (j < iterations);
MathRoundMVCE.longarray[this.thread] += totaltimer;
MathRoundMVCE.sumtotal = localsum;
}
}
长话短说,这段代码在 8u25 和 8u40 中的表现大致相同。如您所见,我现在将所有计算的结果记录到数组中,然后将循环的定时部分之外的这些数组求和到一个局部变量,然后在外部循环结束时将其写入一个静态变量。
8u25以下:总执行时间为261545毫秒
8u40 下:总执行时间为 266890 毫秒
测试条件与之前相同。因此,似乎 8u25 和 8u31 正在执行 8u40 停止执行的死代码删除,导致代码在 8u40 中“减速”。这并不能解释所有突然出现的奇怪小东西,但这似乎是其中的大部分。作为额外的奖励,这里提供的建议和答案给了我灵感来改进我的爱好项目的其他部分,对此我非常感激。谢谢大家!
【问题讨论】:
-
能否提供一个MCVE:stackoverflow.com/help/mcve
-
我已经用 java7 和 java8 运行了这些方法 10K、100K、1M 和 10M 次,并且得到了非常相似的结果。绝对需要那个 MCVE
-
好的,开始重写程序,只关注 Math.round 部分。我会尽快解决的。如果可以的话,我会回滚以更新我的 Windows 分区上的 30。或者至少,我可以试试。 . .无论如何,有问题的代码在这里:dropbox.com/s/53zuk227qr4wdpn/mathtestersource03112015.zip?dl=0。有问题的方法在 RoundFloatToInt、RoundFloatToIntNoDiv、RoundFloatToIntAlternate、RoundFloatToIntNoDivAlternate 和 OmniLoop 类中(OmniLoop 中的方法是 roundfloattointloop/roundfloattointloopalternate)。
-
Math.sqrt() 在 8u40 中也显示出新的行为(阅读:更差的性能),但它并没有那么糟糕。我也会尝试将其包含在 MVCE 中。
-
好的,你要求的,我会在编辑中添加源代码。