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?
0
Если оно только из горячесго кода, то что будет, если только critical-функцию определить? Ошибка на начальных этапах исполнения программы? А достаточно просто два метода с разными именами и параметрами сделать, чтобы работало?
0
Вы сами как-то использовали эту возможность?
0
Сам только недавно наткнулся, копаясь в исходниках Хотспота.
Есть планы опробовать на нашем Java сервере, где всё сетевое I/O на native методах.
Есть планы опробовать на нашем Java сервере, где всё сетевое I/O на native методах.
+2
А зачем на нативных методах? В Java вроде приличное сетевое API.
+1
— Не поддерживаются 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'ах.
Да и по производительности можно выжать больше.
+9
Меня больше интересует сколько времени эти вызовы занимают по сравнению со временем работы самого алгоритма. Это как и с накладными расходами на создание потоков. Если потоки долгоживущие/алгоритм долго выполняется, то все эти накладные расходы нивелируются.
Это сколько?
и, самое главное, метод должен завершаться за короткое время
Это сколько?
+1
Меня больше интересует сколько времени эти вызовы занимают по сравнению со временем работы самого алгоритма.Так из графиков же видно. Сравните arrayElementsCritical (стандартный JNI) с javaCritical: делают они одно и то же, значит, вся разница — и есть накладные расходы. Собственно, абзац после графиков отвечает на вопрос.
Это сколько?Зависит от приложения. Для одних паузы в 100 мс критичны, другие и 5 секунд могут подождать.
Скажем так: если в 10 мс укладывается, значит, для большинства случаев сгодится.
+1
Так из графиков же видно. Сравните arrayElementsCritical (стандартный JNI) с javaCritical: делают они одно и то же, значит, вся разница — и есть накладные расходы. Собственно, абзац после графиков отвечает на вопрос.
В данном вопросе не могу не процитировать Кнута: «Преждевременная оптимизация — корень всех зол». Если процесс выполняется долго, то нет смысла возможно это использовать. А если быстро, то почему бы это и в Java не делать?
0
Скажите пожалуйста, а native методы не попадают под JIT оптимизации? Если попадают, то как происходят эти оптимизации и есть ли вообще в них смысл для native методов?
+1
Для native методов JIT-компилятор генерирует обёртки. Т.е. вся описанная процедура (создание фрейма, перекладывание аргументов, проверка safepoint и т.д.) происходит в динамически скомпилированном коде под данный конкретный метод. Но непосредственно с нативной реализацией метода, которая уже и так в бинарном виде, JIT, естественно, ничего сделать не может и просто вызывает, как есть.
+3
Внезапно возник вопрос, а зачем в JVM появился JavaCritical_, если для этого уже есть интринсики. В чем фундаментальное отличие?
0
Некорректное утверждение. Вы не можете взять и написать интринсик для «этого».
0
Совсем разные вещи.
Интринсики — часть JVM, их пишут компиляторщики. По сути, это переписывание тела метода на суровом хотспотовском IR.
JavaCritical — оптимизация для библиотек. Чтобы код на C, написанный не JVM разработчиками, эффективней вызывался.
Посмотрите, как выглядит хоть один интринсик, например, LibraryCallKit::string_indexOf, и всё станет сразу ясно.
Интринсики — часть JVM, их пишут компиляторщики. По сути, это переписывание тела метода на суровом хотспотовском IR.
JavaCritical — оптимизация для библиотек. Чтобы код на C, написанный не JVM разработчиками, эффективней вызывался.
Посмотрите, как выглядит хоть один интринсик, например, LibraryCallKit::string_indexOf, и всё станет сразу ясно.
0
Когда-то давным-давно была потребность измерять время с мкс разрешением — т.к. штатно в 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)
Буду рад услышать отзыв — может быть я где-то ошибся или не так, что интерпретировал.
0
А что смущает? Выглядит всё логично. 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 — просто какое-то абстрактное число.
0
Сравнение с nanoTime делалось чисто чтобы как-то сравнить разницу — условную, чтобы видет разницу между jni / critical jni.
Про циклы в benchmark-е — сперва было всё без циклов и там jni critical так же на чуть-чуть меньше — подумал, может быть он как-то недостаточно разогрет.
Словом, всё более-менее ожидаемо и спасибо за ответ.
Про циклы в benchmark-е — сперва было всё без циклов и там jni critical так же на чуть-чуть меньше — подумал, может быть он как-то недостаточно разогрет.
Словом, всё более-менее ожидаемо и спасибо за ответ.
0
JavaCritical_ функции, к сожалению, не работают на Windows:
https://bugs.openjdk.java.net/browse/JDK-8167408
Фикс отложен до JDK 10.
0
Sign up to leave a comment.
Дорог ли native метод? «Секретное» расширение JNI