Страницы

Поиск по вопросам

воскресенье, 26 января 2020 г.

Создание переменной ссылочного типа при каждой итерации цикла. Это затратно?

#c_sharp #оптимизация #инспекция_кода #рефакторинг


Есть вот такой код:

...
Match match;
foreach (var value in egeQuestionValues)
{
    match = Regex.Match(value, $@"(?<={number}\()\d+(?=.*%\))");
    if (!match.Success)
        throw new ArgumentException($"The record {value} is incorrect", nameof(egeQuestionValues));

    egeQuestionIntValues.Add(int.Parse(match.Value));                
}

return egeQuestionIntValues.Average();


Я создал переменную match специально вне цикла foreach, чтобы в рантайме не создать
ее при каждой итерации. Делал, думаю что это затратно. Но ReSharper мне советует отрефакторить
этот код таким образом:

...
foreach (var value in egeQuestionValues)
{
    var match = Regex.Match(value, $@"(?<={number}\()\d+(?=.*%\))");
    if (!match.Success)
        throw new ArgumentException($"The record {value} is incorrect", nameof(egeQuestionValues));

    egeQuestionIntValues.Add(int.Parse(match.Value));                
}

return egeQuestionIntValues.Average();


т.е. сейчас при каждой итерации переменная match будет создаваться каждый раз. Разве
это не затратно? ReSharper советует поступать так не только с типом Match и с другими
типами так было.
    


Ответы

Ответ 1



Разницы особой нету. Все переменные ссылочного типа без инициализации автоматически инициализируются null-значением. Что сама по себе переменная ссылочного типа? Это переменная, которая ссылается на какой-то адрес. Смена ссылки - это быстрая операция, так как не создается никаких дополнительных объектов, как это происходит со значимым типом. Скорее всего, компилятор приведет код к первому случаю и IL-код будет идентичным. На мой взгляд овчинка выделки не стоит и это не узкое место, ведь вы программируете на высокоуровневом языке программирования, где на первое место нужно ставить понятность кода, а уж потом если где-то что-то тормозит, то оптимизировать. Второй вариант более приятен, так как переменная находится прямо в месте ее использования и можно сразу понять, что за циклом она нигде использоваться не будет.

Ответ 2



Я что-то отличий не вижу, но может кто чего заметит пусть сигнализирует static void Main(string[] args) { List people1 = GetWithVarInWhile(); List people2 = GetWithVarOutWhile(); } private static List GetWithVarInWhile() { List result = new List(); int i = 10; while (i > 0) { var p = new Person { Name = $"Name{i}" }; result.Add(p); i--; } return result; } private static List GetWithVarOutWhile() { List result = new List(); int i = 10; Person person = null; while (i > 0) { person = new Person { Name = $"Name{i}" }; result.Add(person); i--; } return result; } Вот первый .method private hidebysig static class [mscorlib]System.Collections.Generic.List`1 GetWithVarInWhile() cil managed { // Размер кода: 71 (0x47) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 result, [1] int32 i, [2] class ConsoleWhile.Person p, [3] bool V_3, [4] class [mscorlib]System.Collections.Generic.List`1 V_4) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1::.ctor() IL_0006: stloc.0 IL_0007: ldc.i4.s 10 IL_0009: stloc.1 IL_000a: br.s IL_0037 IL_000c: nop IL_000d: newobj instance void ConsoleWhile.Person::.ctor() IL_0012: dup IL_0013: ldstr "Name{0}" IL_0018: ldloc.1 IL_0019: box [mscorlib]System.Int32 IL_001e: call string [mscorlib]System.String::Format(string, object) IL_0023: callvirt instance void ConsoleWhile.Person::set_Name(string) IL_0028: nop IL_0029: stloc.2 IL_002a: ldloc.0 IL_002b: ldloc.2 IL_002c: callvirt instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0) IL_0031: nop IL_0032: ldloc.1 IL_0033: ldc.i4.1 IL_0034: sub IL_0035: stloc.1 IL_0036: nop IL_0037: ldloc.1 IL_0038: ldc.i4.0 IL_0039: cgt IL_003b: stloc.3 IL_003c: ldloc.3 IL_003d: brtrue.s IL_000c IL_003f: ldloc.0 IL_0040: stloc.s V_4 IL_0042: br.s IL_0044 IL_0044: ldloc.s V_4 IL_0046: ret } // end of method Program::GetWithVarInWhile А вот второй .method private hidebysig static class [mscorlib]System.Collections.Generic.List`1 GetWithVarOutWhile() cil managed { // Размер кода: 73 (0x49) .maxstack 4 .locals init ([0] class [mscorlib]System.Collections.Generic.List`1 result, [1] int32 i, [2] class ConsoleWhile.Person person, [3] bool V_3, [4] class [mscorlib]System.Collections.Generic.List`1 V_4) IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1::.ctor() IL_0006: stloc.0 IL_0007: ldc.i4.s 10 IL_0009: stloc.1 IL_000a: ldnull IL_000b: stloc.2 IL_000c: br.s IL_0039 IL_000e: nop IL_000f: newobj instance void ConsoleWhile.Person::.ctor() IL_0014: dup IL_0015: ldstr "Name{0}" IL_001a: ldloc.1 IL_001b: box [mscorlib]System.Int32 IL_0020: call string [mscorlib]System.String::Format(string, object) IL_0025: callvirt instance void ConsoleWhile.Person::set_Name(string) IL_002a: nop IL_002b: stloc.2 IL_002c: ldloc.0 IL_002d: ldloc.2 IL_002e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1::Add(!0) IL_0033: nop IL_0034: ldloc.1 IL_0035: ldc.i4.1 IL_0036: sub IL_0037: stloc.1 IL_0038: nop IL_0039: ldloc.1 IL_003a: ldc.i4.0 IL_003b: cgt IL_003d: stloc.3 IL_003e: ldloc.3 IL_003f: brtrue.s IL_000e IL_0041: ldloc.0 IL_0042: stloc.s V_4 IL_0044: br.s IL_0046 IL_0046: ldloc.s V_4 IL_0048: ret } // end of method Program::GetWithVarOutWhile

Ответ 3



Компиляторы сейчас практически никогда не компилируют код вот прямо так, как написано в программе. Они стали намного умнее, чем были во времена Кернигана и Ричи. Если у вас появляется переменная, а потом исчезает, и появляется другая переменная, компилятор не будет наивно освобождать память, чтобы тут же снова её выделить. Он заметит, что новую переменную можно использовать место от старой переменной, и так и сделает. Смысла «помочь» компилятору и соптимизировать такие вот мелочи за него нет никакого: компилятор делает это, поверьте, куда лучше нас с вами. Если вы хотите соптимизировать, лучше пытаться улучшать алгоритмы. Переход от квадратичного алгоритма к логарифмическому даст заметное ускорение и улучшение производительности; выигрыш одной локальной переменной, даже если и имел бы какие-то преимущества, принёс бы вам существенно меньше микросекунды, и всё равно никем не был бы замечен.

Комментариев нет:

Отправить комментарий