Разница между объявлением переменных до или в цикле?

Я всегда задавался вопросом, имеет ли вообще объявление отбрасываемой переменной перед циклом, в отличие от многократного использования внутри цикла, какую-либо разницу (производительность)? Пример (совершенно бессмысленно) на Java:

Объявление а) перед циклом:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Объявление б) (многократно) внутри цикла:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Что лучше, а или б?

Я подозреваю, что повторное объявление переменной (пример б) создает дополнительные накладные расходы теоретически, но эти компиляторы достаточно умны, так что это не имеет значения. Пример б имеет то преимущество, что он более компактен и ограничивает область действия переменной тем, где она используется. Тем не менее, я предпочитаю кодировать в соответствии с примером а.

Редактировать: Меня особенно интересует случай Java.

Это важно при написании кода Java для платформы Android. Google предлагает, чтобы критический по времени код объявлял увеличивающиеся переменные вне цикла for, как если бы внутри цикла for, он повторно объявлял это каждый раз в этой среде. Разница в производительности очень заметна для дорогих алгоритмов.

aaroncarsonart 18.11.2015 09:03

@AaronCarson Не могли бы вы предоставить ссылку на это предложение Google

Vitaly Zinchenko 02.06.2016 16:09
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
326
2
139 099
25
Перейти к ответу Данный вопрос помечен как решенный

Ответы 25

Я думаю, это зависит от компилятора, и сложно дать общий ответ.

Это зависит от языка - IIRC C# оптимизирует это, поэтому нет никакой разницы, но JavaScript (например) будет выполнять все операции выделения памяти каждый раз.

Да, но это не так уж много. Я провел простой тест с выполнением цикла for 100 миллионов раз и обнаружил, что наибольшая разница в пользу объявления вне цикла составила 8 мс. Обычно это было больше 3-4, а иногда объявление вне цикла выполнялось ХУЖЕ (до 4 мс), но это не было типично.

user137717 04.04.2016 22:29

Даже если я знаю, что мой компилятор достаточно умен, я не буду полагаться на него и буду использовать вариант а).

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

Обновлено: Джон Скит сделал очень хорошее замечание, показав, что объявление переменной внутри цикла может иметь реальную семантическую разницу.

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

Как правило, я объявляю свои переменные в самой внутренней возможной области. Итак, если вы не используете промежуточный результат вне цикла, я бы выбрал B.

Это зависит от языка и конкретного использования. Например, в C# 1 это не имело значения. В C# 2, если локальная переменная захватывается анонимным методом (или лямбда-выражением в C# 3), это может иметь очень существенное значение.

Пример:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner = {0}, Outer = {1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Выход:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

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

в примере B (исходный вопрос), действительно ли он каждый раз создает новую переменную? что творится на глазах у стека?

Royi Namir 31.05.2012 11:44

@Jon, это была ошибка в C# 1.0? Разве в идеале Outer не должен быть 9?

nawfal 08.07.2014 22:10

@nawfal: Я не понимаю, о чем ты. Лямбда-выражений не было в 1.0 ... и Outer является 9. Какую ошибку вы имеете в виду?

Jon Skeet 08.07.2014 22:32

@nawfal: Я хочу сказать, что в C# 1.0 не было никаких языковых функций, в которых можно было бы отличить объявление переменной внутри цикла от объявления ее снаружи (при условии, что обе скомпилированы). Это изменилось в C# 2.0. Никакой ошибки.

Jon Skeet 08.07.2014 22:40

@JonSkeet О да, теперь я понял, я полностью упустил из виду тот факт, что вы не можете закрывать такие переменные в 1.0, моя беда! :)

nawfal 08.07.2014 22:42

На мой взгляд, структура b лучше. В a последнее значение intermediateResult остается после завершения цикла.

Редактировать: Это не имеет большого значения для типов значений, но ссылочные типы могут быть несколько весомыми. Лично мне нравится разыменование переменных как можно скорее для очистки, и b делает это за вас,

sticks around after your loop is finished - хотя это не имеет значения для такого языка, как Python, где связанные имена остаются до завершения функции.
new123456 08.10.2011 07:24

@ new123456: OP запросил особенности Java, даже если вопрос было задан в некотором роде. Многие языки, производные от C, имеют область видимости на уровне блоков: C, C++, Perl (с ключевым словом my), C# и Java, чтобы назвать 5, которые я использовал.

Powerlord 08.10.2011 19:42

Я знаю - это было наблюдение, а не критика.

new123456 08.10.2011 21:34
Ответ принят как подходящий

Что лучше, а или б?

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

С точки зрения обслуживания лучше использовать б. Объявляйте и инициализируйте переменные в одном месте в максимально узкой области. Не оставляйте пустых промежутков между объявлением и инициализацией и не загрязняйте пространства имен, которые вам не нужны.

Вместо Double, если он имеет дело со String, все же лучше случай "b"?

Antoops 24.02.2014 16:00

@Antoops - да, b лучше по причинам, не имеющим ничего общего с типом данных объявляемой переменной. Почему было бы иначе для струнных?

Daniel Earwicker 25.02.2015 15:32

Я бы всегда использовал A (вместо того, чтобы полагаться на компилятор), а также мог бы переписать так:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Это по-прежнему ограничивает intermediateResult областью цикла, но не объявляется повторно во время каждой итерации.

Вы концептуально хотите, чтобы переменная существовала в течение всего цикла, а не отдельно для каждой итерации? Я редко это делаю. Напишите код, который максимально ясно раскрывает ваше намерение, если только у вас нет очень и очень веской причины поступить иначе.

Jon Skeet 02.01.2009 19:21

Ах, хороший компромисс, я никогда не думал об этом! ИМО, код действительно стал немного менее визуально `` понятным '')

Rabarberski 02.01.2009 19:25

@Jon - я понятия не имею, что OP на самом деле делает с промежуточным значением. Просто подумал, что это стоит рассмотреть вариант.

Triptych 02.01.2009 20:44

Это ошибка VB.NET. Результат Visual Basic не будет повторно инициализировать переменную в этом примере:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

Это будет печатать 0 в первый раз (переменные Visual Basic имеют значения по умолчанию при объявлении!), Но i каждый раз после этого.

Однако, если вы добавите = 0, вы получите то, что и ожидали:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

Я много лет использую VB.NET и не сталкивался с этим !!

ChrisA 02.01.2009 20:33

Да, на практике в этом разбираться неприятно.

Michael Haren 02.01.2009 20:59

Вот ссылка на это от Пола Вика: Panopticoncentral.net/archive/2006/03/28/11552.aspx

ferventcoder 02.01.2009 22:05

@eschneider @ferventcoder К сожалению, @PaulV решил использовать отказаться от его старых сообщений в блоге, так что теперь это мертвая ссылка.

Mark Hurd 19.05.2011 18:58

да, совсем недавно с этим сталкивался; искал официальные документы по этому поводу ...

Eric Schneider 23.05.2011 18:41

да, он не затемняет переменную снова. столкнулся с этим вчера, и ряд поисковиков позволил этому.

smile.al.d.way 22.11.2011 18:25

Что ж, я запускал ваши примеры A и B по 20 раз, зацикливался 100 миллионов раз (JVM - 1.5.0).

A: среднее время выполнения: 0,074 секунды

B: среднее время выполнения: 0,067 сек.

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

Ты победил меня. Я как раз собирался опубликовать свои результаты для профилирования, я получил более или менее то же самое, и да, на удивление, B быстрее, я бы подумал, что A, если бы мне нужно было сделать на это ставку.

Mark Davidson 02.01.2009 19:27

Хорошо, круто, да, я смотрел только на время выполнения, как указал Р. Бемроуз в A, переменная остается после завершения цикла. Результаты вашего профиля рассказали вам что-нибудь об использовании памяти?

Mark 02.01.2009 19:40

Не удивительно - когда переменная является локальной для цикла, ее не нужно сохранять после каждой итерации, поэтому она может оставаться в регистре.

user3458 02.01.2009 19:47

+1 за на самом деле тестирую это, а не просто мнение / теорию, которую ОП мог придумать сам.

MGOwen 05.05.2010 04:53

Какое время выполнения после запуска JIT?

Philip Guin 19.08.2012 08:13

@GoodPerson, честно говоря, я бы хотел, чтобы это было сделано. Я запускал этот тест примерно 10 раз на своей машине для 50 000 000–100 000 000 итераций с почти идентичным фрагментом кода (которым я хотел бы поделиться со всеми, кто хочет запускать статистику). Ответы были разделены почти поровну в обе стороны, обычно с разницей в 900 мс (более 50 миллионов итераций), что на самом деле немного. Хотя моя первая мысль заключается в том, что это будет «шум», он может наклоняться один за другим. Мне эти усилия кажутся чисто академическими (для большинства реальных приложений) .. Я бы все равно хотел увидеть результат;) Кто-нибудь согласен?

javatarz 14.05.2013 13:46

@Wolf Ты не понимаешь, я не говорю, что один лучше другого. Я просто согласен с Рабарберски в том, что я также стараюсь кодировать пример «А».

Mark 04.06.2014 19:54

Хорошо, тогда извините за голос против. Как вы думаете, можно ли улучшить свой ответ (мой голос сейчас заблокирован)? Кстати: на самом деле измерение, а не предположение, стоит поставить +1. Но с точки зрения производительности разработчика, я бы предпочел использовать версию B, если нет свидетельств снижения производительности (обычно тела цикла не такие тривиальные).

Wolf 05.06.2014 00:34

В этом нет никакого смысла. Было бы лучше, если бы вы предоставили статистику. Почему, черт возьми, А должно быть медленнее, чем В !?

user6538026 29.07.2016 15:41

Ставка на мой лучший фунт стерлингов получила 99% голосов без проверки.

Leo 22.11.2016 17:45

Если вы не использовали хорошую систему микропрофилирования, есть множество причин, по которым A работает лучше, чем B, которые не связаны с различиями в коде. Вы пробовали запустить тесты в обратном порядке? Вы запускали циклы разогрева перед запуском тестов по времени? Как вы справлялись со временем ввода-вывода и учитывали ли вы его собственную (иногда весьма существенную) изменчивость?

Ted Hopp 19.01.2017 22:59

Показывать результаты тестов без документирования настроек бесполезно. Это особенно верно в этом случае, когда оба фрагмента кода создают идентичный байт-код, поэтому любая измеренная разница является лишь признаком недостаточных условий тестирования.

Holger 15.02.2017 17:36

@TedHopp. В вашем комментарии говорится, что A работает лучше, чем B, тогда как на самом деле B работает лучше, чем A.

Collin Bell 15.09.2018 09:44

@CollinBell - Да, я ошибся. Тем не менее, это не имеет значения для того, что я пытался сделать в своем комментарии.

Ted Hopp 16.09.2018 03:37

Сотрудник предпочитает первую форму, говоря, что это оптимизация, и предпочитает повторно использовать объявление.

Я предпочитаю второй (и пытаюсь уговорить коллегу! ;-)), прочитав это:

  • Это сокращает объем переменных до того места, где они необходимы, и это хорошо.
  • Java достаточно оптимизируется, чтобы не оказывать существенного влияния на производительность. IIRC, возможно, вторая форма еще быстрее.

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

Ниже приводится то, что я написал и скомпилировал в .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Это то, что я получаю от .NET Reflector, когда CIL возвращается в код.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Таким образом, после компиляции оба выглядят одинаково. В управляемых языках код преобразуется в CL / байт-код, а во время выполнения преобразуется в машинный язык. Таким образом, на машинном языке дубль даже не может быть создан в стеке. Это может быть просто регистр, поскольку код отражает, что это временная переменная для функции WriteLine. Только для циклов существует целый набор правил оптимизации. Так что обычному парню не стоит беспокоиться об этом, особенно если речь идет о управляемых языках. Есть случаи, когда вы можете оптимизировать управляющий код, например, если вам нужно объединить большое количество строк, используя только string a; a+=anotherstring[i], а не используя StringBuilder. Между ними очень большая разница в производительности. Есть много таких случаев, когда компилятор не может оптимизировать ваш код, потому что он не может понять, что предполагается в более широком масштабе. Но он может в значительной степени оптимизировать для вас базовые вещи.

int j = 0 для (; j <0x3e8; j ++) таким образом объявляют один раз одновременно обе переменные, а не каждую для цикла. 2) назначение жирнее всех остальных вариантов. 3) Таким образом, лучшим правилом практики является любое объявление вне итерации для.

luka 07.12.2017 17:24

Я всегда думал, что если вы объявляете свои переменные внутри цикла, вы тратите память. Если у вас что-то вроде этого:

for(;;) {
  Object o = new Object();
}

Тогда не только объект должен быть создан для каждой итерации, но и должна быть выделена новая ссылка для каждого объекта. Кажется, что если сборщик мусора работает медленно, у вас будет куча висящих ссылок, которые необходимо очистить.

Однако, если у вас есть это:

Object o;
for(;;) {
  o = new Object();
}

Тогда вы создаете только одну ссылку и каждый раз назначаете ей новый объект. Конечно, это может занять немного больше времени, чтобы он вышел за пределы области видимости, но тогда нужно иметь дело только с одной висящей ссылкой.

Хотя я не могу говорить о Java, в .NET ссылка не «выделяется» для каждого объекта в первом примере. В стеке есть единственная запись для этой локальной (для метода) переменной. Для ваших примеров созданный IL идентичен.

Jesse C. Slicer 17.12.2010 19:53

Новая ссылка не выделяется для каждого объекта, даже если ссылка объявлена ​​в цикле for. В ОБЕИХ случаях: 1) 'o' - это локальная переменная, и пространство стека выделяется для нее один раз в начале функции. 2) На каждой итерации создается новый объект. Так что разницы в производительности нет. Для организации кода, удобочитаемости и ремонтопригодности лучше объявить ссылку внутри цикла.

Ajoy Bhatia 19.11.2010 00:28

A) более безопасный вариант, чем B) ......... Представьте себе, что если вы инициализируете структуру в цикле, а не int или float, что тогда?

нравиться

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Вы наверняка столкнетесь с проблемами утечки памяти !. Следовательно, я считаю, что «A» более безопасная ставка, в то время как «B» уязвима для накопления памяти, особенно при работе с библиотеками с близким исходным кодом. Вы можете проверить использование инструмента «Valgrind» в Linux, в частности вспомогательного инструмента «Helgrind».

Что ж, вы всегда можете найти место для этого:

{ //Or if (true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Таким образом, вы объявляете переменную только один раз, и она умрет, когда вы выйдете из цикла.

В C# есть разница, если вы используете переменную в лямбде и т. д. Но в целом компилятор будет делать то же самое, предполагая, что переменная используется только внутри цикла.

Учитывая, что они в основном одинаковы: обратите внимание, что версия b делает гораздо более очевидным для читателей, что переменная не может и не может использоваться после цикла. Кроме того, версию b гораздо легче реорганизовать. В версии а сложнее извлечь тело цикла в собственный метод. Более того, версия b гарантирует отсутствие побочных эффектов такого рефакторинга.

Следовательно, версия a меня до бесконечности раздражает, потому что она бесполезна и значительно усложняет рассуждение о коде ...

Моя практика следующая:

  • если тип переменной простой (число, двойное, ...), я предпочитаю вариант б (внутри) .
    Причина: уменьшает объем переменной.

  • если тип переменной не простой (какой то class или struct) Я предпочитаю вариант а (снаружи) .
    Причина: уменьшает количество вызовов ctor-dtor.

Это интересный вопрос. По моему опыту, когда вы обсуждаете этот вопрос для кода, следует учитывать главный вопрос:

Есть ли причина, по которой переменная должна быть глобальной?

Имеет смысл объявлять переменную только один раз, глобально, а не много раз локально, потому что это лучше для организации кода и требует меньше строк кода. Однако, если ее нужно объявить локально только в одном методе, я бы инициализировал ее в этом методе, чтобы было ясно, что переменная имеет отношение исключительно к этому методу. Будьте осторожны, не вызывайте эту переменную вне метода, в котором она инициализируется, если вы выберете последний вариант - ваш код не будет знать, о чем вы говорите, и сообщит об ошибке.

Также, в качестве примечания, не дублируйте имена локальных переменных между разными методами, даже если их цели почти идентичны; это просто сбивает с толку.

lol Я не согласен по многим причинам ... Тем не менее, голосование против ... Я уважаю ваше право выбора

Grantly 31.12.2015 01:53

С точки зрения производительности, снаружи (намного) лучше.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Я выполнил обе функции по 1 миллиарду раз каждую. external () занял 65 миллисекунд. inside () занял 1,5 секунды.

Должно быть, тогда была неоптимизированная компиляция Debug, а?

Tomasz Przychodzki 12.02.2016 13:47

int j = 0 для (; j <0x3e8; j ++) таким образом объявляют один раз одновременно обе переменные, а не каждую для цикла. 2) назначение жирнее всех остальных вариантов. 3) Таким образом, лучшим правилом практики является любое объявление вне итерации для.

luka 07.12.2017 17:25

Провел простой тест:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

против

for (int i = 0; i < 10; i++) {
    int b = i;
}

Я скомпилировал эти коды с помощью gcc - 5.2.0. А потом разобрал основную () этих двух кодов и вот результат:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

против

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Которые точно такие же, как и результат. Разве это не доказательство того, что два кода производят одно и то же?

да, и здорово, что вы это сделали, но это возвращается к тому, что люди говорили о зависимости языка / компилятора. Интересно, как это повлияет на производительность JIT или интерпретируемого языка.

user137717 04.04.2016 22:24

Я тестировал JS с Node 4.0.0, если кому-то интересно. Объявление вне цикла привело к повышению производительности примерно на 0,5 мс в среднем за 1000 испытаний со 100 миллионами итераций цикла за испытание. Так что я собираюсь сказать, продолжайте и напишите его наиболее читаемым / поддерживаемым способом, которым является B, imo. Я бы поместил свой код в скрипку, но я использовал модуль Node для повышения производительности. Вот код:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.info('declared inside loop', insideAvg)
console.info('declared outside loop', outsideAvg)

это лучшая форма

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) таким образом однажды объявили раз и переменную, а не каждую для цикла. 2) назначение жирнее всех остальных вариантов. 3) Таким образом, лучшим правилом практики является любое объявление вне итерации для.

Попробовал то же самое в Go и сравнил вывод компилятора с использованием go tool compile -S с go 1.9.4

Нулевая разница, согласно выводам ассемблера.

У меня долгое время был один и тот же вопрос. Поэтому я протестировал еще более простой фрагмент кода.

Заключение: Для такие случаи существует разница в производительности НЕТ.

Внешний корпус петли

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Внутренний корпус петли

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Я проверил скомпилированный файл в декомпиляторе IntelliJ и в обоих случаях получил одно и тожеTest.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

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

Внешний корпус петли

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Внутренний корпус петли

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Если вы обратите пристальное внимание, только Slot, назначенные для i и intermediateResult в LocalVariableTable, меняются местами в зависимости от порядка их появления. Такая же разница в слоте отражается в других строках кода.

  • Никаких дополнительных операций не выполняется
  • intermediateResult по-прежнему является локальной переменной в обоих случаях, поэтому нет разницы во времени доступа.

БОНУС

Компиляторы делают массу оптимизаций, посмотрите, что в этом случае происходит.

Нулевой рабочий случай

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Ноль работы декомпилирован

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

Другие вопросы по теме