В OS X, почему использование println() приводит к тому, что моя программа работает быстрее, чем без println()

Я столкнулся с действительно странной ошибкой, и я надеюсь, что кто-то здесь может пролить свет, поскольку это выходит из моей области знаний.

Во-первых, соответствующая справочная информация: я запускаю OS X 10.9.4 на MacBook Pro Retina 2013 года с процессором Haswell на 2,4 ГГц. Я использую JDK SE 8u5 для OS X от Oracle, и я запускаю свой код в последней версии IntelliJ IDEA. Эта ошибка также, по-видимому, специфична только для OS X, поскольку я уже опубликовал Reddit об этой ошибке, и другие пользователи с OS X смогли воссоздать ее, в то время как пользователи в Windows и Linux, включая меня, запускали программу, как ожидалось, с помощью Версия println() работает на полсекунды медленнее, чем версия без println().

Теперь для ошибки: в моем коде у меня есть инструкция println(), которая при включении работает от ~ 2,5 секунд. Если я удалю оператор println(), либо удалив его, либо прокомментируя это, программа интуитивно требует более длительной работы в ~ 9 секунд. Это очень странно, так как I/O теоретически замедляет работу программы, а не делает ее быстрее.

Для моего фактического кода это моя реализация Project Euler Проблема 14. Пожалуйста, имейте в виду, что я все еще студент, поэтому это не лучшая реализация:

public class ProjectEuler14
{
    public static void main(String[] args)
    {
        final double TIME_START = System.currentTimeMillis();

        Collatz c = new Collatz();
        int highestNumOfTerms = 0;
        int currentNumOfTerms = 0;
        int highestValue = 0; //Value which produces most number of Collatz terms


        for (double i = 1.; i <= 1000000.; i++)
        {
            currentNumOfTerms = c.startCollatz(i);


            if (currentNumOfTerms > highestNumOfTerms)
            {
                highestNumOfTerms = currentNumOfTerms;
                highestValue = (int)(i);
                System.out.println("New term: " + highestValue); //THIS IS THE OFFENDING LINE OF CODE
            }
        }

        final double TIME_STOP = System.currentTimeMillis();

        System.out.println("Highest term: " + highestValue + " with " + highestNumOfTerms + " number of terms");
        System.out.println("Completed in " + ((TIME_STOP - TIME_START)/1000) + " s");
    }
}


public class Collatz
{
    private static int numOfTerms = 0;
    private boolean isFirstRun = false;

    public int startCollatz(double n)
    {
        isFirstRun = true;
        runCollatz(n);
        return numOfTerms;
    }

    private void runCollatz(double n)
    {
        if (isFirstRun)
        {
            numOfTerms = 0;
            isFirstRun = false;
        }

        if (n == 1)
        {
            //Reached last term, does nothing and causes program to return to startCollatz()
        }

        else if (n % 2 == 0)
        {
            //Divides n by 2 following Collatz rule, running recursion
            numOfTerms = numOfTerms + 1;
            runCollatz(n / 2);
        }

        else if (n % 2 == 1)
        {
            //Multiples n by 3 and adds one, following Collatz rule, running recursion
            numOfTerms = numOfTerms + 1;
            runCollatz((3 * n) + 1);
        }
    }
}

Строка кода, о которой идет речь, была прокомментирована со всеми шапками, так как она не похожа на номера строк SO. Если вы не можете найти его, он находится внутри вложенного оператора if() в цикле my for() в моем основном методе.

Я запустил свой код несколько раз с этой строкой и без нее, и я последовательно получаю указанное выше ~ 2,5 сек с помощью println() и ~ 9sec без println(). Я также перезагрузил свой ноутбук несколько раз, чтобы убедиться, что это не моя текущая работа ОС, и время остается неизменным.

Так как другие пользователи OS X 10.9.4 смогли реплицировать код, я подозреваю, что это связано с ошибкой низкого уровня с самим компилятором, JVM или самой ОС. В любом случае, это не в моих силах. Это не критическая ошибка, но меня определенно интересует, почему это происходит, и будет признателен за любую проницательность.

Ответ 1

Я провел некоторое исследование и еще несколько с @ekabanov и вот результаты.

  • Эффект, который вы видите, происходит только с Java 8, а не с Java 7.
  • Дополнительная строка запускает другую компиляцию/оптимизацию JIT
  • Сборочный код более быстрой версии ~ 3 раза больше, и быстрый взгляд показывает, что он развернул цикл
  • Журнал компиляции JIT показывает, что более медленная версия успешно встроила runCollatz, в то время как более быстрая не указала, что вызываемый слишком большой (вероятно, из-за разворачивания).

Существует отличный инструмент, который помогает вам анализировать такие ситуации, он называется jitwatch. Если это уровень сборки, вам также нужен HotSpot Disassembler.

Я также опубликую мои файлы журналов. Вы можете подавать файлы журнала горячих точек в jitwatch, а извлечение сборки - это то, что вы можете разделить, чтобы выявить различия.