У меня есть довольно простой проект хобби, написанный на Java 8, который широко использует повторяющиеся вызовы Math.round() в одном из своих режимов работы. Например, один такой режим порождает 4 потока и ставит в очередь 48 запущенных задач с помощью ExecutorService, каждый из которых выполняет что-то похожее на следующий блок кода 2 ^ 31 раз:
int3 = Math.round(float1 + float2);
int3 = Math.round(float1 * float2);
int3 = Math.round(float1 / float2);
Это не совсем так, как это (есть задействованные массивы и вложенные циклы), но вы получаете эту идею. Во всяком случае, до Java 8u40 код, похожий на приведенный выше, может завершить полный запуск ~ 103 миллиардов блоков команд примерно за 13 секунд на AMD A10-7700k. С Java 8u40 требуется около 260 секунд, чтобы сделать то же самое. Никаких изменений в коде, ничего нет, просто обновление Java.
Кто-нибудь еще заметил, что Math.round() становится намного медленнее, особенно когда он используется повторно? Это почти так, как если бы JVM делала какую-то оптимизацию, прежде чем это больше не делает. Может быть, он использовал SIMD до 8u40, и теперь это не так?
edit: Я выполнил вторую попытку в 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. Как вы можете видеть, теперь я записываю результаты всех вычислений в массивы и затем суммирую эти массивы вне временной части цикла к локальной переменной, которая затем записывается в статическую переменную в конце внешнего цикла.
Under 8u25: Общее время выполнения составляет 261545 миллисекунд
До 8u40: Общее время выполнения составляет 266890 миллисекунд
Условия испытаний были такими же, как и раньше. Таким образом, казалось бы, что 8u25 и 8u31 делали удаление мертвого кода, которое остановилось 8u40, в результате чего код "замедлялся" в 8u40. Это не объясняет каждую странную мелочь, которая возникла, но это, по-видимому, основная масса. В качестве дополнительного бонуса предложения и ответы, представленные здесь, вдохновили меня на улучшение других частей моего проекта по хобби, за что я очень благодарен. Спасибо вам всем за это!