ПрограммированиеФорумОбщее

Сравнение производительноти в умножении матриц .NET (C#) и C++

#0
10:13, 14 ноя 2008

Решил я приглядеться к .NET платформе для интеграции с движком. Раньше использовал тока для оконных приложений и работы с БД. Решил провести тест на производительность. Конечно самую ресурсоемкую часть я выкинул бы в DLL'ку на C++, но всеже решил проверить. Вообщем проверил умножение 10.000.000 матриц 4х4 и что-то меня совсем скорость разочаровала. Если на С++ у меня это занимает в среднем 1100 мсек то под NET это уже 11000 мсек. Плюс для описания матрицы я использовал класс, так как в перегруженных операциях я так понял нельзя использовать ref или out. Но в сторонних исходниках поглядел все используют структуры и в том числе и в XNA. Почему? если передавать не ссылке (описал матрицу структурой как в XNA) работет еще на 20 процентов медленнее. Отсюда у меня вопрос, почему такая большая разница? Почему всеже в XNA выбрали структуру для описания скажем матрици? Неужели просто по тупости? Или я не знаю каких-то настроек компилятора? Ну разница в 10 раз это уж слишком. Единственно ньанс который я могу представить это в том что данные матрицы в классе описаны 2д массивом 4х4 и это требует каких-то тактов для проверки не вышел ли я за пределы массива или еще каких-то но не в 10 же раз.

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

П.С. Для тестов использовал VS2008 Express Edition

П.П.С Просьба говорить только о платформе NET и не вступать в полемику кто круче C++, C# или VB.NET (неверняка данный код последних двух после компиляции в IL получится одинаковый)

#1
11:14, 14 ноя 2008

Код теста покажи.

#2
12:39, 14 ноя 2008

Core 2 Duo 2.2 ghz. Умножение 10.000.000 XNA матриц -

1. Использование перегруженных операций - 1343 милисек.
2. Использование статического метода Multiply(ref Matrix m1, ref Matrix m2, out Matrix result) - 953 мс

#3
13:07, 14 ноя 2008

> Но в сторонних исходниках поглядел все используют структуры и в том числе и в XNA. Почему?

Вообще я не заглядывал в XNA или в какой-то Managed DirectX, и не знаю как там все это "варится".
Но на счет структур, так как это value type то переменные, объявленые например в функции, будут хранится в стеке.
Вот например:

void Foo()
{
    SomeStruct t;
}

void Foo()
{
    SomeClass t = new SomeClass();
}

- в первом случае t будет хранится в стеке,
во втором случае - это ссылка, для неё нужно выделить память, для этого пинается механизм выделения памяти
из managed heap, сразу можно понять что это дело не быстрое...
А потом нужно GC это все будет почистить, плюс передвижение этих блоков памяти (дефрагментирование), в общем целое дело...

А теперь представим себе какую-то не простую мат. функцию, как правило в ней объявляются переменные хранящие какие-то промежуточные
результаты, ну например:

void TestSomeIntersection(...)
{
    Vector temp1, temp2, temp3 ...
    Vector result ...
}

И эта функция вызывается для треугольников меша (например) коих там ~2000, а мешей может быть несколько, например штук ~4-6,
то есть, вызовов функции за кадр (одну итерацию главного цикла) - будет много...
и если этот Vector будет классом, можно себе представить сколько раз дернется менеджер памяти (а потом и GC) для создания
инстансов: 2000*6*(кол-во переменных в функции, напр. 5) = 60000 - и это только для этой функции, все, производительность на нуле
(даже пусть там VS и .NET применит какие-то оптимизации, все равно...).
Совсем другое дело структуры, они (инстансы) хранятся в стеке, и с этим будет намного попроще (побыстрее).

А передавать инстансы структур в методы как параметры - через ref/out, и никакого копирования.

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

#4
17:41, 14 ноя 2008

В общем, и я провел небольшой тест. Я не буду вдаваться в подробности или постить код. Скажу лишь
что код абсолютно идентичен на обоих языках С++ и С# (в обоих случаях для матрицы юзал структуру), функцию умножения писал сам.
Умножение идет самым прямолинейным способом, то есть перебирается каждый элемент матрицы и для него считается значение,
никаких оптимизаций (и конечно же все в одном потоке). Данные матрицы хранятся в отдельных 16-и флоатах.
Метод для .NET выглядит так:
void MulMatr(ref Matrix A, ref Matrix B, out Matrix res);

Для С++:
void MulMatr(const Matrix& A, const Matrix& B, Matrix& res);

Результаты - примерно 4 секунды для обоих вариантов, то есть, одинаково (10,000,000 умножений).

#5
20:15, 14 ноя 2008

Intel Core 2 Duo, 3.2Гц
С++ : в среднем 2200мс
Matlab: в среднем 2150мс
С# не юзаю, извиняй. Почему Matlab здесь привел - он оптимизирован под работу с матрицами и там используются максимально быстрые алгоритмы для работы с ними. Правда увеличение скорости значительно для матриц порядка 10 и выше, т.к. там идет куча проверок на все и вся.

#6
20:30, 14 ноя 2008

а если C++ скомпилять с ключами "/Ox /arch:SSE2" какие времена?

#7
8:40, 15 ноя 2008

Abibok
забавно твои тесты и камень смотрятся на фоне моих. Результаты ты уже видел, вот код -

Matrix first = Matrix.Identity;
Matrix second = Matrix.CreateRotationX(0.5f);
Matrix result = Matrix.Identity;

result = first * second; // jit-им нужные методы
Matrix.Multiply(ref first, ref second, out result);// jit-им нужные методы

DateTime start1 = DateTime.Now;

for (int i = 0; i < 10000000; i++)
{
  result = first * second;
}

TimeSpan elapsed1 = DateTime.Now - start1; // 1343 милисек. 

Matrix test = result; // эт чтоб компилер не наоптимизировал чего лишнего

DateTime start2 = DateTime.Now;

for (int i = 0; i < 10000000; i++)
{
  Matrix.Multiply(ref first, ref second, out result);
}

TimeSpan elapsed2 = DateTime.Now - start2;// 953 милисек. 

test = result;
#8
14:43, 15 ноя 2008
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace MdxTestMatrix {
    class Program {
        static void Main( string[] args ) {
            Matrix a = new Matrix(), b = new Matrix(), r = new Matrix();
            a.M11 =  1; a.M12 =  2; a.M13 =  3; a.M14 =  4;
            a.M21 =  5; a.M22 =  6; a.M23 =  7; a.M24 =  8;
            a.M31 =  9; a.M32 = 10; a.M33 = 11; a.M34 = 12;
            a.M41 = 13; a.M42 = 14; a.M43 = 15; a.M44 = 16;

            b.M11 = 17; b.M12 = 18; b.M13 = 19; b.M14 = 20;
            b.M21 = 21; b.M22 = 22; b.M23 = 23; b.M24 = 24;
            b.M31 = 25; b.M32 = 26; b.M33 = 27; b.M34 = 28;
            b.M41 = 29; b.M42 = 30; b.M43 = 31; b.M44 = 32;

            DateTime utc = DateTime.UtcNow;
            for (int i = 0; i < 100000000; ++i)
                r = a * b;
            TimeSpan diff = DateTime.UtcNow - utc;                       // 1сек = 10 млн тиков
            Console.WriteLine( "{0} {1}", diff.Ticks, r.M11 );  
} } }

Pentium-4 3GHz (2 года тачке, ядро хз какое)
100 млн итераций
примерно такие матрицы и вычисления на всех языках
там, где можно было юзать ref, юзал ref (а этот код для MDX)

MDX:
    139 687 500
    141 875 000

VС++9 D3DXMatrixMultiply (в disasm-e реализован через FPU):
    33 750 000
    32 968 750

VC++9 самописный через ссылки на матрицы (r11=a11*b11+..) ключи: /fp:fast
    65 000 000
    64 843 750
    с ключами: /fp:fast /arch:SSE2 (по генерации кода ничего не изменилось, а время просто.. раз на раз)
    63 593 750
    62 500 000

VC++9 mul4x4 (где-то здесь на форуме у кого-то была реализация через SSE mul4x4):
    22 968 750
    23 125 000

SlimDX (september 2008, написан на C++\CLI):
    175 937 500
    178 281 250

XNA2-Matrix, а также XNA3-Matrix, а также самописный mul в простой консоли:
    ~92 000 000

#9
18:16, 15 ноя 2008

Мда, намудрил я чегой-то...

void CMyApp::ProcessMultiply()
{

  D3DXMATRIXA16 matFirst, matSecond, matResult;
  D3DXMatrixIdentity( &matFirst );
  D3DXMatrixIdentity( &matSecond );
  D3DXMatrixIdentity( &matResult );

  double StartTime = GetTimer();

  for ( int i = 0; i < 10000000; i++ )
  {
    D3DXMatrixMultiply( &matResult, &matFirst, &matSecond );
  }

  double EndTime = GetTimer();

  dResult = EndTime - StartTime;

  _sntprintf( tResult, 16, "%.2f", dResult );

  return;
}

офигеть...
248мс...
а в прошлый раз умножение вручную писал...

#10
0:50, 16 ноя 2008

да если честно у меня примерно одинакого вышло:
XNA - http://code.hash.su/415 - 1500 ms
C++ - http://code.hash.su/416 - 238 ms

#11
1:48, 17 ноя 2008

Причину нашел. Работа с массивами меня убила. Вообщем если использовать массивы в среднем 11000 мсек. Если массивы заменить на переменные примерно 1600 мсек. Я понимаю постоянные проверки на то чтоб не выйти за приделы массива но блин такая разница!:(

#12
3:17, 17 ноя 2008

Заоптимизировал до 1300 мсек. (Cel 2.8 ГГц)

#13
3:30, 17 ноя 2008

Еще провел тест если функцию перемножения импортировать из сишной дллки. Так же 10.000.000 матриц

CPPMult(ref Matrix4 m1, ref Matrix4 m2, ref Matrix4 r); - 2900 мсек

Mult(ref Matrix4 m1, ref Matrix4 m2, ref Matrix4 r)
{
    CPPMult(ref m1, ref m2, ref r);                                -2600 мсек
}

Тоесть во втором варианте над этой функцией для эксперимента сделал обертку в шарпе. 300 мсек, тоесть разница примерно 10.5 процентов. Помоему многовато для простого вызова функции с тремя аргументами (по ссылке)

#14
3:39, 17 ноя 2008

Блин да и вообще скорость вызова функции из длл удручает:(

ПрограммированиеФорумОбщее

Тема в архиве.