Здравствуйте, Sharov, Вы писали: S>А что мешает с появлением нового типа посмотреть от кого он наследуется, и обновить соотв. структуры?
Эмм, какие именно структуры? Речь идёт о JIT-оптимизациях.
Теоретически, конечно, возможно создавать таблицы вида "при компиляции метода X мы пользовались предположениями о наследниках типа Y". И, соответственно, при загрузке нового наследника Y проходить по всем методам, которые мы найдём в этой таблице, и ревертить их обратно к байт-коду.
На практике эта таблица может оказываться огромного размера, а потребуется, возможно, никогда.
Проще оборудовать спекулятивные оптимизации guard-ами — они отлавливают не только случай "мы думали, тут больше никого не будет", но и все остальные случаи изменения статистики вызовов.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Sharov, Вы писали: S>>А что мешает с появлением нового типа посмотреть от кого он наследуется, и обновить соотв. структуры? S>Эмм, какие именно структуры? Речь идёт о JIT-оптимизациях.
Ну какие данные rt держит для соотв. типов и т.п. вещи? jit я тоже отношу к rt.
S>Теоретически, конечно, возможно создавать таблицы вида "при компиляции метода X мы пользовались предположениями о наследниках типа Y". И, соответственно, при загрузке нового наследника Y проходить по всем методам, которые мы найдём в этой таблице, и ревертить их обратно к байт-коду.
Ну да, типа того.
S>На практике эта таблица может оказываться огромного размера, а потребуется, возможно, никогда. S>Проще оборудовать спекулятивные оптимизации guard-ами — они отлавливают не только случай "мы думали, тут больше никого не будет", но и все остальные случаи изменения статистики вызовов.
Здравствуйте, Sinclair, Вы писали: S>·>Это и любопытно... А где можно кишки этого дела найти? Я что-то не смог нагуглить сходу. S>Вы уже прочитали статью на Хабре, на которую я ссылался? https://habr.com/ru/post/590069/
Ой, пропустил. Прочитал, интересно. Т.е. они захватывают не только значения, но и имена параметров. Т.е. можно не писать ("a={}", a), а просто ($"{a}). С другой стороны, смотрю я на реальный код и обычно вижу ("Fooing bar moo boo id={}, state={}", someStuff.getRequestId(), someStuff.getState())... S>>>Судя по результатм бенчмарка, самый быстрый способ — всё же оборачивать логгер в if. S>·>Посмотри внимательнее "conditional check is useless here": тайминги getTimeWithCheck=1.030 vs getTimeWithoutCheck=1.140 — в пределах погрешности измерения. S>Ну, 10% это всё же 10%.
Ты на столбик error ещё обращай внимание, мерять наносекундные величины точно не получается. А то внезапно получится, что условие и ускорять может: getTimeWithCheckBenchLocal=1.608 getTimeWithoutCheckLocal=1.613. Вообще там какой-то странный тест. Я написал свой, java 11. И у меня внезапно с проверкой получается всё примерно одинаково (Код ниже):
S>·>Вот мне и неясно чем $"Some message {data} more" поможет, кроме как если компилятор делает какую-то специальную магию, так что проверка уровня происходит до интерполяции. Неясно как фича интерполяции взаимодействует с фичей проверки уровня логгера. S>Всё же прочтите статью. После этого можно будет попробовать её разбрать на sharplab.io и посмотреть, что за код генерируется в итоге. S>Разница между C# и интерполяцией из log2j2 не в поведении при отключении логгирования, а в безопасности.
Это криворукость конкретно log4j. В том же logback (альтернативная реализация того же slf4j api) такого идиотизма нет.
LoggerPerf
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class LoggerPerf
{
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerPerf.class);
private final Logger logger = LoggerFactory.getLogger(LoggerPerf.class);
private volatile UUID p1 = UUID.randomUUID();
@Benchmark
public long baseline() {
final UUID p1 = this.p1;
return p1.getLeastSignificantBits();
}
@Benchmark
public long globalWithCheck() {
final UUID p1 = this.p1;
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("This log is {} check", p1);
}
return p1.getLeastSignificantBits();
}
@Benchmark
public long globalWithoutCheck() {
final UUID p1 = this.p1;
LOGGER.trace("This log is {} check", p1);
return p1.getLeastSignificantBits();
}
@Benchmark
public long localWithCheck() {
final UUID p1 = this.p1;
if (logger.isTraceEnabled()) {
LOGGER.trace("This log is {} check", p1);
}
return p1.getLeastSignificantBits();
}
@Benchmark
public long localWithoutCheck() {
final UUID p1 = this.p1;
LOGGER.trace("This log is {} check", p1);
return p1.getLeastSignificantBits();
}
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(LoggerPerf.class.getSimpleName())
.forks(1)
.warmupForks(1)
.warmupIterations(4)
.warmupTime(TimeValue.seconds(2))
.measurementIterations(4)
.measurementTime(TimeValue.seconds(2))
// .addProfiler(GCProfiler.class)
// .addProfiler(PausesProfiler.class)
.build();
new Runner(opt).run();
}
}
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Sinclair, Вы писали:
S>·>C наследованием jit в java и так хорошо справляется — он знает кол-во наследников, и если один — virtual call elimination. S>Тут такая штука: гарантировать количество наследников может только final. В остальных случаях мы можем только констатировать, что пока другие наследники нам неизвестны.
JVM ещё с середины 90-х годов отслеживает наследников динамически, и при появлении новых разоптимизирует прямой вызов метода и заменяет на динамический. Это практически привело к тому, что final в Java ничем не помогает для ускорения.
Фактически, предвестник трассирующих JIT для JavaScript.
S>Вызов по интерфейсу — это всегда лотерея. Я в курсе про спекулятивные оптимизации в JVM, и именно про них я и писал — будет вставлен guard condition, который не нужен в дотнете.
Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый.
S>>Вызов по интерфейсу — это всегда лотерея. Я в курсе про спекулятивные оптимизации в JVM, и именно про них я и писал — будет вставлен guard condition, который не нужен в дотнете. C>Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый.
Не очень понял, что ты имеешь в виду.
Как я понял, там ставится условный переход по типу и оптимизация опирается на branch predictor в cpu. Притом диспатчинг может даже по нескольким типам происходить. Ну и при появлении нового типа срабатывает uncommon trap, который запустит деоптимизацию.
2.24% 1.77% mov 0x8(%r8),%r10d ; load coder.<classword>
8.38% 9.45% cmp $0xf801040a,%r10d ; if coder is Coder0
je CODER_0 ; jump over
5.65% 9.22% cmp $0xf8012585,%r10d ; if coder is Coder1
jne SLOW_PATH ; not? jump to slow path
...
SLOW_PATH:
...
callq 0x00007f3dc10051a0 ; <uncommon trap>
Здравствуйте, Cyberax, Вы писали:
C>Это практически привело к тому, что final в Java ничем не помогает для ускорения.
А почему, если final\sealed дает гарантию отсутствия наследников?
C>Фактически, предвестник трассирующих JIT для JavaScript.
S>>Вызов по интерфейсу — это всегда лотерея. Я в курсе про спекулятивные оптимизации в JVM, и именно про них я и писал — будет вставлен guard condition, который не нужен в дотнете. C>Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый.
А как jvm в отношении виртуальности и динамичности может быть на 100% уверена?
Здравствуйте, Sinclair, Вы писали:
S>Всё же прочтите статью. После этого можно будет попробовать её разбрать на sharplab.io и посмотреть, что за код генерируется в итоге. S>Разница между C# и интерполяцией из log2j2 не в поведении при отключении логгирования, а в безопасности.
На сколько я понимаю, причина тут совсем не в интерполяции, а в lookup и jndi. Т.е. множество случаев срабатывает,
например, log.debug("${jndi://что-тотут}"), хотя у нас стоит уровень, например info, т.е. debug отключен, но
выражение "${jndi://что-тотут}" (или как там правильно, не суть) будет вычислено, т.е. интерполировано. И это,
безусловно нехорошо, но проблема в этом? Это как преподаватель в вузе хорошую метафору решения проблем безопасности
мс рассказал -- вот, дескать, есть дырка в заборе, мс берет и передвигает забор, формально на старом месте дырки нет,
а фактически... Т.е., кмк, решение проблем интерполяции это именно перенос дырки -- ну будет реже эксплуатироваться, но
дырка-то от этого никуда не денется
Здравствуйте, Cyberax, Вы писали:
S>>Вызов по интерфейсу — это всегда лотерея. Я в курсе про спекулятивные оптимизации в JVM, и именно про них я и писал — будет вставлен guard condition, который не нужен в дотнете. C>Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый.
Хм. Это интересно. В какой момент будет компилироваться новый вариант кода?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, ·, Вы писали: ·>Ой, пропустил. Прочитал, интересно. Т.е. они захватывают не только значения, но и имена параметров. Т.е. можно не писать ("a={}", a), а просто ($"{a}).
Это причём не коробочная реализация — там совместно работает несколько фич компилятора. ·>С другой стороны, смотрю я на реальный код и обычно вижу ("Fooing bar moo boo id={}, state={}", someStuff.getRequestId(), someStuff.getState())...
Ну, при должной обработке в логе так и будет someStuff.RequestId, someStuff.State. ·>Ты на столбик error ещё обращай внимание, мерять наносекундные величины точно не получается. А то внезапно получится, что условие и ускорять может: getTimeWithCheckBenchLocal=1.608 getTimeWithoutCheckLocal=1.613. Вообще там какой-то странный тест. Я написал свой, java 11. И у меня внезапно с проверкой получается всё примерно одинаково (Код ниже): ·>
Ок, это означает, что код logger всё же инлайнится, и повторная проверка в нём устранена.
S>>Всё же прочтите статью. После этого можно будет попробовать её разбрать на sharplab.io и посмотреть, что за код генерируется в итоге. S>>Разница между C# и интерполяцией из log2j2 не в поведении при отключении логгирования, а в безопасности. ·>Это криворукость конкретно log4j. В том же logback (альтернативная реализация того же slf4j api) такого идиотизма нет.
Эмм, а в чём конкретно криворукость? В самой идее интерполяции, или в её реализации?
Если второе, то как logback решает проблему безопасности?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sharov, Вы писали:
S>На сколько я понимаю, причина тут совсем не в интерполяции, а в lookup и jndi. Т.е. множество случаев срабатывает, S>например, log.debug("${jndi://что-тотут}"), хотя у нас стоит уровень, например info, т.е. debug отключен, но S>выражение "${jndi://что-тотут}" (или как там правильно, не суть) будет вычислено, т.е. интерполировано.
Эмм. Если мы про java — то нет, интерполяция откладывается до вызова метода; и там, внутри, она стоит под if. Если debug отключён, то никакой интерполяции не будет.
Если мы про C# — то наличие либо отсутствие интерполяции зависит от того типа, которым обрабатывается интерполированный аргумент. S> И это, безусловно нехорошо, но проблема в этом? Это как преподаватель в вузе хорошую метафору решения проблем безопасности S>мс рассказал -- вот, дескать, есть дырка в заборе, мс берет и передвигает забор, формально на старом месте дырки нет, S>а фактически... Т.е., кмк, решение проблем интерполяции это именно перенос дырки -- ну будет реже эксплуатироваться, но S>дырка-то от этого никуда не денется
Конечно же денется. В C# невозможно подпихнуть неожиданную {} — строку так, чтобы заставить его интерполироать выражение, не предусмотренное разработчиком приложения.
То есть я безо всяких приседаний мог бы сделать
logger.debug($"Here goes my legitimate jndi call result: {jndi.Resolve("ldap:http://good.addresss"}");
и у меня бы вызов jndi прошёл.
А вот такой вот код интерполяции jndi не вызовет:
var externalParameterValue = "{jndi.Resolve(\"ldap:http://good.addresss\"}";
logger.debug($"Here goes my legitimate jndi call result:"+externalParameterValue);
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sharov, Вы писали:
C>>Это практически привело к тому, что final в Java ничем не помогает для ускорения. S>А почему, если final\sealed дает гарантию отсутствия наследников?
Просто не нужно.
C>>Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый. S>А как jvm в отношении виртуальности и динамичности может быть на 100% уверена?
JVM же знает какие классы были загружены. И если есть только одна загруженная реализация IInterface, то есть полная гарантия, что виртуальных вызовов не будет.
Здравствуйте, Sinclair, Вы писали:
C>>Никакого guard condition'а не будет. JVM знает все места использования девиртуализованного интерфейса, и просто скомпилирует новый вариант кода, которым заменит старый. S>Хм. Это интересно. В какой момент будет компилироваться новый вариант кода?
Когда я это смотрел лет 10 назад, это было при инициализации второго класса, реализующего интерфейс.
От постоянной деоптимизации при запуске программы помогает то, что JVM сначала некоторое время работает в режиме интерпретации. Так что к моменту начала JIT-а уже есть некоторая уверенность, что большинство нужных классов уже загружено.
Здравствуйте, Sinclair, Вы писали:
S>Конечно же денется. В C# невозможно подпихнуть неожиданную {} — строку так, чтобы заставить его интерполироать выражение, не предусмотренное разработчиком приложения. S>То есть я безо всяких приседаний мог бы сделать S>
S>logger.debug($"Here goes my legitimate jndi call result: {jndi.Resolve("ldap:http://good.addresss"}");
S>
S>и у меня бы вызов jndi прошёл.
А почему нельзя:
logger.debug($"Here goes my legitimate jndi call result: {externalParameterValue}");
(Подумав)С др. стороны, это все равно строка, т.е. externalParameterValue . Т.е. он просто напечатает
"jndi.Resolve("ldap:http://evil.addresss"}"
Но тогда в шарпе это надо было бы вызывать как-то явно и уже логгировать результат. Опять же, это была бы
изначально кривая функциональность. Тогда причем здесь интерполяция?
S>А вот такой вот код интерполяции jndi не вызовет: S>
S>logger.debug($"Here goes my legitimate jndi call result: {externalParameterValue}");
S>
S>где externalParameterValue = "jndi.Resolve(\"ldap:http://evil.addresss\"}" ?
Можно. Но это просто подставит значение параметра, а не значение выражения в параметре. S>(Подумав)С др. стороны, это все равно строка, т.е. externalParameterValue . Т.е. он просто напечатает S>"jndi.Resolve("ldap:http://evil.addresss"}"
Это напечатает
Here goes my legitimate jndi call result: jndi.Resolve("ldap:http://evil.address")
S>Но тогда в шарпе это надо было бы вызывать как-то явно и уже логгировать результат. Опять же, это была бы S>изначально кривая функциональность. Тогда причем здесь интерполяция?
При том, что интерполяция работает. Причём ожидаемым образом — в частности, выражение externalParameterValue не вычисляется, если отладочное логгирование отключено в конфигурации.
S>Тут по сути 2 строки складываются, и почему при этом должна быть одна из них интерполирована
Ну вот в log4j2 интерполируется результат сложения
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>>Но тогда в шарпе это надо было бы вызывать как-то явно и уже логгировать результат. Опять же, это была бы S>>изначально кривая функциональность. Тогда причем здесь интерполяция? S>При том, что интерполяция работает. Причём ожидаемым образом — в частности, выражение externalParameterValue не вычисляется, если отладочное логгирование отключено в конфигурации.
Т.е. в яве она пытается зачем-то вычислить $"{some_expr}" при том, что some expr это просто строка? Если да, тогда
все ясно и это очень странно. В шарпе это тоже возможно, но явно $"{fn()}".
Вопрос -- а как они в яве тогда отличают обычные строки от необычных, анализируют что-то?
Как они понимают, что тут надо просто подставить значение, а тут надо jndi чего-то там?
S>>Тут по сути 2 строки складываются, и почему при этом должна быть одна из них интерполирована S>Ну вот в log4j2 интерполируется результат сложения
Это как? Интерполяция -- это подстановка вычисления в строку. Оно может быть уже вычислено,
либо $"{fn()}".
Здравствуйте, SkyDance, Вы писали:
vsb>>Вообще в жава с логгингом какое-то мракобесие.
SD>Мракобесие не в жаве с логгингом, а в самой идее решения проблемы рефакторинга путем добавления слоев. SD>Потому как только самые продвинутые человеческие мозги могут решить проблему путем упрощения. Большинство будет надстраивать.
А почему это проблема, если упрощение можно достичь путем именно добавления слоев. Я вот ссылку выше привел,
где британские ученые именно этот подход и предлагают. Т.е. негоже такой функционал(jndi) в лог тащить.
Здравствуйте, Sinclair, Вы писали:
S>·>С другой стороны, смотрю я на реальный код и обычно вижу ("Fooing bar moo boo id={}, state={}", someStuff.getRequestId(), someStuff.getState())... S>Ну, при должной обработке в логе так и будет someStuff.RequestId, someStuff.State.
У меня в уме какое-то разделение — логи должны быть для людей с ролью поддержки, т.е. их явно формируешь для читабельности. Экспозить имена локальных переменных, которые для читабельности кода, люди с ролью программиста — так себе... Другими словами, логи должны описывать что делается, а не как. Притом, когда я рефакторю код, обычно логгирование оставляю неизменным.
Впрочем не знаю, может это навязанная привычка, были бы другие фичи в библиотеке логгирования, привык бы к другому.
S>>>Всё же прочтите статью. После этого можно будет попробовать её разбрать на sharplab.io и посмотреть, что за код генерируется в итоге. S>>>Разница между C# и интерполяцией из log2j2 не в поведении при отключении логгирования, а в безопасности. S>·>Это криворукость конкретно log4j. В том же logback (альтернативная реализация того же slf4j api) такого идиотизма нет. S>Эмм, а в чём конкретно криворукость? В самой идее интерполяции, или в её реализации? S>Если второе, то как logback решает проблему безопасности?
В самой идее интерполяции, точнее в дизайне "фичи" сделать её рекурсивной. Сейчас поигрался, т.е. log.info("Hello {}", "abc${env:VALUE}efg"), где VALUE=123${env:VALUE2}456, VALUE2=XYZ будет раскрывать ${} до конца:
Т.е. как я понял, кому-то это показалось гениальной супер-полезной фичей... и никто не подумал, что это хрень какая-то, да ещё и дырища...
Если подумать... как вообще кому-то в голову такое могло прийти <10 лет назад??! И это в ИТ-индустрии наученной SQL-инъекциями...
Одно оправдывает. Этот подход часто используется в конфигурационных файлах и там оно действительно полезно. Но конфиги — таки доверяемый код. Короче, запилили фичу "как у всех" не подумав.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай