Влияет ли создание экземпляра локальной строковой переменной на производительность?

У меня две ситуации:

    static void CreateCopyOfString()
    {
        string s = "Hello";
        ProcessString(s);
    }

и

    static void DoNotCreateCopyOfString()
    {
        ProcessString("Hello");
    }

IL для этих двух ситуаций выглядит следующим образом:

    .method private hidebysig static void  CreateCopyOfString() cil managed
    {
        // Code size       15 (0xf)
        .maxstack  1
        .locals init ([0] string s)
        IL_0000:  nop
        IL_0001:  ldstr      "Hello"
        IL_0006:  stloc.0
        IL_0007:  ldloc.0
        IL_0008:  call       void ConsoleApplication1.Program::ProcessString(string)
        IL_000d:  nop
        IL_000e:  ret
    } // end of method Program::CreateCopyOfString

и

    .method private hidebysig static void  DoNotCreateCopyOfString() cil managed
    {
          // Code size       13 (0xd)
          .maxstack  8
          IL_0000:  nop
          IL_0001:  ldstr      "Hello"
          IL_0006:  call       void ConsoleApplication1.Program::ProcessString(string)
          IL_000b:  nop
          IL_000c:  ret
    } // end of method Program::DoNotCreateCopyOfString

В первом случае есть дополнительные вызовы для string init, stloc.0 и ldloc.0. Означает ли это, что первый случай будет работать слабее, чем второй случай, когда строка напрямую передается методу, а не сначала сохраняется в локальной переменной?

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


person ashtee    schedule 22.03.2013    source источник
comment
Как вы компилировали код? Это было в режиме отладки или выпуска? Я считаю, что в релизе оба IL выглядели бы абсолютно одинаково.   -  person MarcinJuraszek    schedule 23.03.2013
comment
Он был скомпилирован в режиме отладки. Позвольте мне проверить с режимом выпуска.   -  person ashtee    schedule 23.03.2013
comment
Тот же результат с релизной сборкой.   -  person ashtee    schedule 23.03.2013


Ответы (2)


Во-первых, вы смотрите на неоптимизированный IL — отсюда и все «нет». Вы можете обнаружить, что при сборке версии Release генерируется другой код.

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

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

Как всегда:

  • Прежде чем приступить к работе, установите цели производительности и оцените их соответствие.
  • Выясните, какие решения будет трудно исправить позже с точки зрения производительности, и беспокойтесь о них гораздо больше, чем о таких решениях, которые можно изменить позже без каких-либо последствий в других местах.
  • Сначала напишите самый простой, самый читаемый код, который будет работать.
  • Если это работает недостаточно хорошо, выясните, достаточно ли внесение изменений, которые ухудшают читабельность, повышают производительность, чтобы оправдать боль.
person Jon Skeet    schedule 22.03.2013
comment
Нет, если только ваша виртуальная машина не выполняет il, отправляя ее по электронной почте на удаленный сервер для выполнения... Интересно, какова относительная стоимость в циклах пары stloc/ldloc? Как вы думаете, это, вероятно, будет оптимизировано в регистр? - person JerKimball; 23.03.2013
comment
@JerKimball: Вполне возможно. Но оптимизирующий JIT действительно не должен быть ужасно умным, чтобы заметить это :) - person Jon Skeet; 23.03.2013
comment
Кроме наличия nops разницы в отладочной и релизной сборке не вижу. - person ashtee; 23.03.2013
comment
Уверен, что читабельность лучше при первом подходе с более сложным формированием строки с использованием чего-то вроде string.Format. Вот где я обсуждал, должна ли локальная переменная хранить строку, а затем передавать ее следующему методу или нет. - person ashtee; 23.03.2013
comment
@ashtee: Не нужно спорить - просто напишите простой код. Сравните сложность синтаксического анализа строки формата, создания этой новой строки и т. д. с возможной работой (которой, вероятно, даже не существует после JITting) сохранения ссылки и ее повторной загрузки. - person Jon Skeet; 23.03.2013

Нет, это не повлияет на производительность. Вы можете подтвердить это, убедившись, что машинный код, созданный для обоих, одинаков. Обратите внимание, что в оптимизированном JIT ProcessString может быть встроен. Чтобы избежать этого, вы можете добавить [MethodImpl(MethodImplOptions.NoInlining)]. Скомпилируйте оптимизированную (Release) сборку.

  1. Откройте исполняемый файл в WinDbg. Используйте соответствующую 32- или 64-битную версию в зависимости от вашего EXE.
  2. Введите sxe ld clrjit для прерывания при загрузке clrjit.dll. Введите g, чтобы продолжить до перерыва.
  3. Загрузите SOS с помощью .loadby sos clr. Обратите внимание, что для более ранних версий CLR вам нужно использовать mscorwks вместо clr.
  4. Найдите адрес таблицы методов с !name2ee * <full class name>.
  5. Введите !dumpmt -md <address of MethoTable> для вывода сведений о методе. Обратите внимание, что CreateCopyOfString и DoNotCreateCopyOfString еще не прошли JIT-компиляцию.
  6. Введите !bpmd <full class name>.CreateCopyOfString и !bpmd <full class name>.DoNotCreateCopyOfString для прерывания при вызове метода. Введите g, чтобы продолжить. Также можно использовать !bpmd -md <address of MethodDesc> для установки точек останова.
  7. Когда сработает точка останова, введите !u <address of MethodDesc>, чтобы вывести машинный код метода.

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

Фактические регистры и адреса будут различаться, но оба метода привели к следующему машинному коду:

sub     rsp,28h
mov     rcx,121E3258h
mov     rcx,qword ptr [rcx]
call    000007fe`9852c038
nop
add     rsp,28h
ret

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

person Dono    schedule 23.03.2013
comment
Спасибо за разъяснения. - person ashtee; 23.03.2013