Накладные расходы на измерение времени в Java

При измерении прошедшего времени на низком уровне я могу использовать любой из них:

System.currentTimeMillis();
System.nanoTime();

Оба метода реализованы native. Прежде чем копаться в каком-либо коде C, кто-нибудь знает, есть ли какие-либо существенные накладные расходы при вызове того или другого? Я имею в виду, если меня не волнует дополнительная точность, какой из них будет требовать меньше времени процессора?

NB: я использую стандартный Java 1.6 JDK, но вопрос может быть актуален для любой JRE...


person Lukas Eder    schedule 12.04.2011    source источник
comment
Имейте в виду, что nanoTime не отображает реальное время и должно использоваться только для измерения того, сколько времени что-то заняло.   -  person Powerlord    schedule 12.04.2011
comment
@R.Bemrose, спасибо, я знаю. Это действительно для измерения того, сколько времени что-то занимает.   -  person Lukas Eder    schedule 12.04.2011


Ответы (8)


Ответ, помеченный как правильный на этой странице, на самом деле неверен. Это недопустимый способ написания эталонного теста из-за устранения мертвого кода JVM (DCE), замены в стеке (OSR), развертывания циклов и т. д. Только такая среда, как среда микротестирования Oracle JMH, может правильно измерить что-то подобное. Прочитайте это сообщение, если у вас есть какие-либо сомнения относительно достоверности таких микротестов.

Вот тест JMH для System.currentTimeMillis() против System.nanoTime():

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class NanoBench {
   @Benchmark
   public long currentTimeMillis() {
      return System.currentTimeMillis();
   }

   @Benchmark
   public long nanoTime() {
    return System.nanoTime();
   }
}

И вот результаты (на Intel Core i5):

Benchmark                            Mode  Samples      Mean   Mean err    Units
c.z.h.b.NanoBench.currentTimeMillis  avgt       16   122.976      1.748    ns/op
c.z.h.b.NanoBench.nanoTime           avgt       16   117.948      3.075    ns/op

Что показывает, что System.nanoTime() немного быстрее ~ 118 нс на вызов по сравнению с ~ 123 нс. Однако также ясно, что после учета средней ошибки разница между ними очень мала. Результаты также могут различаться в зависимости от операционной системы. Но общий вывод должен заключаться в том, что они по существу эквивалентны с точки зрения накладных расходов.

ОБНОВЛЕНИЕ 2015/08/25: хотя этот ответ ближе к правильному, чем большинство, с использованием JMH для измерения, он все же неверен. Измерение чего-то вроде System.nanoTime() — это особый вид искаженного бенчмаркинга. Ответ и исчерпывающая статья находятся здесь.

person brettw    schedule 10.03.2014
comment
Замечательное использование JMH! Я бы хотел, чтобы больше микробенчмарков на этом сайте воспользовались этим. - person Joe C; 20.06.2015
comment
Я хотел попробовать ваш пример и задался вопросом, почему аннотация @GenerateMicroBenchmark не работает. Кажется, он был переименован в @Benchmark более или менее недавно. - person dajood; 07.07.2016

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

for (int j = 0; j < 5; j++) {
    long time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        long x = System.currentTimeMillis();
    }
    System.out.println((System.nanoTime() - time) + "ns per million");

    time = System.nanoTime();
    for (int i = 0; i < 1000000; i++) {
        long x = System.nanoTime();
    }
    System.out.println((System.nanoTime() - time) + "ns per million");

    System.out.println();
}

И последний результат:

14297079ns per million
29206842ns per million

Похоже, что System.currentTimeMillis() в два раза быстрее, чем System.nanoTime(). Однако 29 нс будет намного короче, чем все остальное, что вы все равно измеряете. Я бы выбрал System.nanoTime() из-за точности и правильности, так как это не связано с часами.

person WhiteFang34    schedule 12.04.2011
comment
Хороший эталон. Хотя на моем компе все наоборот. Я получаю этот вывод: 17258920 нс на миллион, 14974586 нс на миллион. Это означает, что это действительно зависит от JVM, процессоров, операционных систем и т. д. В остальном разница почти не имеет значения. Спасибо за хороший ответ! - person Lukas Eder; 12.04.2011
comment
Без проблем. Да, я уверен, что есть все виды факторов, которые заставят его измениться. В любом случае, вам придется вызывать его миллионы раз в секунду на современной машине, чтобы он вызывал заметные накладные расходы времени. - person WhiteFang34; 12.04.2011
comment
Базовая ОС была бы интересна. Я подозреваю, что если реализация использует POSIX clock_gettime(), разница будет ~ 0. - person ninjalj; 12.04.2011
comment
Истинный. Но ваш внешний цикл, похоже, показывает, что результаты в одной и той же системе несколько согласуются. За исключением первой итерации, которая может иметь некоторые накладные расходы JVM, все итерации дают примерно одинаковые значения... Так что безопасно выбрать nanoTime() и получить небольшую дополнительную точность. - person Lukas Eder; 12.04.2011
comment
@ninjalj: Я использую Windows 7. Бог знает, сколько часов MSDOS 6.23, Windows 3.11 для рабочих групп и Windows ME все еще работают параллельно :) - person Lukas Eder; 12.04.2011
comment
На самом деле это напоминает мне. Точность System.currentTimeMillis() зависит от ОС. В Windows 7 и других современных системах точность составляет 1 мс. Однако я очень хорошо помню только разрешение 10 мс и 16 мс. И System.nanoTime() в то время не было доступно в Java. IIRC в действительно старых версиях Windows составлял 50 мс, что было серьезной проблемой. - person WhiteFang34; 12.04.2011
comment
@LukasEder На ваш первоначальный комментарий это потому, что микробенчмаркинг в Java - нетривиальное дело. Значительно различающиеся результаты обычно подразумевают плохо спроектированный микротест. См. javacodegeeks.com/2011/09/< /а> - person b1nary.atr0phy; 18.05.2013
comment
@ b1narytr0phy прав, микро-бенчмаркинг почти всегда выполняется неправильно. Смотрите мой ответ внизу этой страницы. - person brettw; 10.03.2014
comment
Переменная X не используется, поэтому системный вызов для получения времени можно оптимизировать. В более позднем состоянии можно увидеть, что внутри циклов не выполняется никакой работы, и их тоже можно оптимизировать. Оставив очень мало для измерения. Опасность микробенчмарков и JIT. - person UnixShadow; 20.10.2015
comment
К сожалению, этот ответ определенно неверен из-за оптимизации, которую может выполнить JIT. Нужно использовать что-то вроде JMH, чтобы правильно сравнить что-то подобное. Самое главное здесь то, что возвращаемое значение отбрасывается, поэтому весь вызов System#currentTimeMillis, скорее всего, будет удален полностью. - person pjulien; 20.11.2016

Вы должны использовать только System.nanoTime() для измерения того, сколько времени требуется для выполнения чего-либо. Дело не только в точности в наносекундах, System.currentTimeMillis() — это «время настенных часов», в то время как System.nanoTime() предназначено для хронометража и не имеет причуд «реального мирового времени», которые есть у других. Из Javadoc System.nanoTime():

Этот метод можно использовать только для измерения прошедшего времени и он не связан ни с каким другим понятием системного или настенного времени.

person ColinD    schedule 12.04.2011
comment
Да, я знаю, что Javadoc упоминает об этом. Но оба они могут использоваться одинаково, если вы много раз измеряете значения в диапазоне от 1 до 10 мс. Что вы подразумеваете под причудами реального времени? - person Lukas Eder; 12.04.2011
comment
@Lukas: Проблема в том, что System.currentTimeMillis() может (я полагаю) делать такие вещи, как вставлять небольшие корректировки часов, которые сбивают тайминги. Вероятно, редко, но nanoTime() предназначен для измерений, на которые подобные вещи не должны влиять. - person ColinD; 12.04.2011
comment
@Lukas: На самом деле, это просто отражает системное время, которое показывает ваш компьютер. Измените дату на своем компьютере на неделю назад во время отсчета времени, используя currentTimeMillis(), и вы получите хорошее отрицательное число! =) Не так для nanoTime(). - person ColinD; 12.04.2011
comment
Этого не произойдет, но хорошая мысль :-) - person Lukas Eder; 12.04.2011
comment
Вам нужно беспокоиться не только об изменении времени вручную. Существует также летнее время и часы, которые автоматически настраиваются после проверки на сервере в Интернете. - person Jonathan; 23.02.2015

Если у вас есть время, посмотрите выступление Клиффа Клик., он говорит о цене System.currentTimeMillis, а также о других вещах.

person mindas    schedule 12.04.2011

System.currentTimeMillis() обычно очень быстрый (насколько известно, 5-6 циклов процессора, но я не знаю, где я это читал), но его разрешение зависит от разных платформ.

Поэтому, если вам нужна высокая точность, выберите nanoTime(), если вас беспокоят накладные расходы, выберите currentTimeMillis().

person Nikolaus Gradwohl    schedule 12.04.2011
comment
Хорошая точка зрения. Посмотрите ответ WhiteFang34. Они кажутся примерно одинаково быстрыми, в зависимости от системы, используемой для их тестирования. - person Lukas Eder; 12.04.2011

Принятый ответ на этот вопрос действительно неверен. Альтернативный ответ, предоставленный @brettw, хорош, но, тем не менее, не содержит деталей.

Для полного рассмотрения этого вопроса и реальной стоимости этих вызовов см. https://shipilev.net/blog/2014/nanotrusting-nanotime/

Чтобы ответить на заданный вопрос:

кто-нибудь знает, есть ли какие-либо существенные накладные расходы, вызывающие тот или иной?

  • Накладные расходы на вызов System#nanoTime составляют от 15 до 30 наносекунд на вызов.
  • Значение, сообщаемое nanoTime, его разрешение, изменяется только один раз в 30 наносекунд.

Это означает, что в зависимости от того, пытаетесь ли вы выполнять миллион запросов в секунду, вызов nanoTime означает, что вы фактически теряете огромную часть второго вызова nanoTime. В таких случаях рассмотрите возможность измерения запросов со стороны клиента, чтобы не попасть в согласованное упущение, измерение глубины очереди также является хорошим индикатором.

Если вы не пытаетесь втиснуть как можно больше работы в одну секунду, то nanoTime не будет иметь большого значения, но скоординированное упущение все еще имеет значение.

Наконец, для полноты картины currentTimeMillis нельзя использовать независимо от ее стоимости. Это связано с тем, что не гарантируется переход вперед между двумя вызовами. Особенно на сервере с NTP currentTimeMillis постоянно перемещается. Не говоря уже о том, что большинство вещей, измеряемых компьютером, не занимают целую миллисекунду.

person pjulien    schedule 20.11.2016
comment
Спасибо за ответ. Между тем, я также наткнулся на статью Алексея Шипилева. Не могли бы вы изложить суть этой статьи в качестве ответа на мой конкретный вопрос о переполнении стека в духе переполнения стека (который должен иметь полный ответ, возможно, подкрепленный дополнительными подробностями в ссылках непосредственно на переполнение стека)? Тогда я приму твой ответ. - person Lukas Eder; 20.11.2016
comment
Я делаю это не для того, чтобы получить принятый ответ. В конечном счете, проблема в том, что принятый ответ направляет людей в неправильном направлении, что, к сожалению. - person pjulien; 20.11.2016
comment
Обновил его, так как вы спросили, но в то же время я всегда чувствую себя коротким StackOverflow, как будто такие ответы просто напрашиваются на неприятности, поскольку они всегда оставляют слишком много места для интерпретации. - person pjulien; 20.11.2016

На теоретическом уровне для виртуальной машины, использующей собственные потоки и работающей в современной операционной системе с вытеснением, currentTimeMillis можно реализовать так, чтобы она читалась только один раз за квант времени. Предположительно, реализации nanoTime не пожертвуют точностью.

person Dilum Ranatunga    schedule 12.04.2011
comment
Это хорошее замечание о точности. Если вы правы, то currentTimeMillis() не может быть точнее. Или, поскольку это не должно быть точным, это может быть реализовано таким образом. Но с другой стороны, это не говорит о том, кто из них быстрее... - person Lukas Eder; 12.04.2011

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

person TennisBowling    schedule 22.01.2020