2015-02-14 3 views
1

У меня есть многомерный массив double[,] results, где каждый столбец представляет собой ценовой временной ряд для определенных предметов (например, автомобиль, дом ...). Я хотел бы рассчитать логарифмические данные для каждого временного ряда как Log (price_t/price_t1) Где t> t1. Поэтому я буду генерировать новый временной цикл log-return для каждого столбца double[,] results. Как это можно сделать на C# эффективным способом? Объем данных велик, и я пытался решением, как:Как эффективно вычислять log-return

for(int col = 1; col <= C; col++) 
{ 
    for(int row = 1; row <= R; row++) 
    { 
     ret = Math.Log(results[row+1;col]/ results[row;col]) 
    } 
} 

где С и Р является число столбцов и строки в double[,] results. Этот solutioh работает довольно медленно и кажется очень неэффективным. Любое предложение, как быстрее выполнить аналогичный расчет?

Я видел, что в таких языках, как MATLAB, можно векторизовать код и просто делить исходную матрицу на другую, которая просто отстает от одного элемента. Затем возьмем лог всей матрицы, которая получается из деления. Это возможно в C# и как?

ответ

2

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

Double[,] ComputeLogReturns(Double[,] data) { 
    var rows = data.GetLength(0); 
    var columns = data.GetLength(1); 
    var result = new Double[rows - 1, columns]; 
    for (var row = 0; row < rows - 1; row += 1) 
    for (var column = 0; column < columns; column += 1) 
     result[row, column] = Math.Log(data[row + 1, column]/data[row, column]); 
    return result; 
} 

Я протестированный эту функцию с входным массивом 1000 х 1000 значений. На моем компьютере время выполнения 100 вызовов составляло около 3 секунд.

Поскольку тело цикла может выполняться параллельно я тогда переписал функцию, чтобы использовать Parallel.For:

Double[,] ComputeLogReturnsParallel(Double[,] data) { 
    var rows = data.GetLength(0); 
    var columns = data.GetLength(1); 
    var result = new Double[rows - 1, columns]; 
    Parallel.For(0, rows - 1, row => { 
    for (var column = 0; column < columns; column += 1) 
     result[row, column] = Math.Log(data[row + 1, column]/data[row, column]); 
    }); 
    return result; 
} 

На моем компьютере с 4 ядрами (8 логических ядер) выполнение 100 вызовов занимает около 0,9 секунд , Это немного больше, чем в 3 раза быстрее, что указывает на то, что только физические, а не логические ядра могут вычислять логарифм.

Современные процессоры x86 имеют специальные инструкции, называемые SSE, которые позволяют вам векторизовать определенные вычисления. Я бы ожидал, что MATLAB использует эти инструкции, и это может объяснить, почему вы получаете гораздо лучшую производительность в MATLAB по сравнению с вашим собственным кодом C#.

Для проверки SSE я попробовал Yeppp!, который имеет привязки к C#. Библиотека доступна в NuGet как предварительная публикация и имеет функцию логарифма. инструкции SSE работает только на одномерных массивов, так что я переписал исходную функцию:

Double[] ComputeLogReturns(Double[] data, Int32 rows, Int32 columns) { 
    var result = new Double[(rows - 1)*columns]; 
    for (var row = 0; row < rows - 1; row += 1) 
    for (var column = 0; column < columns; column += 1) 
     result[row*columns + column] = Math.Log(data[(row + 1)*columns + column]/data[row*columns + column]); 
    return result; 
} 

С тем же входом и 100 итераций, время выполнения в настоящее время, кажется, немного меньше, чем за 3 секунды, указывающий, что один одномерный массив может (но логически это не должно, если только это дополнительная проверка аргументов, которая влияет на время выполнения).

Использование Yeppp! функция будет:

Double[] ComputeLogReturnsSse(Double[] data, Int32 rows, Int32 columns) { 
    var quotient = new Double[(rows - 1)*columns]; 
    for (var row = 0; row < rows - 1; row += 1) 
    for (var column = 0; column < columns; column += 1) 
     quotient[row*columns + column] = data[(row + 1)*columns + column]/data[row*columns + column]; 
    var result = new Double[(rows - 1)*columns]; 
    Yeppp.Math.Log_V64f_V64f(quotient, 0, result, 0, quotient.Length); 
    return result; 
} 

Мне не удалось найти функцию, проделать векторизованное разделение с помощью Yeppp! поэтому деление выполняется с использованием «нормального» деления. Тем не менее, я все еще ожидаю, что логарифм станет самой дорогой операцией. Первоначально производительность была ужасной, и 100 итераций занимали 17 секунд, но потом я заметил, что в Yeppp возникла проблема! о плохой производительности при работе как 32-битный процесс. Переключение на 64-разрядную улучшенную производительность значительно увеличилось примерно на 1,3 секунды.Избавление от двух распределений массива внутри функции (которое повторялось 100 раз) опустило выполнение примерно до 0,7 секунды, что быстрее, чем параллельная реализация. Используя Parallel.For, чтобы уменьшить время выполнения умножения примерно на 0,4 секунды. Если Yeppp! имел способ выполнить деление (которое имеет SSE), вы можете получить еще более низкое время выполнения, что может привести к десятикратному увеличению скорости.

Основываясь на моих экспериментах с SSE, вы должны быть способны добиться значительных улучшений производительности. Тем не менее, вы должны, вероятно, обратить внимание на точность, если это имеет значение. Функция журнала SSE может дать несколько иные результаты по сравнению с реализацией .NET.

+0

Благодарим за ответ. Так вы думаете, что с Parallel.For я по-прежнему получаю худшую производительность, чем векторизованное вычисление? – mickG

+0

@mickG: Я угадываю здесь, но я ожидал бы, что SSE будет намного быстрее. Единственная проблема заключается в том, что вы должны реализовать логарифм, используя SSE самостоятельно, и вам придется делать компромисс между временем выполнения и точностью. Я считаю, что у Intel есть библиотека, которую вы можете использовать, но я не знаю о ее доступности и о том, насколько сложно использовать ее с C#. –

+0

Я вижу, поэтому нет прямого способа улучшить расчет. Я попробую Parallel.For, похоже, это отличное предложение. Параллельно. Стоит ли использовать в любых условиях руду, существует минимальный расчет, по которому накладные расходы компенсируются параллельным запуском? Я помню, что в R при параллельном запуске для небольших циклов tgere на самом деле была наихудшей по сравнению с непараллельным исполнением. Параллельное выполнение выигрывает от использования только больших циклов. Есть ли что-то подобное для рассмотрения в C# или я всегда могу использовать Parallel.For? – mickG

Смежные вопросы