Ускорение объявления Math.NET для симметричных матриц

Я создаю довольно большую матрицу с помощью этого конструктора:

var M = Matrix<double>.Build.Dense(N, N, (i, j) => SomeRoutine(i, j));

N большой, а SomeRoutine медленный, поэтому я пытаюсь оптимизировать некоторые моменты. Я заметил, что для любого i, j выполняется SomeRoutine(i, j) == SomeRoutine(j, i), т.е. M симметрично, поэтому можно определить только верхний (или нижний) треугольник, уменьшая количество обращений к SomeRoutine с N^2 до N(N+1)/2, что приятно.

Вот мой подход к этой оптимизации.

var arr = new double[N, N];
for (int i = 0; i < N; i++)
{
  for (int j = 0; j < N; j++)
  {
    arr[i, j] = (i <= j) ? SomeRoutine(i, j) : arr[j, i];
  }
}
var M = Matrix<double>.Build.DenseOfArray(arr);

Мне не кажется очень элегантным. Можно ли как-то приблизиться к той же идее оптимизации, сохранив объявление в стиле лямбда? Или, может быть, я должен написать какую-то обертку, которая будет маскировать for циклов?


person tonytonov    schedule 08.06.2015    source источник
comment
Если вы хотите использовать лямбду, у вас может быть функция-оболочка для SomeRoutine, которая использует кеш и проверяет как i, j, так и j, i в кеше или устанавливает их после вычисления.   -  person AlexDev    schedule 08.06.2015
comment
@AlexDev Буду признателен, если вы покажете, как реализовать вашу идею.   -  person tonytonov    schedule 09.06.2015


Ответы (2)


Я думаю, что было бы лучше разделить это на два: сначала вычислить нижний треугольник, затем в отдельном цикле присвоить значения из нижнего треугольника верхнему треугольнику. Вероятно, он будет более производительным. Вы также можете использовать Parallel.For для внешнего цикла (во время вычислений), чтобы ускорить его.

Что-то вроде этого:

public static void Main()
{
    var test = CreateLowerMatrix(5);
    CopyLowerToUpperMatrix(test, 5);
}

public static double[,] CreateLowerMatrix(int n)
{
    var result = new double[n,n];

    Parallel.For(0, n, r => {
        for (int c = r; c < n; ++c)
            result[r, c] = calc(r, c); });

    return result;
}

public static void CopyLowerToUpperMatrix(double[,] matrix, int n)
{
    for (int r = 1; r < n; ++r)
        for (int c = r + 1; c < n; ++c)
            matrix[c, r] = matrix[r, c];
}

public static double calc(int x, int y)
{
    return x * y;
}

Я сомневаюсь, что было бы полезно распараллелить CopyLowerToUpperMatrix().

person Matthew Watson    schedule 08.06.2015

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

Я бы рекомендовал построить матрицу из массива, как в вопросе или других ответах, но если вы решите использовать это, просто убедитесь, что он хорошо прокомментирован.

var cache = new double?[N, N];
Func<int, int, double> WrapAndCacheSomeRoutine = (i, j) => 
    cache[i,j] ?? cache[j,i] ?? (cache[i,j] = SomeRoutine(i, j));
var M = Matrix<double>.Build.Dense(N, N, WrapAndCacheSomeRoutine);
person AlexDev    schedule 09.06.2015
comment
Спасибо, ценю это. - person tonytonov; 10.06.2015