Микрооптимизация Java: кэшировать или нет кэшировать возвращаемое значение System.currentTimeMillis ()? - PullRequest
0 голосов
/ 22 мая 2018

Простой вопрос, который меня интересовал.Из следующих двух версий кода, что лучше оптимизировать?Предположим, что значение времени, полученное в результате вызова System.currentTimeMillis (), должно быть точным довольно , поэтому кэширование следует рассматривать только с точки зрения производительности.

Это (со значениемкэширование):

    long time = System.currentTimeMillis();
    for (long timestamp : times) {
        if (time - timestamp > 600000L) {
            // Do something
        }
    }

Или вот это (без кэширования):

    for (long timestamp : times) {
        if (System.currentTimeMillis() - timestamp > 600000L) {
            // Do something
        }
    }

Я предполагаю, что System.currentTimeMillis () уже является очень оптимизированным и легким вызовом метода, но давайте предположим,Я буду вызывать его много, много раз за короткий период.

Сколько значений должен содержать коллекция / массив «times», чтобы оправдать кэширование возвращаемого значения System.currentTimeMillis () в его собственной переменной?

Это лучше сделать с точки зрения оптимизации процессора или памяти?

1 Ответ

0 голосов
/ 22 мая 2018

A long в основном бесплатно.JVM с JIT-компилятором может хранить его в регистре, и, поскольку он является инвариантом цикла, он может даже оптимизировать ваше условие цикла до -timestamp < 600000L - time или timestamp > time - 600000L.то есть условие цикла становится тривиальным сравнением между итератором и константой, инвариантной к циклу в регистре.

Так что, да, очевидно, более эффективно вывести вызов функции из цикла и сохранить результат в переменной,особенно когда оптимизатор не может сделать это за вас, и особенно, когда результатом является примитивный тип, а не Object.


Предполагается, что ваш код работает на JVM, который JITs машинный код x86, System.currentTimeMillis(), вероятно, будет включать по крайней мере инструкцию rdtsc и некоторое масштабирование этого результата 1 .Таким образом, самое дешевое, что может быть , возможно, (на Skylake, например), - это микрокодированная инструкция с 20 моп с пропускной способностью один на 25 тактов (http://agner.org/optimize/).

Если ваш // Do something прост, например, несколько обращений к памяти, которые обычно попадают в кэш, или более простые вычисления, или что-то еще, с чем может сработать неправильное выполнение, что может быть большей частью стоимости вашего цикла.Если каждая итерация цикла обычно занимает несколько микросекунд (т. Е. Время для тысяч инструкций на суперскалярном процессоре 4 ГГц), выведение System.currentTimeMillis() из цикла может, вероятно, иметь ощутимую разницу. Маленькие и огромные будут зависеть от того, насколько просты вашитело цикла имеет вид.

Если вы можете доказать, что удаление его из вашего цикла не вызовет проблем с корректностью, то сделайте это.

Даже если оно находится внутри вашего цикла, вашпоток все еще может бездействовать в течение неограниченного промежутка времени между вызовом его и выполнением работы для этой итерации. Но вывод его из цикла делает его болеевполне вероятно, что вы могли реально наблюдать такой эффект на практике;запускать итерации «слишком поздно».


Сноска 1 : В современном x86 счетчик меток времени работает с фиксированной скоростью, поэтому он полезен в качестве источника времени с низкими издержкамии менее полезен для точного цикла микробенчмаркинга.(Для этого используйте счетчики производительности или отключите турбо / энергосбережение, чтобы тактовая частота ядра = эталонные часы.)

IDK, если JVM фактически столкнется с проблемой реализации собственной функции времени.Он может просто использовать предоставленную ОС функцию времени.В Linux gettimeofday и clock_gettime реализованы в пользовательском пространстве (с данными кода + масштабного коэффициента, экспортируемыми ядром в пользовательскую память, в области VDSO ).Так что оболочка glibc просто вызывает это, вместо фактического syscall.

Так что clock_gettime может быть очень дешевым по сравнению с реальным системным вызовом, который переключается в режим ядра и обратно.Это может занять не менее 1800 тактов на Skylake в ядре с включенным смягчением Spectre + Meltdown.

Так что да, можно надеяться, можно предположить, что System.currentTimeMillis() "очень оптимизирован и легок"", но даже rdtsc само по себе дорого по сравнению с некоторыми телами петель.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...