Comments 23
Ого, реально интересная фича! Спасибо за статью!
Я правильно понимаю из кода
Что этим ассертом, а именно «Thread::current()->is_VM_thread()», разработчики хотели ограничить использование JavaCritical_ не внутри JVM?
Я правильно понимаю из кода
+ void enter_critical() { assert(Thread::current() == this || + Thread::current()->is_VM_thread() && SafepointSynchronize::is_synchronizing(), + "this must be current thread or synchronizing");
Что этим ассертом, а именно «Thread::current()->is_VM_thread()», разработчики хотели ограничить использование JavaCritical_ не внутри JVM?
Если оно только из горячесго кода, то что будет, если только critical-функцию определить? Ошибка на начальных этапах исполнения программы? А достаточно просто два метода с разными именами и параметрами сделать, чтобы работало?
Вы сами как-то использовали эту возможность?
Сам только недавно наткнулся, копаясь в исходниках Хотспота.
Есть планы опробовать на нашем Java сервере, где всё сетевое I/O на native методах.
Есть планы опробовать на нашем Java сервере, где всё сетевое I/O на native методах.
А зачем на нативных методах? В Java вроде приличное сетевое API.
— Не поддерживаются TCP опции: TCP_DEFER_ACCEPT, TCP_CORK и т.п.
— Не поддерживаются флаги send и recv: MSG_MORE, MSG_PEEK…
— Selector глючный и не потокобезопасный.
— Нельзя делеать select() на блокирующих сокетах.
— setSoTimeout не работает на SocketChannel'ах.
Да и по производительности можно выжать больше.
— Не поддерживаются флаги send и recv: MSG_MORE, MSG_PEEK…
— Selector глючный и не потокобезопасный.
— Нельзя делеать select() на блокирующих сокетах.
— setSoTimeout не работает на SocketChannel'ах.
Да и по производительности можно выжать больше.
Меня больше интересует сколько времени эти вызовы занимают по сравнению со временем работы самого алгоритма. Это как и с накладными расходами на создание потоков. Если потоки долгоживущие/алгоритм долго выполняется, то все эти накладные расходы нивелируются.
Это сколько?
и, самое главное, метод должен завершаться за короткое время
Это сколько?
Меня больше интересует сколько времени эти вызовы занимают по сравнению со временем работы самого алгоритма.Так из графиков же видно. Сравните arrayElementsCritical (стандартный JNI) с javaCritical: делают они одно и то же, значит, вся разница — и есть накладные расходы. Собственно, абзац после графиков отвечает на вопрос.
Это сколько?Зависит от приложения. Для одних паузы в 100 мс критичны, другие и 5 секунд могут подождать.
Скажем так: если в 10 мс укладывается, значит, для большинства случаев сгодится.
Так из графиков же видно. Сравните arrayElementsCritical (стандартный JNI) с javaCritical: делают они одно и то же, значит, вся разница — и есть накладные расходы. Собственно, абзац после графиков отвечает на вопрос.
В данном вопросе не могу не процитировать Кнута: «Преждевременная оптимизация — корень всех зол». Если процесс выполняется долго, то нет смысла возможно это использовать. А если быстро, то почему бы это и в Java не делать?
Скажите пожалуйста, а native методы не попадают под JIT оптимизации? Если попадают, то как происходят эти оптимизации и есть ли вообще в них смысл для native методов?
Для native методов JIT-компилятор генерирует обёртки. Т.е. вся описанная процедура (создание фрейма, перекладывание аргументов, проверка safepoint и т.д.) происходит в динамически скомпилированном коде под данный конкретный метод. Но непосредственно с нативной реализацией метода, которая уже и так в бинарном виде, JIT, естественно, ничего сделать не может и просто вызывает, как есть.
Внезапно возник вопрос, а зачем в JVM появился JavaCritical_, если для этого уже есть интринсики. В чем фундаментальное отличие?
Некорректное утверждение. Вы не можете взять и написать интринсик для «этого».
Совсем разные вещи.
Интринсики — часть JVM, их пишут компиляторщики. По сути, это переписывание тела метода на суровом хотспотовском IR.
JavaCritical — оптимизация для библиотек. Чтобы код на C, написанный не JVM разработчиками, эффективней вызывался.
Посмотрите, как выглядит хоть один интринсик, например, LibraryCallKit::string_indexOf, и всё станет сразу ясно.
Интринсики — часть JVM, их пишут компиляторщики. По сути, это переписывание тела метода на суровом хотспотовском IR.
JavaCritical — оптимизация для библиотек. Чтобы код на C, написанный не JVM разработчиками, эффективней вызывался.
Посмотрите, как выглядит хоть один интринсик, например, LibraryCallKit::string_indexOf, и всё станет сразу ясно.
Когда-то давным-давно была потребность измерять время с мкс разрешением — т.к. штатно в java это нельзя сделать — мотивация простая — хочется мерять время между боксами — и готовы терпеть jni вызовы и погрешность, но на боксах ntp работает достаточно точно.
И вот решил таки попробовать напилить то же самое, но с Critical JNI:
к нему com_PreciseTimestamp.h
и com_PreciseTimestamp.c
(поскольку проверка гонялась не на linux, а на macosx)
и конечно же benchmark
и был удивлён, когда увидел результаты
Т.е видно, конечно, что Critical JNI таки быстрее, но не сильно (по сравнению с System.nano)
Буду рад услышать отзыв — может быть я где-то ошибся или не так, что интерпретировал.
И вот решил таки попробовать напилить то же самое, но с Critical JNI:
package com;
public final class PreciseTimestamp {
static {
try {
System.loadLibrary("precisetimestamp");
} catch (Throwable e){
throw new RuntimeException(e.getMessage(), e);
}
}
public static native long getMicros();
public static native long getCMicros();
public static void main(String[] args) {
System.out.println(PreciseTimestamp.getMicros());
System.out.println(PreciseTimestamp.getCMicros());
System.out.println(System.currentTimeMillis());
}
}
к нему com_PreciseTimestamp.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_PreciseTimestamp */
#ifndef _Included_com_PreciseTimestamp
#define _Included_com_PreciseTimestamp
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_PreciseTimestamp
* Method: getMicros
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_PreciseTimestamp_getMicros
(JNIEnv *, jclass);
/*
* Class: com_PreciseTimestamp
* Method: getCMicros
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_com_PreciseTimestamp_getCMicros
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
и com_PreciseTimestamp.c
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include "com_PreciseTimestamp.h"
JNIEXPORT jlong JNICALL Java_com_PreciseTimestamp_getMicros
(JNIEnv * env, jclass jc)
{
//#if defined(_POSIX_TIMERS)
// {
// struct timespec ts;
// if ( 0 == clock_gettime(CLOCK_REALTIME, &ts) )
// {
// return ((((jlong) ts.tv_sec) * 1000000) +
// (((jlong) ts.tv_nsec)) / 1000);
// }
// }
//#endif
// otherwise use gettimeofday
struct timeval tv;
gettimeofday(&tv, NULL);
return (((jlong) tv.tv_sec) * 1000000) + ((jlong) tv.tv_usec);
}
JNIEXPORT jlong JNICALL Java_com_PreciseTimestamp_getCMicros
(JNIEnv * env, jclass jc)
{
//#if defined(_POSIX_TIMERS)
// {
// struct timespec ts;
// if ( 0 == clock_gettime(CLOCK_REALTIME, &ts) )
// {
// return ((((jlong) ts.tv_sec) * 1000000) +
// (((jlong) ts.tv_nsec)) / 1000);
// }
// }
//#endif
// otherwise use gettimeofday
struct timeval tv;
gettimeofday(&tv, NULL);
return (((jlong) tv.tv_sec) * 1000000) + ((jlong) tv.tv_usec);
}
JNIEXPORT jlong JNICALL JavaCritical_com_PreciseTimestamp_getCMicros
()
{
//#if defined(_POSIX_TIMERS)
// {
// struct timespec ts;
// if ( 0 == clock_gettime(CLOCK_REALTIME, &ts) )
// {
// return ((((jlong) ts.tv_sec) * 1000000) +
// (((jlong) ts.tv_nsec)) / 1000);
// }
// }
//#endif
// otherwise use gettimeofday
struct timeval tv;
gettimeofday(&tv, NULL);
return (((jlong) tv.tv_sec) * 1000000) + ((jlong) tv.tv_usec);
}
(поскольку проверка гонялась не на linux, а на macosx)
и конечно же benchmark
package com;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
/**
* @author vladimir.dolzhenko@gmail.com
* @since 2016-11-17
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(1)
@Warmup(iterations = 3, time = 10000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 3, time = 10000, timeUnit = TimeUnit.MILLISECONDS)
@Threads(1)
public class PerfTiming {
@Benchmark
public void testSystemNano(Blackhole bh){
long v = 0;
for(int i = 0; i < 30_000; i++){
v += System.nanoTime();
}
bh.consume(v);
}
@Benchmark
public void testJni(Blackhole bh){
long v = 0;
for(int i = 0; i < 30_000; i++){
v += PreciseTimestamp.getMicros();
}
bh.consume(v);
}
@Benchmark
public void testCriticalJni(Blackhole bh){
long v = 0;
for(int i = 0; i < 30_000; i++){
v += PreciseTimestamp.getCMicros();
}
bh.consume(v);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(PerfTiming.class.getSimpleName())
.warmupIterations(5)
.measurementIterations(5)
.build();
new Runner(opt).run();
}
}
и был удивлён, когда увидел результаты
Benchmark Mode Cnt Score Error Units
PerfTiming.testCriticalJni avgt 5 1636.577 ± 40.370 us/op
PerfTiming.testJni avgt 5 1673.504 ± 86.489 us/op
PerfTiming.testSystemNano avgt 5 1179.510 ± 31.920 us/op
Т.е видно, конечно, что Critical JNI таки быстрее, но не сильно (по сравнению с System.nano)
Буду рад услышать отзыв — может быть я где-то ошибся или не так, что интерпретировал.
А что смущает? Выглядит всё логично. nanoTime() быстрее, потому что это JVM intrinsic и выполняется в контексте Java кода без переключения в натив. Critical JNI ненамного быстрее, потому что в простом методе без аргументов ему экономить особо не на чем, разве что на лишних JNIEnv и jclass.
Кроме того, сравнивать gettimeofday с nanoTime не совсем корректно, т. к. nanoTime реализован по-другому. А вот currentTimeMillis реализован как раз через gettimeofday.
Кстати, не надо в JMH бенчмарках гонять циклы — рискуете попасться на все те грабли, с которыми JMH борется. Там внутри и так есть цикл. К тому же, теряется смысл Score. Если я измеряю одну операцию, то Score даёт интуитивно понятную оценку, типа nanoTime работает около 15 наносекунд, что соответствует примерно 30 тактам 2GHz процессора. А так 1179 — просто какое-то абстрактное число.
Кроме того, сравнивать gettimeofday с nanoTime не совсем корректно, т. к. nanoTime реализован по-другому. А вот currentTimeMillis реализован как раз через gettimeofday.
Кстати, не надо в JMH бенчмарках гонять циклы — рискуете попасться на все те грабли, с которыми JMH борется. Там внутри и так есть цикл. К тому же, теряется смысл Score. Если я измеряю одну операцию, то Score даёт интуитивно понятную оценку, типа nanoTime работает около 15 наносекунд, что соответствует примерно 30 тактам 2GHz процессора. А так 1179 — просто какое-то абстрактное число.
Сравнение с nanoTime делалось чисто чтобы как-то сравнить разницу — условную, чтобы видет разницу между jni / critical jni.
Про циклы в benchmark-е — сперва было всё без циклов и там jni critical так же на чуть-чуть меньше — подумал, может быть он как-то недостаточно разогрет.
Словом, всё более-менее ожидаемо и спасибо за ответ.
Про циклы в benchmark-е — сперва было всё без циклов и там jni critical так же на чуть-чуть меньше — подумал, может быть он как-то недостаточно разогрет.
Словом, всё более-менее ожидаемо и спасибо за ответ.
JavaCritical_ функции, к сожалению, не работают на Windows:
https://bugs.openjdk.java.net/browse/JDK-8167408
Фикс отложен до JDK 10.
Sign up to leave a comment.
Дорог ли native метод? «Секретное» расширение JNI