Мне вложили очень большое для цикла, в котором некоторое умножение и дополнения выполняются на числах с плавающей точкой.
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%-е повышение скорости для этого кода.
В то время как большинство других ответов склонны предлагать вам изучить решения C #, большинство упускает один момент: код C для этого метода будет быстрее, при условии, что вы используете хороший оптимизирующий компилятор (я бы посоветовал Intel, отлично подходит для этого вид кода).
Компилятор также сэкономит немного времени на JIT и даст гораздо лучший скомпилированный вывод (даже компилятор MSVC может генерировать инструкции SSE2). Границы массива по умолчанию не проверяются, вероятно, произойдет разворачивание цикла и - в целом - вы, вероятно, увидите значительный прирост производительности.
Как было правильно указано, вызов собственного кода может иметь небольшие накладные расходы; это, однако, должно быть незначительным по сравнению с ускорением, если length1 достаточно велик.
Вы можете сохранить этот код на C #, но помните, что по сравнению с несколькими компиляторами C CLR (как и все другие известные мне виртуальные машины) мало что делает для оптимизации сгенерированного кода.
Используйте небезопасный
блок и указатели для индексации в массиве omega
. Это устранит накладные расходы на проверку диапазона и может быть значительным выигрышем, если вы сделаете достаточное количество обращений. Много времени также может быть потрачено на ваши функции GetS ()
и GetC ()
, исходный код которых вы не предоставили.
Вы пробовали параллельное программирование?
http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.aspx
Для простой 64-битной арифметики на Java я увидел примерно 33% ускорение (23 нс до 16 нс) при переносе на C и возиться с флагами оптимизации (-fprofile-generate, -fprofile-use). Возможно, оно того стоит.
Другое дело, что omega[i][j] создает впечатление, что omega - это массив массивов - вы можете получить лучшую производительность с двумерным массивом (я думаю, синтаксис что-то вроде omega[i,j], но я забыл, как вы его выделяете).
Сильно сомневаюсь. Внутренний цикл, который обрабатывает примитивные типы и не выделяет память, будет чертовски эффективным в C #. Собственный байт-код будет сгенерирован один раз из IL, поэтому особых управляемых накладных расходов быть не должно.
Учитывая, что это довольно небольшая функция, вы можете профилировать обе и посмотреть, есть ли разница.
Также учитывайте стоимость распределения данных между управляемыми и собственными вызовами. C # имеет довольно быструю скорость выполнения. Вы также можете NGEN для сборки для создания образов сборки в машинном коде для более быстрого выполнения.
Маловероятно, что запуск этого на родном C / C ++ "автоматически" ускорит процесс. Если вы хорошо разбираетесь в SIMD (а length1
и length2
достаточно велики, чтобы вызов P / Invoke не имел значения), тогда , возможно, вы могли бы что-то сделать.
Но единственный способ узнать наверняка - это попробовать и составить профиль.
Вы можете попробовать использовать 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;
}
}
Простое использование C или C ++ не даст вам значительного увеличения скорости, вам также потребуется провести оптимизацию. У вас также есть накладные расходы на вызов подпрограммы C, что не оказывает большого влияния, если вы не делаете это много раз в цикле.
Сначала попробуйте другие вещи на C #. Если переменные являются плавающими, а не удваиваются, это замедляет вычисления. Также, как сказал Радж, использование параллельного программирования даст вам большой прирост скорости.
.net interop с неуправляемым кодом очень медленный. Вы можете использовать все преимущества неуправляемой памяти, просто используя системный api для выделения неуправляемой памяти.
Вы можете вызвать VirtualAlloc для выделения страниц памяти и затем вызвать VirtualProtect, чтобы закрепить их непосредственно в оперативной памяти без подкачки.
Такой подход позволяет выполнять вычисления над большим объемом данных по крайней мере в 3 раза быстрее, чем это можно сделать в управляемой памяти.
Обновлено:
Что произойдет, если вы напишете внутренний цикл для учета местоположения ссылки:
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;
}
}
Я понятия не имею, насколько это практично, но не думали ли вы попробовать запустить это на графическом процессоре? Возможно, используя что-то вроде OpenCL или DirectCompute?
Зависимости и квадратный корень могут вас убить, но графические процессоры в наши дни имеют на порядок большую производительность с плавающей запятой, чем процессоры.