Инкапсулированный 2-мерный массив по сравнению с простой версией - скорость доступа

0

Этот вопрос является продолжением этого: 2-мерный массив на куче, какая версия быстрее?

Я определил что-то вроде этого:

class Array
{
    double *data;
    int X;
    int Y;

public:
    Array(int X, int Y, double init = 0) : X(X), Y(Y)
    {
        data = new double [X*Y];
        for (int i=0; i<X*Y; i++)
            data[i] = init;
    }
    ~Array() { delete[] data; }
    double *operator[] (int x) { return (data+x*Y); }
};

Я хочу иметь преимущество скорости непрерывного массива с удобочитаемостью двумерного. Я думал, что class Array сделает это, с

Array arr(1000,1000);
arr[x][y] = n;

(почти) так же быстро, как и обычная версия

double *arr = new double [1000*1000];
arr[x*1000+y] = n;

так как operator[] определен в inline.

Но простая версия выполняется намного быстрее, а инкапсулированный - немного быстрее, чем истинный двумерный double **arr;...; arr[x][y] = n; double **arr;...; arr[x][y] = n; не совсем верно, см. Edit2

Это нормально? Я собираюсь на VC++ 2010 с оптимизацией.

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

Изменить:

Я прочитал комментарии, что мой class Array делает 2 поиска, и я должен использовать прямой поиск 1 и ссылку возврата в double. Я пробовал это, и скорость не улучшается, это точно так же.

И я действительно не понимаю, почему мой класс выполняет 2 поиска:

Array arr(1000,1000);
arr[x][y] = n;

следует включить в:

(arr.data+x*arr.Y)[y] = n;

и далее:

*((arr.data+x*arr.Y)+y) = n;

что точно такое же:

arr.data[x*arr.Y+y] = n; // the proposed 1 lookup access

Я ошибаюсь?

Редактировать2:

Я снова приурочивался и заметил, что double **arr; arr[x][y] = n; double **arr; arr[x][y] = n; решение имеет разное время от 1:47 мин до 2:10 мин - в случайном стиле.

Все остальные решения:

  1. инкапсулирующий class Array как указано выше
  2. с double &operator() (int x, int y) как предлагается
  3. с простым double *arr; arr[x*Y+y] = n; double *arr; arr[x*Y+y] = n;

на самом деле такие же быстрые около 1:44 мин и всегда постоянны.

  • 0
    «... немного быстрее, чем истинный двухмерный»: вы оцениваете это как раз наоборот. Динамический массив указателей на динамический массив (ы) данных - это не что иное, как «истинный 2-мерный массив». Единственное, что у него общего с реальным двумерным массивом, это то, что Type data[N][M] является синтаксическим сахаром, и разборка сгенерированных кодов операций покажет вам это. Вы правы, что смежный регион с одним поиском будет быстрее; ваш код просто этого не делает (пока). Я уверен, что некоторые ответы продемонстрируют это.
  • 0
    @WhozCraig: см. Редактировать
Показать ещё 1 комментарий
Теги:
optimization
arrays
heap

3 ответа

1
Лучший ответ

Вы не получаете выигрыш в производительности, потому что вам все равно придется делать два поиска в памяти в вашей обернутой версии. Арифметика int 1-й случай, доступ к элементу x * 1000 + y требует только одного поиска в памяти. Ваша версия с оболочкой возвращает указатель, который затем должен быть разыменован, что является медленной частью.

Попробуйте переустановить доступ к вашей оболочке в виде

inline double  operator()(int x, int y) const {return data[x*Y + y];}
inline double& operator()(int x, int y) {return data[x*Y + y];}

и вызывая

arr(x,y) = n;

Я удивлен тем, что инкапсулированный один из них быстрее, чем простой 2-й массив, поскольку он может иметь только дополнительные накладные расходы.

EDIT: И теперь, когда я больше смотрю на проблему, я вижу, что ваше решение фактически не выполняет два поиска, так как ваш оператор overloaded [] ведет себя по-разному. См. Мой комментарий к исходному сообщению.

  • 1
    C ++ не позволяет дифференцировать перегрузки только по типу возврата. Первый из них должен быть const членом для решения этой проблемы .
  • 0
    Ах, пропустил это спасибо.
Показать ещё 1 комментарий
1

Если я правильно понимаю, вы спрашиваете, почему ускорение использования 2D vs 1D кажется незначительным.

На мой взгляд, лучший способ сделать доступ к 2D-матрице - это использовать что-то вроде следующего.

double& operator()(const int row, const int col) inline{
    return data[X*row + col];
}

double operator()(const int row, const int col) inline const{
    return data[X*row + col];
}

Это дает вам ссылку и метод копирования.

Проблема со скоростью заключается в том, что она сильно зависит от базовой архитектуры вашего устройства.

Первая проблема - размер кеша. Очевидно, чем больше кеш, тем лучше и 1D-версия должна работать лучше, чем 2D в целом, как непрерывная память, лучше работает с кешем.

Также в вашем примере доступ к одному элементу в первый раз будет медленным, независимо от того, как упорядочена память, поскольку элемент не находится в кеше. Однако, если вы обращаетесь к этому элементу много раз или к элементу в той же области (кэш-строке), скорость должна быть более выраженной.

Второй вопрос - векторизация. В зависимости от операций, которые вы выполняете, особенно если они являются математическими операциями, такими как добавление и т.д., Они будут определять скорость. Если у вас есть более новый процессор с расширениями SSE или AVX, убедитесь, что компилятор компилирует эти функции, как правило, это делается автоматически при настройке. Вы можете убедиться, что, добавив -march = native и -msse3 или эквивалент Windows.

Еще одна небольшая оптимизация - сделать X, Y const. Это сделает вложение еще более эффективным, но, очевидно, с недостатком, который присваивает, становится болью.

Заключительное слово: профиль, посмотрите, где вы тратите больше всего времени и улучшаете его.

  • 0
    Замедление, безусловно, происходит из-за того, что двумерному массиву необходимо выполнить два поиска в памяти, что намного медленнее, чем 1-му массиву 1 (сложение и умножение) и одному поиску. Этот метод уплощенного массива, который вы упомянули здесь (и я упомянул ниже, по-видимому, спустя 7 секунд), дает как инкапсуляцию, так и ускорение использования арифметики.
  • 0
    @ user2711915 Я не уверен, что следую, как [x] [y] требует 2 поиска в памяти? data[5] в терминах указателей - это просто синтаксический сахар для data + sizeof(data[0])*5 Если вы расширите это с помощью [x][y] , дополнительных обращений к памяти не будет, так как sizeof (data [0]) должно быть знать во время компиляции. Это точно такая же арифметика. Поэтому [x] [y] такой же, как я определил, однако компилятор генерирует за кулисами.
Показать ещё 4 комментария
0

Другим преимуществом простого является то, что размеры являются константами, если вам не нужны размеры времени выполнения, попробуйте использовать шаблон:

Также, а не беспокоиться об управлении памятью - это будет лучше всего использовать с помощью std::unique_ptr и вместо double[] использовать std::array

template <class T, size_t X, size_t Y>
class Array
{
    using custom_array=std::array<T,X*Y>;
    std::unique_ptr<custom_array> data;

public:
    Array() : data{new custom_array} {}
    Array(const Array& rhs) : data{new custom_array(*(rhs.data))} {}
    Array& operator=(const Array& rhs) {
         if (&rhs != this)
         {
            *data=*(rhs.data);
         }
         return *this;
    }
    ~Array() {}
    T& operator() (int x, int y) { return data->at(x*Y+y); }
    T operator() (int x, int y) const { return data->at(x*Y+y); }
};

Использование: Array<double,1000,1000> A; double b=A(3,4); Array<double,1000,1000> A; double b=A(3,4);

Для яблок для яблок - выделяйте данные как std::array<double,X*Y>, но поскольку вы хотите выделить кучу, используйте вышеописанное с помощью unique_ptr.

  • 0
    да, мне действительно нужна куча в моем случае!
  • 0
    @ mb84 отредактировано для кучи

Ещё вопросы

Сообщество Overcoder
Наверх
Меню