Ускорить математический код на С#, написав 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 и связать его с С#?
EDIT: Я обновил код, чтобы показать, как сгенерированы команды s и c. Также внутренний цикл фактически идет от 0 до i, хотя, вероятно, это не имеет большого значения для вопроса
EDIT2: Я реализовал алгоритм в VС++ и связал его с С# через dll и увидел увеличение скорости на 28% по сравнению с С# при включении всех оптимизаций. Аргумент для включения SSE2 работает особенно хорошо. Компиляция с MinGW и gcc4.4 давала только 15% -ное повышение скорости. Просто попробовал компилятор Intel и увидел повышение скорости на 49% для этого кода.
Ответы
Ответ 1
В то время как большинство других ответов, как правило, указывают на то, что вы смотрите на решения С#, большинство пропускает точку: код C для этого метода будет быстрее, если вы используете хороший оптимизирующий компилятор (я бы предложил Intel, отлично работает для этого вид кода).
Компилятор также сохранит немного работы из JIT и даст намного лучший скомпилированный вывод (даже компилятор MSVC может генерировать инструкции SSE2). Оценки массива по умолчанию не будут проверяться, вероятно, будет развертка цикла и - в целом - вы, вероятно, увидите значительное повышение производительности.
Как было правильно указано, вызов в собственный код может иметь немного накладных расходов; это должно, однако, быть незначительным по сравнению с ускорением, если длина 1 достаточно велика.
Вы можете сохранить этот код на С#, но, пожалуйста, помните, что по сравнению с несколькими компиляторами C CLR (как и все другие виртуальные машины, которые я знаю) мало помогает оптимизировать сгенерированный код.
Ответ 2
Обновлено:
Что произойдет, если вы напишете внутренний цикл, чтобы учесть локальность ссылки:
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;
}
}
Ответ 3
Используйте блок unsafe
и указатели для индексации в массив omega
. Это позволит устранить накладные расходы на проверку диапазона и может стать значительной победой, если вы выполняете достаточно доступа. Также много времени можно потратить на функции GetS()
и GetC()
, для которых вы не предоставили источник.
Ответ 4
Очень маловероятно, что запуск этого на родном C/С++ "автоматически" ускорит работу. Если вы хорошо разбираетесь в SIMD (и length1
и length2
достаточно велики, что вызов P/Invoke не имеет значения), возможно, вы могли бы что-то сделать.
Но единственный способ узнать наверняка - попробовать его и профиль.
Ответ 5
Вы можете попытаться использовать Mono.Simd для более эффективного использования CPU.
http://tirania.org/blog/archive/2008/Nov-03.html
Как сказано, многое можно получить на С#, вручную извлекая повторяющиеся утверждения из циклов.
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;
}
}
Ответ 6
Просто использование C или С++ не даст вам большого увеличения скорости, если оно есть. У вас также есть накладные расходы на вызов в подпрограмме C, но не огромное влияние, если вы не делаете это много раз в цикле.
Сначала попробуйте другие вещи на С#.
Если переменные являются поплавками, а не удваиваются, это замедляет вычисления.
Также, как сказал Радж, использование параллельного программирования даст вам большой импульс скорости.
Ответ 7
.net-взаимодействие с неуправляемым кодом очень медленное.
Вы можете использовать все преимущества неуправляемой памяти, просто используя систему api для распределения неуправляемой памяти.
Вы можете вызвать VirtualAlloc для выделения страниц памяти, а затем вызвать VirtualProtect, чтобы связать их напрямую с ОЗУ без обмена.
Этот подход позволяет выполнять вычисления на большом количестве данных по крайней мере в 3 раза быстрее, чем вы могли бы сделать это в управляемой памяти.
Ответ 8
Пробовали ли вы параллельное программирование?
http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.aspx
Ответ 9
Для простой 64-разрядной арифметики в Java я видел около 33% ускорения (от 23 нс до 16 нс) при переносе на C и возиться с флагами оптимизации (-fprofile-generate, -fprofile-use). Это может стоить того.
Другое дело, что omega [i] [j] делает вид, что omega - это массив массивов - вы можете получить лучшую производительность с двухмерным массивом (я думаю, что синтаксис - это что-то вроде omega [i, j], но я забыл, как вы его выделяете).
Ответ 10
Очень сомневаюсь в этом. Внутренний цикл, который обрабатывает примитивные типы и не выделяет память, будет очень эффективен в С#. Нативный байт-код будет генерироваться один раз от IL, поэтому не должно быть много управляемых служебных данных.
Учитывая, что это довольно маленькая функция, вы можете профилировать оба и посмотреть, есть ли разница.
Ответ 11
Также рассмотрите стоимость сортировки данных между управляемыми и нативными вызовами. С# имеет довольно быструю скорость выполнения. Вы также можете NGEN сборку для генерации собственных изображений сборки для более быстрого выполнения.
Ответ 12
Я понятия не имею, насколько это практично, но подумали ли вы о попытке запустить это на GPU? Возможно, используя что-то вроде OpenCL или DirectCompute?
Зависимости и квадратный корень могут вас убьют, но в наши дни графические процессоры имеют на порядок более высокую производительность с плавающей запятой, чем процессоры.