Пока идёт горячее обсуждение быть или нет быть jigsaw в java 9 и в каком виде ему быть — не стоит забывать про полезняшки, которые несёт с собой девятка — и одна из них — повышение точности Clock.systemUTC() — JDK-8068730.
Что же было раньше ?
До java 8 был System.currentTimeMillis() и System.nanoTime(), и если первый давал wall clock время, но с миллисекундным разрешением, то второй даёт время с разрешением до наносекунд, но область применения ограничена измерением разности времён, причём в рамках одной jvm — и ни о каком использовании такой временной метки между разными машинами и быть не может.
Поэтому часто велосипедят свои precise timestamp дающие wall clock время с большим разрешением, чем у currentTimeMillis (используя jni со всеми вытекающими) — более подробно про разницу между currentTimeMillis и nanoTime, и про велосипед можно почитать в моём старом посте.
Java 8 заложил очень мощный фундамент — Java Time API. С ним можно сказать пока и joda time, и встроить свой велосипед в java.time.Clock, т.к. штатный SystemClock по своей сути работает поверх System.currentTimeMillis() и не может обеспечить разрешение, лучше, чем миллисекунда.
И вот теперь в игру вступает java 9 и ничего не ломая (что касается времени и его измерения) приносит улучшение — можно выбросить свой jni велосипед, по крайней мере на Linux, MacOSX, BSD, Solaris и Windows — см. коммит в openjdk.
С практической точки зрения имеют смысл микросекундные временные метки, а не наносекундные — причина тому, что ntp в рамках intranet способна дать время с точностью до 1 мкс.
Запилим провайдера точного wall clock времени с микросекундным разрешением (исходники в т.ч. и native часть):
public final class PreciseTimestamp { static final Clock clock = Clock.systemUTC(); static { try { System.loadLibrary("precisetimestamp"); } catch (Throwable e){ throw new RuntimeException(e.getMessage(), e); } } // JNI microsecond timestamp provider public static native long getMicros(); // Critical JNI microsecond timestamp provider public static native long getCMicros(); public static long clockMicros(){ final Instant instant = clock.instant(); return instant.getEpochSecond() * 1_000_000L + (instant.getNano() / 1_000); } }
Java 8 даст примерно такие результаты
JNI micros: 1494398000506568 Critical JNI micros: 1494398000506986 ClockUTC micros: 1494398000507000 currentTimeMillis: 1494398000507
Ничего удивительного — внутри старый-добрый System.currentTimeMillis()
И Java 9:
JNI micros: 1494398039238236 Critical JNI micros: 1494398039238439 ClockUTC micros: 1494398039238498 currentTimeMillis: 1494398039239
Дополнено:
Т.е. выглядит так, что штатный SystemClock можно использовать как замену jni велосипеду — считаем, что мы можем доверять подлезжащей реализации, что касается корректности получения и монотонности возрастания wall clock времени, рассмотрим гранулярность (тест на гранулярность и результаты ):
java 9: OS Value Units MacOSX: 1011.522 ns/op Windows: 999916.218 ns/op Linux: 1002.419 ns/op
Т.о. резонность интуитивного предположения об использовании микросекундной точности подтверждается и измерением, за исключением windows, которая обладает известной проблемой с использованием GetSystemTimeAsFileTime — на эту проблему была зарепорчена бага JDK-8180466.
Но как же перформанс ? — крикнут перформансники — и будут правы — на лицо лишнее создание объекта Instant.
Пилим benchmark, который сравнивает конечно же jni-велосипед (и обычный, и critical), метод основанный на clock, и для оценки масштабов бедствия System.currentTimeMillis() и System.nanoTime():
Смотрите доклад про Escape Analysis и скаляризацию, если не понятно, почему вызов clock.instant() оказывается дороже (хоть и не намного), чем вызов более сложного метода clockMicros:
public static long clockMicros(){ final Instant instant = clock.instant(); return instant.getEpochSecond() * 1_000_000L + (instant.getNano() / 1_000); }
Дополнено:
Убедится в том, что работает скаляризация можно добававив -prof:gc:
Benchmark Mode Cnt Score Error Units PerfTiming.clockMicros:·gc.alloc.rate avgt 5 ≈ 10⁻⁵ MB/sec PerfTiming.clockMicros:·gc.alloc.rate.norm avgt 5 ≈ 10⁻⁶ B/op PerfTiming.instant:·gc.alloc.rate avgt 5 327,083 ± 13,098 MB/sec PerfTiming.instant:·gc.alloc.rate.norm avgt 5 24,000 ± 0,001 B/op
Выводы: Использование SystemClock в 9ке вполне может заменить jni велосипед — цена ~10% от вызова, много это или мало это — каждый решает сам — я готов жертвовать этими 10%, чтобы забыть головную боль про сборку jni библиотеки и не забывать её деплоить.
