Skip to content

Instantly share code, notes, and snippets.

@vladimirdolzhenko
Last active May 17, 2017 06:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vladimirdolzhenko/45e81e877c498e9706c59e4ba5f3a1d3 to your computer and use it in GitHub Desktop.
Save vladimirdolzhenko/45e81e877c498e9706c59e4ba5f3a1d3 to your computer and use it in GitHub Desktop.

Precise timestamp

Пока идёт горячее обсуждение быть или нет быть jigsaw в java 9 и в каком виде ему быть - не стоит забывать про полезняшки, которые несёт с собой девятка - и одна из них - повышение точности Clock.systemUTC() - JDK-8068730 - начало обсуждения в core-libs-dev.

Что же было раньше ?

До 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 библиотеки и не забывать её деплоить.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment