Накладные расходы (скорость и мусор) на вызов функций, которые ничего не делают

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

Я провел тест на вызов следующих методов:

public static final isLogging = false; 

public static logObjs(Object[] params) {
  if(isLogging)
    System.out.println(params[0]);
}

public static log3Obj(Object a, Object b, Object c) {
  if(isLogging)
     System.out.println(a);
}

public static logInts(int a, int b, int c) {
  if(isLogging)
    System.out.println(a);
}

Я проверил функции с помощью метода драйвера

long sum = 0;
for(int i = 0; i < 100000000; ++i) {
   int a = i; int b = i+1; int c = i+2;
   logFoo(a,b,c);
   sum += a; }

logObjs(i, i+1, i+2) занимает около 2 секунд для 1e8 итераций и производит много мусора. Источниками, я полагаю, являются автоупаковка целых чисел и создание Object[] для переменной # параметров.

log3Obj производит много (хотя и меньше) мусора и занимает около 1,2 секунды; опять же, я предполагаю, что автобоксинг все еще происходит.

logInts выполняется очень быстро (0,2 секунды), так же быстро, как цикл без вызова функции.

Итак, проблема в том, что даже если функция детерминистически ничего не делает, автоупаковка все равно происходит. В моем коде я бы предпочел, чтобы isLogging не был окончательным, а был бы параметром времени выполнения, но для этого в этом более простом случае (где компилятор может доказать, что функция ничего не делает) должен работать. Конечно, я могу заменить все свои операторы регистрации на

if(isLogging)
   logObjs(a, b, c);

но это очень неэлегантно. Я думал, что это то, о чем должна позаботиться JIT. Я перепробовал множество настроек компилятора, но, может быть, я что-то упускаю? Как сделать так, чтобы код не генерировал столько мусора, ничего не делая?


person bsdfish    schedule 22.06.2010    source источник
comment
Как сделать так, чтобы код не генерировал столько мусора, ничего не делая? остановить автобокс   -  person matt b    schedule 22.06.2010
comment
Я бы не стал доверять таким небрежным тестам. Читайте дальше: code.google.com/p/caliper/wiki/JavaMicrobenchmarks   -  person Kevin Bourrillion    schedule 22.06.2010


Ответы (3)


Это очень похоже на этот вопрос: ">Будет ли оптимизатор Java удалять построение параметров для пустых вызовов методов?

Как я написал там: JIT вполне может понять, что ему не нужно ничего делать (немного меньше в вашем случае, поскольку он более вовлечен). Но, по-видимому, это не так.

Я бы предложил создать несколько методов журнала, общий с некоторыми параметрами vararg и некоторыми перегруженными, принимающими целые числа и предотвращающими автоупаковку в первую очередь:

Log(Object... arguments) { /* do logging */ }
Log(Object a, Object b, Object c}  { /* special case for 3 objects */ }
Log(int a, int b, int c}  { /* special case for 3 ints */ }

ОБНОВЛЕНИЕ: Еще лучше, посмотрите ответ Петера Торёка «не изобретайте велосипед» …

person Janick Bernet    schedule 22.06.2010

Вы действительно должны предпочесть существующую структуру ведения журналов, такую ​​​​как Log4J, вместо того, чтобы изобретать колесо. Эти ребята приложили больше усилий для оптимизации своей структуры ведения журналов, чем вы когда-либо могли себе представить. См. раздел «Производительность» в конце этого Краткого введения в log4j.

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

person Péter Török    schedule 22.06.2010
comment
Спасибо за Ваш ответ. В реальной программе код, который я пишу, представляет собой обертку над log4j, которая добавляет некоторые функции. Однако я понимаю, что log4j страдает от той же проблемы. В частности, в разделе, на который вы указываете, пункт 1. точно указана стоимость, которую я пытаюсь минимизировать. Он предлагает непривлекательную альтернативу — засорять мой код без регистрации с помощью if(isLogging) { doLog(); } конструкции. Я надеюсь найти более чистый способ. В C++ я бы использовал шаблоны для автоматического создания целой группы методов ведения журнала, которые позволили бы избежать упаковки... - person bsdfish; 22.06.2010
comment
Я бы вряд ли назвал использование if (log.isDebugEnabled()) мусором. Это довольно стандартно и сделано по уважительной причине. Вы можете легко настроить свою IDE на использование шаблона кода, чтобы всего несколько нажатий клавиш генерировали для вас полную строку кода. - person matt b; 22.06.2010
comment
@bsdfish, да, это правда. Вы можете попробовать улучшить свой API, чтобы избежать автоматической упаковки и помочь оптимизатору JIT, как это было предложено другими. Но не заходите слишком далеко — дизайн API не должен быть направлен на оптимизацию производительности. Затем, если у вас есть приложение, в котором ведение журнала является узким местом, вы все равно можете добавить эти уродливые ifs в крайнем случае. - person Péter Török; 22.06.2010
comment
Конечно мусорит! Это серьезное нарушение абстракции, затрудняющее чтение кода и повышающее вероятность ошибок. на самом деле это должно быть что-то вроде if(log.isLogLevelEnabled(LogLevel.A_LOG_LEVEL) log.log(LogLevel.A_LOG_LEVEL, formatString, formatArgs). Или оператор журнала не может принимать уровень журнала, что делает его уязвимым для ошибка, связанная с отсутствием проверки того, следует ли распечатать оператор. - person bsdfish; 22.06.2010
comment
@peter: это узкое место. Я пишу чувствительное к задержке приложение, и на ведение журнала приходится около 90% генерируемого мусора ... Я хотел знать, есть ли способ сделать то, о чем я просил, в java. Это довольно просто в С++ с препроцессором и/или шаблонами. В Лиспе я бы сделал log макросом, который бы неявно генерировал if(correctLogLevelEnabled). - person bsdfish; 22.06.2010
comment
@bsdfish, возможно, это вопрос личного вкуса, но я не нахожу if (log.isInfoEnabled()) log.info(".."); таким уж плохим. API-интерфейс log4j и аналогичные API отлично справляются с задачей сделать функцию «проверить-если-включено-тог-журнал» максимально читабельной и короткой. - person matt b; 22.06.2010
comment
@bsdfish, мусор или нет, по-видимому, зависит от личного вкуса. Поскольку в Java нет ни препроцессора, ни шаблонов в стиле C++, вам просто нужно решить, что вас больше волнует: нарушения абстракции или производительность. ИМХО, независимо от языка, оптимизированный по производительности код редко бывает красивым и чистым - вы должны чем-то жертвовать... - person Péter Török; 23.06.2010

Я думаю, вы должны регистрировать то, что вам нужно, а не преобразовывать целые числа в объекты.

Вызов logObjs() или log3Obj() для регистрации целых чисел - просто пустая трата времени - вы собираетесь создавать временные объекты для упаковки/распаковки, и их нужно будет утилизировать. Вы ничего не можете с этим поделать.

Я подозреваю, что большую часть времени, когда вы собираетесь вызывать logObjs(), вы будете передавать функции реальные объекты, и в этом случае стоимость GC почти равна нулю, так как вы просто будете передавать ссылки на эти объекты в logObjs( ) и не требуется создание или удаление объекта.

Напишите несколько функций ведения журнала, которые принимают разные списки аргументов, соответствующие тому, что вы собираетесь регистрировать в данный момент. Это то, чем я занимаюсь. Если мне нужно зарегистрировать строку и 32-битное значение, у меня есть API регистрации с этими параметрами и т. д. Нет (или немного) ненужного/временного создания объекта.

Если вам нужно зарегистрировать 3 целых числа, напишите API, который принимает 3 целых числа. Если вам нужно зарегистрировать объект Weeble и объект Wobble, а также объект Dont Fall Down, напишите API и для этого. Сведите к минимуму эффекты box/unobx/GC, используя соответствующие API для функций регистрации.

person Stephen Kellett    schedule 22.06.2010