Редактировать: Я извиняюсь перед всеми. Я использовал термин «зубчатый массив», когда на самом деле хотел сказать «многомерный массив» (как видно из моего примера ниже). Я прошу прощения за использование неправильного имени. На самом деле я обнаружил, что зубчатые массивы быстрее, чем многомерные! Я добавил свои измерения для зубчатых массивов.
Я пытался использовать многомерный массив с зазубринами сегодня, когда заметил, что его производительность не соответствует ожиданиям. Использование одномерного массива и ручное вычисление индексов было намного быстрее (почти в два раза), чем использование двумерного массива. Я написал тест, используя массивы 1024*1024
(инициализированные случайными значениями), для 1000 итераций, и на моем компьютере я получил следующие результаты:
sum(double[], int): 2738 ms (100%)
sum(double[,]): 5019 ms (183%)
sum(double[][]): 2540 ms ( 93%)
Это мой тестовый код:
public static double sum(double[] d, int l1) {
// assuming the array is rectangular
double sum = 0;
int l2 = d.Length / l1;
for (int i = 0; i < l1; ++i)
for (int j = 0; j < l2; ++j)
sum += d[i * l2 + j];
return sum;
}
public static double sum(double[,] d) {
double sum = 0;
int l1 = d.GetLength(0);
int l2 = d.GetLength(1);
for (int i = 0; i < l1; ++i)
for (int j = 0; j < l2; ++j)
sum += d[i, j];
return sum;
}
public static double sum(double[][] d) {
double sum = 0;
for (int i = 0; i < d.Length; ++i)
for (int j = 0; j < d[i].Length; ++j)
sum += d[i][j];
return sum;
}
public static void Main() {
Random random = new Random();
const int l1 = 1024, l2 = 1024;
double[ ] d1 = new double[l1 * l2];
double[,] d2 = new double[l1 , l2];
double[][] d3 = new double[l1][];
for (int i = 0; i < l1; ++i) {
d3[i] = new double[l2];
for (int j = 0; j < l2; ++j)
d3[i][j] = d2[i, j] = d1[i * l2 + j] = random.NextDouble();
}
//
const int iterations = 1000;
TestTime(sum, d1, l1, iterations);
TestTime(sum, d2, iterations);
TestTime(sum, d3, iterations);
}
Дальнейшие исследования показали, что IL для второго метода на 23% больше, чем для первого метода. (Размер кода 68 против 52.) Это в основном из-за звонков на System.Array::GetLength(int)
. Компилятор также генерирует вызовы Array::Get
для многомерного массива jagged , тогда как для простого массива он просто вызывает ldelem
.
Так что мне интересно, почему доступ через многомерные массивы медленнее, чем обычные массивы? Я бы предположил, что компилятор (или JIT) будет делать нечто похожее на то, что я делал в моем первом методе, но на самом деле это не так.
Не могли бы вы помочь мне понять, почему это происходит именно так?
Обновление: В соответствии с предложением Хенка Холтермана, вот реализация TestTime
:
public static void TestTime<T, TR>(Func<T, TR> action, T obj,
int iterations)
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; ++i)
action(obj);
Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
}
public static void TestTime<T1, T2, TR>(Func<T1, T2, TR> action, T1 obj1,
T2 obj2, int iterations)
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; ++i)
action(obj1, obj2);
Console.WriteLine(action.Method.Name + " took " + stopwatch.Elapsed);
}