Как с помощью System.nanoTime () я могу точно отложить итерацию al oop, чтобы достичь частоты, например 1M в секунду? - PullRequest
2 голосов
/ 28 мая 2020

Я создал программу Java для генерации событий с определенной c частотой. Я использую System.nanoTime() вместо Thread.sleep(), потому что первый дает более высокую точность в интервале согласно многим ссылкам здесь и здесь . Однако я предполагаю, что когда я пытаюсь настроить его на передачу данных со скоростью 1 млн записей в секунду, это не достигает цели. Это мой код:

long delayInNanoSeconds = 1000000;

private void generateTaxiRideEvent(SourceContext<TaxiRide> sourceContext) throws Exception {
    gzipStream = new GZIPInputStream(new FileInputStream(dataFilePath));
    reader = new BufferedReader(new InputStreamReader(gzipStream, StandardCharsets.UTF_8));
    String line;
    TaxiRide taxiRide;
    while (reader.ready() && (line = reader.readLine()) != null) {
        taxiRide = TaxiRide.fromString(line);
        sourceContext.collectWithTimestamp(taxiRide, getEventTime(taxiRide));
        // sleep in nanoseconds to have a reproducible data rate for the data source
        this.dataRateListener.busySleep();
    }
}

public void busySleep() {
    final long startTime = System.nanoTime();
    while ((System.nanoTime() - startTime) < this.delayInNanoSeconds) ;
}

Итак, когда я жду 10000 наносекунд в переменной delayInNanoSeconds, я получу рабочую нагрузку 100K rec / se c (1_000_000_000 / 10_000 = 100K r / s) . Когда я жду 2000 наносекунд в переменной delayInNanoSeconds, я получаю рабочую нагрузку 500K rec / se c (1_000_000_000 / 2_000 = 500K r / s). За 1000 наносекунд я получу рабочую нагрузку 1M rec / se c (1_000_000_000 / 1000 = 1M r / s). И для 500 наносекунд рабочая нагрузка 2M rec / se c (1_000_000_000 / 500 = 2M r / s).

Я видел здесь , что может быть лучше использовать double вместо long для повышения точности. Это как-то связано? Или проблема просто в ограничении ОС (я использую Linux Ubuntu 18)? Или, может быть, это я, потому что я использую метод readLine() и есть более быстрый способ генерировать эти события? Я думаю, что когда я использую класс GZIPInputStream, я загружаю весь файл в память, и readLine() больше не обращается к диску. Как я могу увеличить скорость передачи данных моего приложения?

1 Ответ

2 голосов
/ 28 мая 2020

@ TobiasGeiselmann хорошо замечает: ваш расчет задержки не учитывает время, потраченное между звонками на busySleep

Вы должен рассчитывать крайний срок относительно последнего крайнего срока , а не текущего времени после регистрации. Также не используйте результат из предыдущего System.nanoTime(); это будет некоторое время> = фактический крайний срок (поскольку сам nanoTime требует времени, по крайней мере, несколько наносекунд, поэтому он неизбежно засыпает). Так вы накапливаете ошибку.

Перед первой итерацией найдите текущее время и установите long deadline = System.nanoTime();. В конце каждой итерации выполняйте deadline += 1000; и используйте свое занятое ожидание l oop, чтобы вращать до сих пор> = крайний срок.


Если deadline - now достаточно велико, используйте что-то, что передает ЦП другим потокам, пока не истечет крайний срок пробуждения . Согласно комментариям, LockSupport.parkNanos(…) - хороший выбор для современного Java, и на самом деле он может ждать достаточно короткого сна (?). Я действительно не знаю Java. Если это так, вы должны просто проверить текущее время, рассчитать время до крайнего срока и вызвать его один раз. 1027 *tpause для простоя ядра ЦП до заданного крайнего срока TS C. Не через ОС, а просто пауза крайнего срока, удобная для гиперпоточности, хороша для короткого сна на ЦП SMT.)

Ожидание при занятости обычно плохо, но подходит для высокоточных очень коротких задержек. 1 микросекунды недостаточно, чтобы позволить контексту ОС переключиться на что-то другое и обратно на текущем оборудовании с текущими ОС. Но более длительные интервалы сна (когда вы выбрали более низкую частоту) должны спать, чтобы позволить ОС делать что-то полезное на этом ядре, вместо того, чтобы просто так долго ждать.

В идеале, когда вы крутите время -check, вы выполняете инструкцию типа x86 pause с задержкой l oop, чтобы быть более дружелюбным к другому логическому ядру, использующему то же физическое ядро ​​(гиперпоточность / SMT). Java 9 Thread.onSpinWait(); следует вызывать в циклах спин-ожидания (особенно при ожидании в памяти), что позволяет JVM раскрывать эту концепцию переносимым способом. (Я предполагаю, что это то, для чего это нужно.)


Это будет работать, если ваша система достаточно быстра, чтобы не отставать от выполнения этой функции получения времени один раз за итерацию. Если нет, то вы могли бы проверять крайний срок каждые 4 итерации (l oop разворачивание), чтобы амортизировать стоимость nanoTime(), чтобы вы регистрировались пакетами из 4 или около того.

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

...