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

Я всегда задавался вопросом, имеет ли вообще объявление отбрасываемой переменной перед циклом, в отличие от многократного использования внутри цикла, какую-либо разницу (производительность)? (совершенно бессмысленный) пример на 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);
}

Что лучше: a или b?

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

Изменить: меня особенно интересует случай Java.


person Rabarberski    schedule 02.01.2009    source источник
comment
Это важно при написании кода Java для платформы Android. Google предлагает, чтобы критичный по времени код объявлял увеличивающиеся переменные вне цикла for, как если бы внутри цикла for, он повторно объявлял это каждый раз в этой среде. Разница в производительности очень заметна для дорогих алгоритмов.   -  person aaroncarsonart    schedule 18.11.2015
comment
@AaronCarson Не могли бы вы предоставить ссылку на это предложение Google   -  person Vitaly Zinchenko    schedule 02.06.2016


Ответы (26)


Что лучше: a или b?

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

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

person Daniel Earwicker    schedule 02.01.2009
comment
Вместо Double, если он имеет дело со String, все же лучше вариант b? - person Antoops; 24.02.2014
comment
@Antoops - да, b лучше по причинам, не имеющим ничего общего с типом данных объявляемой переменной. Почему было бы иначе для струнных? - person Daniel Earwicker; 25.02.2015

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

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

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

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

person Mark    schedule 02.01.2009
comment
Ты победил меня. Я как раз собирался опубликовать свои результаты для профилирования, я получил более или менее то же самое, и да, на удивление, B быстрее, я бы подумал, что A, если бы мне нужно было сделать на это ставку. - person Mark Davidson; 02.01.2009
comment
Хорошо, круто, да, я смотрел только на время выполнения, как указал Р. Бемроуз в А, переменная остается после завершения цикла. Результаты вашего профиля рассказали вам что-нибудь об использовании памяти? - person Mark; 02.01.2009
comment
Не удивительно - когда переменная является локальной для цикла, ее не нужно сохранять после каждой итерации, поэтому она может оставаться в регистре. - person ; 02.01.2009
comment
+1 за фактическое тестирование, а не просто мнение / теорию, которую ОП мог придумать сам. - person MGOwen; 05.05.2010
comment
Какое время выполнения после запуска JIT? - person Philip Guin; 19.08.2012
comment
@GoodPerson, честно говоря, я бы хотел, чтобы это было сделано. Я запускал этот тест примерно 10 раз на своей машине для 50 000 000–100 000 000 итераций с почти идентичным фрагментом кода (которым я хотел бы поделиться со всеми, кто хочет запускать статистику). Ответы были разделены почти поровну в обе стороны, обычно с разницей в 900 мс (более 50 миллионов итераций), что на самом деле немного. Хотя моя первая мысль заключается в том, что это будет шум, он может немного наклоняться один за другим. Мне эти усилия кажутся чисто академическими (для большинства реальных приложений) .. Я бы все равно хотел увидеть результат;) Кто-нибудь согласен? - person javatarz; 14.05.2013
comment
@Wolf Ты не понимаешь, я не говорю, что один лучше другого. Я просто согласен с Рабарберски в том, что я также стараюсь кодировать пример «А». - person Mark; 04.06.2014
comment
Хорошо, тогда извините за голос против. Как вы думаете, можно ли улучшить свой ответ (мой голос сейчас заблокирован)? Кстати: действительно измерять, а не гадать, стоит поставить +1. Но с точки зрения производительности разработчика, я бы предпочел использовать версию B, если нет свидетельств снижения производительности (обычно тела цикла не такие тривиальные). - person Wolf; 05.06.2014
comment
В этом нет никакого смысла. Было бы лучше, если бы вы предоставили статистику. С какой стати A должен быть медленнее, чем B !? - person ; 29.07.2016
comment
Ставка на мой лучший фунт стерлингов получила 99% голосов без проверки. - person Leo; 22.11.2016
comment
Если вы не использовали хорошую систему микропрофилирования, есть множество причин, по которым A работает лучше, чем B, которые не связаны с различиями в коде. Вы пробовали запустить тесты в обратном порядке? Вы запускали циклы разогрева перед запуском тестов по времени? Как вы справлялись со временем ввода-вывода и учитывали ли вы его собственную (иногда весьма существенную) изменчивость? - person Ted Hopp; 19.01.2017
comment
Показывать результаты тестов без документирования настроек бесполезно. Это особенно верно в этом случае, когда оба фрагмента кода создают идентичный байт-код, поэтому любая измеренная разница является лишь признаком недостаточных условий тестирования. - person Holger; 15.02.2017
comment
@TedHopp. В вашем комментарии говорится, что A работает лучше, чем B, тогда как на самом деле B работает лучше, чем A. - person Collin Bell; 15.09.2018
comment
@CollinBell - Да, я ошибся. Однако это не имеет значения, в том смысле, который я пытался подчеркнуть в своем комментарии. - person Ted Hopp; 16.09.2018

Это зависит от языка и конкретного использования. Например, в 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.

person Jon Skeet    schedule 02.01.2009
comment
в примере B (исходный вопрос), действительно ли он каждый раз создает новую переменную? что творится на глазах у стека? - person Royi Namir; 31.05.2012
comment
@Jon, это была ошибка в C # 1.0? Разве в идеале Outer не должно быть 9? - person nawfal; 08.07.2014
comment
@nawfal: Я не понимаю, что вы имеете в виду. Лямбда-выражений не было в 1.0 ... а Outer - 9. Какую ошибку вы имеете в виду? - person Jon Skeet; 08.07.2014
comment
@nawfal: Я хочу сказать, что в C # 1.0 не было никаких языковых функций, в которых можно было бы отличить объявление переменной внутри цикла от объявления ее снаружи (при условии, что обе скомпилированы). Это изменилось в C # 2.0. Никакой ошибки. - person Jon Skeet; 08.07.2014
comment
@JonSkeet О да, теперь я понял, я полностью упустил из виду тот факт, что вы не можете закрывать такие переменные в 1.0, моя беда! :) - person nawfal; 08.07.2014

Ниже приводится то, что я написал и скомпилировал в .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. Между ними очень большая разница в производительности. Есть много таких случаев, когда компилятор не может оптимизировать ваш код, потому что он не может понять, что предполагается в более широком масштабе. Но он может в значительной степени оптимизировать для вас базовые вещи.

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

Это ошибка 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...
person Michael Haren    schedule 02.01.2009
comment
Я много лет использую VB.NET и не сталкивался с этим !! - person ChrisA; 02.01.2009
comment
Да, на практике в этом разбираться неприятно. - person Michael Haren; 02.01.2009
comment
Вот ссылка на это от Пола Вика: Panopticoncentral.net/archive/ 28 марта 2006 г. / 11552.aspx - person ferventcoder; 02.01.2009
comment
@eschneider @ferventcoder К сожалению, @PaulV решил отказаться от своего старого сообщения в блоге, так что теперь это мертвая ссылка. - person Mark Hurd; 19.05.2011
comment
да, совсем недавно столкнулся с этим; искал официальные документы по этому поводу ... - person Eric Schneider; 23.05.2011
comment
да, он не затемняет переменную снова. столкнулся с этим вчера, и ряд поисковиков позволил этому. - person smile.al.d.way; 22.11.2011

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

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

vs

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

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

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

vs

   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 

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

person UserX    schedule 10.10.2015
comment
да, и здорово, что вы это сделали, но это возвращается к тому, что люди говорили о зависимости языка / компилятора. Интересно, как это повлияет на производительность JIT или интерпретируемого языка. - person user137717; 04.04.2016

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

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

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

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

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

person Triptych    schedule 02.01.2009
comment
Вы концептуально хотите, чтобы переменная существовала в течение всего цикла, а не отдельно для каждой итерации? Я редко это делаю. Напишите код, который максимально ясно раскрывает ваше намерение, если только у вас нет очень и очень веской причины поступить иначе. - person Jon Skeet; 02.01.2009
comment
Ах, хороший компромисс, я никогда не думал об этом! ИМО, код действительно стал немного менее визуально `` понятным '') - person Rabarberski; 02.01.2009
comment
@Jon - я понятия не имею, что OP на самом деле делает с промежуточным значением. Просто подумал, что это стоит рассмотреть. - person Triptych; 02.01.2009

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

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

person Powerlord    schedule 02.01.2009
comment
sticks around after your loop is finished - хотя это не имеет значения для такого языка, как Python, где связанные имена остаются до завершения функции. - person new123456; 08.10.2011
comment
@ new123456: OP запросил особенности Java, даже если вопрос был задан в некотором роде. Многие языки, производные от C, имеют область видимости на уровне блоков: C, C ++, Perl (с ключевым словом my), C # и Java, чтобы назвать 5, которые я использовал. - person Powerlord; 08.10.2011
comment
Я знаю - это было наблюдение, а не критика. - person new123456; 08.10.2011

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

person Stew S    schedule 02.01.2009

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

person Chris    schedule 02.01.2009

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

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

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

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

person PhiLho    schedule 02.01.2009

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

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

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

person Mark Sowul    schedule 07.11.2012

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

{ //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);
    }
}

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

person Marcelo Faísca    schedule 23.10.2011

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

person SquidScareMe    schedule 02.01.2009

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

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

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

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

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

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

person R. Carr    schedule 16.01.2010
comment
Новая ссылка не выделяется для каждого объекта, даже если ссылка объявлена ​​в цикле for. В ОБЕИХ случаях: 1) 'o' - это локальная переменная, и пространство стека выделяется для нее один раз в начале функции. 2) На каждой итерации создается новый объект. Так что разницы в производительности нет. Для организации кода, удобочитаемости и ремонтопригодности лучше объявить ссылку внутри цикла. - person Ajoy Bhatia; 19.11.2010
comment
Хотя я не могу говорить о Java, в .NET ссылка не «выделяется» для каждого объекта в первом примере. В стеке есть единственная запись для этой локальной (для метода) переменной. Для ваших примеров созданный IL идентичен. - person Jesse C. Slicer; 17.12.2010

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

  • если тип переменной простой (int, double, ...) Я предпочитаю вариант b (внутри).
    Причина: сокращение объем переменной.

  • если тип переменной не простой (какой-то class или struct) я предпочитаю вариант a (внешний).
    Причина: сокращение количество звонков ctor-dtor.

person fat    schedule 05.02.2014

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

Вывод: для таких случаев разницы в производительности НЕТ.

Корпус с внешней петлей

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);
}
person twitu    schedule 09.02.2019

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

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 секунды.

person Alex    schedule 20.04.2015
comment
Должно быть, тогда была неоптимизированная компиляция Debug, а? - person Tomasz Przychodzki; 12.02.2016
comment
int j = 0 для (; j ‹0x3e8; j ++) таким образом объявляют один раз одновременно обе переменные, а не каждую для цикла. 2) назначение жирнее всех остальных вариантов. 3) Таким образом, лучшим правилом практики является любое объявление вне итерации для. - person luka; 07.12.2017

Я тестировал 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.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)
person user137717    schedule 04.04.2016

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».

person enthusiasticgeek    schedule 11.05.2010

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

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

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

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

person Joshua Siktar    schedule 06.03.2015
comment
lol Я не согласен по многим причинам ... Тем не менее, голосование против ... Я уважаю ваше право выбора - person Grantly; 31.12.2015

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

double intermediateResult;
int i = byte.MinValue;

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

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

person luka    schedule 13.07.2016

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

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

person SteveOC 64    schedule 19.02.2018

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

person user3029478    schedule 15.04.2021

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

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

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

person Abgan    schedule 02.01.2009