Ускорить математический код в C# путем записи C dll?

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

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

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

12
задан Projectile Fish 27 May 2010 в 03:20
поделиться

12 ответов

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

2
ответ дан 2 December 2019 в 05:14
поделиться

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

7
ответ дан 2 December 2019 в 05:14
поделиться

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

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

1
ответ дан 2 December 2019 в 05:14
поделиться

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

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

1
ответ дан 2 December 2019 в 05:14
поделиться

Сильно сомневаюсь. Внутренний цикл, который обрабатывает примитивные типы и не выделяет память, будет чертовски эффективным в C #. Собственный байт-код будет сгенерирован один раз из IL, поэтому особых управляемых накладных расходов быть не должно.

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

0
ответ дан 2 December 2019 в 05:14
поделиться

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

0
ответ дан 2 December 2019 в 05:14
поделиться

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

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

3
ответ дан 2 December 2019 в 05:14
поделиться

Вы можете попробовать использовать 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
ответ дан 2 December 2019 в 05:14
поделиться

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

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

2
ответ дан 2 December 2019 в 05:14
поделиться

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

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

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

2
ответ дан 2 December 2019 в 05:14
поделиться

Обновлено:

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

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; 
    } 
} 
8
ответ дан 2 December 2019 в 05:14
поделиться

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

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

0
ответ дан 2 December 2019 в 05:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: