2013-03-22 2 views
3

У меня есть две ситуации:Создает ли экземпляр локальной производительности с переменной скоростью?

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

и

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

The 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. Означает ли это, что первый случай будет работать слабее, чем второй случай, когда строка непосредственно передается методу вместо того, чтобы сначала хранить его в локальной переменной?

Я видел вопрос Does initialization of local variable with null impacts performance?, но, похоже, он немного отличается от того, что мне нужно знать здесь. Благодарю.

+3

Как вы скомпилировали код? Был ли он в режиме отладки или выпуска? Я считаю, что в релизе оба IL выглядели бы точно так же. – MarcinJuraszek

+0

Он был скомпилирован в отладке. Позвольте мне проверить с режимом выпуска. – ashtee

+0

Тот же результат с выпуском сборки. – ashtee

ответ

11

Вы смотрите на неоптимизированный ИЛ, с одной стороны - отсюда все «nop» s. Вы может найти, что он генерирует разный код при создании версии Release.

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

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

Как всегда:

  • цели производительности Установить, прежде чем начать, и мера против них.
  • Определите, какие решения будут трудно исправить позже с точки зрения производительности и беспокоиться о тех, которые гораздо важнее решений, подобных этому, которые впоследствии могут быть изменены без какого-либо воздействия в других местах.
  • Напишите самый простой, наиболее читаемый код, который будет работать в первую очередь.
  • Если не работает достаточно хорошо, изучите вопрос о том, могут ли изменения, влияющие на удобочитаемость, справляться с производительностью, чтобы гарантировать боль.
+0

Если только ваш vm не выполняет il, отправив его на удаленный сервер для выполнения ... Интересно, какова относительная стоимость циклов пары stloc/ldloc? Вы думаете, что это будет оптимизировано в регистр? – JerKimball

+0

@JerKimball: Вполне возможно. Но оптимизирующий JIT действительно не должен быть ужасно умным, чтобы это определить :) –

+0

Помимо присутствия «nop» s, я не вижу разницы в отладочной и выпускной версиях. – ashtee

1

Нет, это не повлияет на производительность. Вы можете подтвердить это, проверив, что машинный код, созданный для обоих, одинаковый. Обратите внимание, что в оптимизированной 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 еще не JITed.
  6. Тип !bpmd <full class name>.CreateCopyOfString и !bpmd <full class name>.DoNotCreateCopyOfString для разрыва при вызове метода. Тип g для продолжения. Также можно использовать !bpmd -md <address of MethodDesc> для установки контрольных точек.
  7. При ударе точки останова введите !u <address of MethodDesc>, чтобы сбрасывать машинный код для метода.

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

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

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

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

+0

Спасибо за разъяснение. – ashtee