Передать аргумент в метод через вызов другого метода. Есть ли разница в производительности?

К примеру, я могу написать такой код

var key = ConvertToKey<TViewModel>();
var page = typeof(TView);
_ = _pages.TryAdd(key, page)
    ? true
    : throw new InvalidOperationException($"ViewModel already registered '{key}'");

И полная аналогия, но без объявления переменных

_ = _pages.TryAdd(ConvertToKey<TViewModel>(), typeof(TView))
    ? true
    : throw new InvalidOperationException($"ViewModel already registered '{ConvertToKey<TViewModel>()}'");

Мне интересно какова разница с точки зрения времени выполнения (runtime). Код выше для примера, абстрагируемся от простых возвращаемых типов. Представим, что подобных методов в программе будет 100 или 1000. Какой из вариантов написания предпочтительнее для лучшей производительности?

Может быть дело только в читаемости? И в одном случае предпочтительней написать в одну строку, в другом случае создавать локальные переменные.


Ответы (1 шт):

Автор решения: default locale

Мне интересно какова разница с точки зрения времени выполнения (runtime).

Никакой. Компилятор с большой вероятностью сгенерирует эквивалентный по производительности IL-code.

Рассмотрим на примере код C#:

using System;
public static class C {
    public static void Inline() 
    {
        Use(Generate());    
    }   

    public static void Declare() 
    {
        int val = Generate();
        Use(val);    
    }   

    private static void Use(int a) 
    {
        Console.Write(a);
    }

    private static int Generate() 
    {
        return DateTime.Now.Millisecond;
    }
}

Сравним сгенерированный компилятором IL-code (например, на https://sharplab.io/) для методов Inline (без переменной) и Declare:

.method public hidebysig static 
    void Inline () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 11 (0xb)
    .maxstack 8

    IL_0000: call int32 C::Generate()
    IL_0005: call void C::Use(int32)
    IL_000a: ret
} // end of method C::Inline

.method public hidebysig static 
    void Declare () cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 11 (0xb)
    .maxstack 8

    IL_0000: call int32 C::Generate()
    IL_0005: call void C::Use(int32)
    IL_000a: ret
} // end of method C::Declare

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

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

На практике работа над улучшением производительности начинается с тщательного измерения скорости выполнения. После этого будут обнаружены части кода, которые отнимают наибольшее время. Со 100%-й вероятностью локальная переменная не будет узким местом программы, кроме, разве что, тех случаев, когда объявление связано с логической ошибкой и изменением поведения программы.

«Преждевременная оптимизация — корень всех зол.» — Дональд Кнут.

Для чистоты кода следует выполнять простые правила для локальных переменных:

  • если переменная объявляется, то объявлять ее следует как можно ближе к месту использования;
  • нужно удалять неиспользуемые переменные.

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

Какой из вариантов написания предпочтительнее для лучшей производительности?

С точки зрения производительности варианты эквивалентны.

Может быть дело только в читаемости?

Только в ней. Как удобнее так и пишите.

И в одном случае предпочтительней написать в одну строку, в другом случае создавать локальные переменные.

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

→ Ссылка