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

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

а) декларация перед циклом:

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

b) декларация (повторно) внутри цикла:

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

Какой из них лучше, a или b ?

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

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

    24 Solutions collect form web for “Разница между объявлением переменных до или в цикле?”

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

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

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

    Ну, я побежал ваши примеры A и B по 20 раз каждый, зацикливая 100 миллионов раз (JVM – 1.5.0)

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

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

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

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

    Пример:

     using System; using System.Collections.Generic; class Test { static void Main() { List actions = new List(); 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 переменную.

    Следующее – это то, что я написал и скомпилировал в .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] string a; a+=anotherstring[i] vs с помощью StringBuilder . Существует очень большая разница в производительности между ними. Есть много таких случаев, когда компилятор не может оптимизировать ваш код, потому что он не может понять, что предназначено в большей области. Но он может в значительной степени оптимизировать основные вещи для вас.

    Это будет в 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 имеют значения по умолчанию при объявлении!), Но каждый раз после этого.

    Если вы добавите a = 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... 

    Я сделал простой тест:

     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 
    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
    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 
    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
    0x00000000004004d3 < +29>: mov eax,0x0 0x00000000004004d8 < +34>: pop rbp 0x00000000004004d9 < +35>: ret

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

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

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

    Это все еще ограничивает intermediateResult в области цикла, но не обновляется во время каждой итерации.

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

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

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

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

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

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

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

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

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

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

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

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

    Ну, вы всегда можете сделать это для этого:

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

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

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

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

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

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

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

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

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

    Моя практика такова:

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

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

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

     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 млрд. Раз. outside () занимает 65 миллисекунд. внутрь () заняло 1,5 секунды.

    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? } 

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

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

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

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

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

    Я тестировал JS с Node 4.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) 

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

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

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

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

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

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

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

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

    Interesting Posts

    В каком порядке выполняются статические блоки и блоки инициализации при использовании наследования?

    Windows 7 Меню «Пуск»: «Поиск программ и файлов»

    Перекрытие совпадений в Regex

    Предотвратить преобразование имени рецензента в «Автор» после сохранения документа Word

    атрибут, зависящий от другого поля

    C #, путь к% AppData%

    Драйвер nVidia не установлен для x64 Windows 7 (Vaio VGN-SZ460N)

    Формула Excel для выведения котировок

    Как указать имя пользователя и пароль при подключении к сетевому ресурсу

    Подключение Java к базе данных MySQL

    Как эффективно удалять дубликаты из массива без использования Set

    ATI Radeon 5770 Eyefinity – три монитора

    Как определить, какой тип JRE установлен – 32 бит против 64 бит

    Сообщения ядра ядра на другой процесс по мере их возникновения

    Невозможно выполнить загрузку после удаленного системного зарезервированного раздела

    Давайте будем гением компьютера.