Почему разница в производительности между C # (немного медленнее) и Win32 / C? - PullRequest
16 голосов
/ 29 июня 2009

Мы пытаемся перенести приложение, критичное к производительности, на .Net и обнаруживаем, что версия c # на 30–100% медленнее, чем Win32 / C, в зависимости от процессора (разница больше отмечена на мобильном процессоре T7200). У меня есть очень простой пример кода, который демонстрирует это. Для краткости я просто покажу версию C - 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 для «другого», то имеем:

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

(это отладка, но потерпите меня)

Разборка для оптимизированной версии c # с использованием отладчика 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 вопроса действительно:

  1. Я смотрю на правильную разборку для 2 программ или инструменты вводят меня в заблуждение?

  2. Если разница в количестве сгенерированных инструкций не является причиной разницы, что это?

  3. Что мы можем с этим поделать, кроме как сохранить весь наш критичный для производительности код в собственной DLL.

Заранее спасибо Стив

PS Недавно я получил приглашение на совместный семинар MS / Intel под названием что-то вроде «Создание критичных для производительности собственных приложений» Хмм ...

Ответы [ 7 ]

18 голосов
/ 29 июня 2009

Я полагаю, что вашей основной проблемой в этом коде будет проверка границ ваших массивов.

Если вы переключитесь на использование небезопасного кода в C # и используете математические указатели, вы сможете добиться того же (или потенциально более быстрого) кода.

Этот же вопрос ранее подробно обсуждался в этом вопросе .

13 голосов
/ 29 июня 2009

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

Я полагаю, что JITer может распознавать шаблоны, подобные циклам, которые переходят в массив. Длина и позволяет избежать проверки границ, но не похоже, что ваш код может использовать это.

6 голосов
/ 29 июня 2009

Как уже говорили другие, одним из аспектов является проверка границ. Есть также некоторая избыточность в вашем коде с точки зрения доступа к массиву. Мне удалось несколько улучшить производительность, изменив внутренний блок на:

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 голосов
/ 30 июня 2009

Просто для удовольствия, я попытался построить это в C # в 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.

2 голосов
/ 29 июня 2009

C # выполняет проверку границ

при выполнении расчетной части в небезопасном коде C # она работает так же, как и собственная реализация?

1 голос
/ 29 июня 2009

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

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

Чего вы хотите добиться от переписывания? По крайней мере, серьезно подумайте о решении на разных языках, используя уже отлаженный код C для высокопроизводительных разделов и используя C #, чтобы получить приятный пользовательский интерфейс или удобную интеграцию с новейшими библиотеками .NET.

Более длинный ответ на возможно связанную тему.

0 голосов
/ 29 июня 2009

Я уверен, что оптимизация для C отличается от C #. Также вы должны ожидать хотя бы небольшого снижения производительности. .NET добавляет еще один слой к приложению со структурой.

Компромисс - более быстрое развитие, огромные библиотеки и функции для (что должно быть) небольшого количества скорости.

...