Ускорить математический код в C #, написав C DLL? - PullRequest
12 голосов
/ 25 мая 2010

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

for (int i = 0; i < length1; i++)
{
    double aa = 0;
    for(int h = 0; h < 10; h++)
    {
       aa += omega[i][outsideGeneratedAddress[h]];
    }

    double alphaOld = alpha;
    alpha = Math.Sqrt(alpha * alpha + aa * aa);

    s = -aa / alpha;
    c = alphaOld / alpha;

    for(int j = 0; j <= i; j++)
    {
        double oldU = u[j];
        u[j] = c * oldU + s * omega[i][j];
        omega[i][j] = c * omega[i][j] - s * oldU;
    }
}

Этот цикл занимает большую часть моего времени обработки и является узким местом.

Могу ли я увидеть какие-либо улучшения скорости, если переписать этот цикл в C и подключиться к нему из C #?

РЕДАКТИРОВАТЬ: Я обновил код, чтобы показать, как генерируются s и c. Кроме того, внутренний цикл на самом деле идет от 0 до i, хотя это, вероятно, не имеет большого значения для вопроса

EDIT2: Я реализовал алгоритм в VC ++ и связал его с C # через dll и увидел увеличение скорости на 28% по сравнению с C #, когда все оптимизации включены. Аргумент для включения SSE2 работает особенно хорошо. Компиляция с MinGW и gcc4.4 только дала прирост скорости на 15%. Только что попробовал компилятор Intel и увидел увеличение скорости этого кода на 49%.

Ответы [ 12 ]

8 голосов
/ 25 мая 2010

Обновлен:

Что произойдет, если вы напишите внутренний цикл, чтобы учесть местность ссылок:

for (int i = 0; i < length1; i++) 
{ 
    s = GetS(i); 
    c = GetC(i); 
    double[] omegaTemp = omega[i]; 

    for(int j = 0; j < length2; j++) 
    { 
        double oldU = u[j]; 
        u[j] = c * oldU + s * omegaTemp[j]; 
        omegaTemp[j] = c * omegaTemp[j] - s * oldU; 
    } 
} 
7 голосов
/ 25 мая 2010

Используйте блок unsafe и указатели для индексации в вашем массиве omega. Это снимет накладные расходы на проверку дальности и может стать значительным выигрышем, если вы сделаете достаточно обращений. Также может быть потрачено много времени на функции GetS() и GetC(), для которых вы не указали источник.

3 голосов
/ 25 мая 2010

Вы можете попытаться использовать Mono.Simd, чтобы использовать процессор более оптимально.

http://tirania.org/blog/archive/2008/Nov-03.html

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

var outsideAddr0 = outsideGeneratedAddress[0];
var outsideAddr1 = outsideGeneratedAddress[1];
var outsideAddr2 = outsideGeneratedAddress[2];
var outsideAddr3 = outsideGeneratedAddress[3];
var outsideAddr4 = outsideGeneratedAddress[4];
var outsideAddr5 = outsideGeneratedAddress[5];
var outsideAddr6 = outsideGeneratedAddress[6];
var outsideAddr7 = outsideGeneratedAddress[7];
var outsideAddr8 = outsideGeneratedAddress[8];
var outsideAddr9 = outsideGeneratedAddress[9];
for (int i = 0; i < length1; i++)
{
  var omegaAtI = omega[i];
  double aa = 
   omegaAtI[outsideAddr0]
   + omegaAtI[outsideAddr1]
   + omegaAtI[outsideAddr2]
   + omegaAtI[outsideAddr3]
   + omegaAtI[outsideAddr4]
   + omegaAtI[outsideAddr5]
   + omegaAtI[outsideAddr6]
   + omegaAtI[outsideAddr7]
   + omegaAtI[outsideAddr8]
   + omegaAtI[outsideAddr9];

  double alphaOld = alpha;
  alpha = Math.Sqrt(alpha * alpha + aa * aa);

  var s = -aa / alpha;
  var c = alphaOld / alpha;

  for(int j = 0; j <= i; j++)
  {
    double oldU = u[j];
    var omegaAtIJ = omegaAtI[j];
    u[j] = c * oldU + s * omegaAtIJ;
    omegaAtI[j] = c * omegaAtIJ  - s * oldU;
  }
}
3 голосов
/ 25 мая 2010

Маловероятно, что запуск этого на родном C / C ++ "автоматически" ускорит процесс. Если вы хорошо разбираетесь с SIMD (а length1 и length2 достаточно велики, чтобы вызов P / Invoke не был значительным), то возможно вы могли бы что-то сделать.

Но единственный способ узнать наверняка - это попробовать и профиль.

2 голосов
/ 25 мая 2010

В то время как большинство других ответов, как правило, предлагают взглянуть на решения C #, большинство упускают из виду: код на C для этого метода будет быстрее, при условии, что вы используете хороший оптимизирующий компилятор (я бы посоветовал Intel, отлично подходит для этого вид кода).
Компилятор также сэкономит немного работы из JIT и даст гораздо лучший скомпилированный вывод (даже компилятор MSVC может генерировать инструкции SSE2). Границы массива не будут проверяться по умолчанию, возможно, будет развернуто несколько циклов, и - в целом - вы, вероятно, увидите значительное повышение производительности.
Как было правильно указано, обращение к нативному коду может иметь некоторые накладные расходы; однако это должно быть незначительным по сравнению с ускорением, если length1 достаточно велико.
Вы можете хранить этот код на C #, но помните, что по сравнению с несколькими компиляторами C CLR (как и все другие известные мне виртуальные машины) мало что делает для оптимизации сгенерированного кода.

2 голосов
/ 25 мая 2010

.net взаимодействие с неуправляемым кодом очень медленное. Вы можете использовать все преимущества неуправляемой памяти, просто используя системные API для выделения неуправляемой памяти.

Вы можете вызвать VirtualAlloc для выделения страниц памяти, а затем вызвать VirtualProtect, чтобы закрепить их непосредственно в ОЗУ без замены.

Этот подход позволяет выполнять вычисления для большого объема данных как минимум в 3 раза быстрее, чем вы могли бы сделать это в управляемой памяти.

2 голосов
/ 25 мая 2010

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

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

1 голос
/ 25 мая 2010

Для простой 64-битной арифметики в Java я видел ускорение примерно на 33% (от 23 нс до 16 нс) при переносе его на C и переключении с флагами оптимизации (-fprofile-generate, -fprofile-use). Это может стоить того.

Другое дело, что омега [i] [j] делает вид, что омега - это массив массивов - вы можете получить лучшую производительность с двумерным массивом (я думаю, что синтаксис похож на омега [i, j] , но я забыл, как вы выделяете один).

1 голос
/ 25 мая 2010

Вы пробовали параллельное программирование?

http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.aspx

0 голосов
/ 27 мая 2010

Понятия не имею, насколько это практично, но вы задумывались о том, чтобы попытаться запустить это на GPU? Возможно, использовать что-то вроде OpenCL или DirectCompute?

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

...