Почему разница в производительности между С# (довольно немного медленнее) и Win32/C?
Мы стремимся перенести приложение с критическими характеристиками на .Net и обнаруживаем, что версия С# на 30% -100% медленнее, чем Win32/C, в зависимости от процессора (разница больше на процессоре T7200). У меня очень простой пример кода, который демонстрирует это. Для краткости я просто покажу версию C - С# - прямой перевод:
#include "stdafx.h"
#include "Windows.h"
int array1[100000];
int array2[100000];
int Test();
int main(int argc, char* argv[])
{
int res = Test();
return 0;
}
int Test()
{
int calc,i,k;
calc = 0;
for (i = 0; i < 50000; i++) array1[i] = i + 2;
for (i = 0; i < 50000; i++) array2[i] = 2 * i - 2;
for (i = 0; i < 50000; i++)
{
for (k = 0; k < 50000; k++)
{
if (array1[i] == array2[k]) calc = calc - array2[i] + array1[k];
else calc = calc + array1[i] - array2[k];
}
}
return calc;
}
Если мы посмотрим на разборку в Win32 для "else", мы имеем:
35: else calc = calc + array1[i] - array2[k];
004011A0 jmp Test+0FCh (004011bc)
004011A2 mov eax,dword ptr [ebp-8]
004011A5 mov ecx,dword ptr [ebp-4]
004011A8 add ecx,dword ptr [eax*4+48DA70h]
004011AF mov edx,dword ptr [ebp-0Ch]
004011B2 sub ecx,dword ptr [edx*4+42BFF0h]
004011B9 mov dword ptr [ebp-4],ecx
(это отлаживается, но несите меня)
Разборка для оптимизированной версии С# с использованием отладчика CLR на оптимизированном exe:
else calc = calc + pev_tmp[i] - gat_tmp[k];
000000a7 mov eax,dword ptr [ebp-4]
000000aa mov edx,dword ptr [ebp-8]
000000ad mov ecx,dword ptr [ebp-10h]
000000b0 mov ecx,dword ptr [ecx]
000000b2 cmp edx,dword ptr [ecx+4]
000000b5 jb 000000BC
000000b7 call 792BC16C
000000bc add eax,dword ptr [ecx+edx*4+8]
000000c0 mov edx,dword ptr [ebp-0Ch]
000000c3 mov ecx,dword ptr [ebp-14h]
000000c6 mov ecx,dword ptr [ecx]
000000c8 cmp edx,dword ptr [ecx+4]
000000cb jb 000000D2
000000cd call 792BC16C
000000d2 sub eax,dword ptr [ecx+edx*4+8]
000000d6 mov dword ptr [ebp-4],eax
Еще много инструкций, по-видимому, причина разницы в производительности.
Итак, 3 вопроса:
-
Я рассматриваю правильную разборку для двух программ или инструменты меня вводят в заблуждение?
-
Если разница в количестве сгенерированных инструкций не является причиной разницы, что такое?
-
Что мы можем сделать с этим, кроме того, чтобы сохранить весь наш критически важный код в собственной DLL.
Спасибо заранее
Стив
PS Недавно я получил приглашение на совлокальный семинар MS/Intel, озаглавленный "Создание высокопроизводительных критически важных приложений". Хмм...
Ответы
Ответ 1
Я считаю, что ваша основная проблема в этом коде будет проверкой границ ваших массивов.
Если вы переключитесь на использование небезопасного кода на С# и используйте математику указателя, вы сможете добиться того же (или потенциально более быстрого) кода.
Эта же проблема была которая была подробно обсуждена в этом вопросе.
Ответ 2
Я считаю, что вы видите результаты проверок границ массивов. Вы можете избежать проверки границ с помощью небезопасного кода.
Я считаю, что JITer может распознавать шаблоны, подобные циклам, которые подходят к array.Length и избегают проверки границ, но это не похоже на то, что ваш код может использовать это.
Ответ 3
Как говорили другие, одним из аспектов является проверка границ. Там также есть избыточность в вашем коде с точки зрения доступа к массиву. Мне удалось улучшить производительность, изменив внутренний блок на:
int tmp1 = array1[i];
int tmp2 = array2[k];
if (tmp1 == tmp2)
{
calc = calc - array2[i] + array1[k];
}
else
{
calc = calc + tmp1 - tmp2;
}
Это изменение сбило общее время с ~ 8,8 с до ~ 5 с.
Ответ 4
Просто для удовольствия, я попытался построить это на С# в Visual Studio 2010 и посмотрел на разборку JITed:
else
calc = calc + array1[i] - array2[k];
000000cf mov eax,dword ptr [ebp-10h]
000000d2 add eax,dword ptr [ebp-14h]
000000d5 sub eax,edx
000000d7 mov dword ptr [ebp-10h],eax
Они сделали ряд улучшений для джиттера в 4.0 CLR.
Ответ 5
С# выполняет проверку границ
при запуске расчетной части в небезопасном коде С# она выполняет, а также собственную реализацию?
Ответ 6
Если критический путь производительности приложения полностью состоит из неконтролируемой обработки массива, я бы посоветовал вам не переписывать его на С#.
Но тогда, если ваше приложение уже отлично работает на языке X, я бы посоветовал вам не переписывать его на языке Y.
Чего вы хотите добиться от перезаписи? По крайней мере, серьезно рассмотрите решение смешанного языка, используя уже отлаженный C-код для высокопроизводительных разделов и используя С#, чтобы получить приятный пользовательский интерфейс или удобную интеграцию с последними богатыми библиотеками .NET.
Более длинный ответ на возможно связанную тему.
Ответ 7
Я уверен, что оптимизация для C отличается от С#. Также вы должны ожидать, что по меньшей мере производительность замедляется..NET добавляет еще один слой в приложение с каркасом.
Компромисс - это более быстрое развитие, огромные библиотеки и функции, для (что должно быть) небольшой скорости.