Используйте контейнеры STL! …если вы не хотите ехать быстрее

Мы все знаем, что нужно использовать STL, когда это возможно, не изобретать велосипед и двигаться вперед как сообщество.
Тем не менее, некоторые его недостатки и задержки в процессе интеграции часто являются причинами для развертывания собственных контейнеров/алгоритмов (mozilla::vector, eastl::list, так далее). Мы собираемся увидеть реальный пример этого.

В этом случае у нас есть 2D изображение (матрица значений), которые нам нужно сериализовать в длинный 1D вектор (поставить ряды в линию).
Каждая строка является смежной, но они не обязательно являются смежными друг с другом.

изображение.png

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

Мы будем использовать следующую реализацию image:

const size_t width = 2000000, height = 5; // Small sizes for example sake
std::vector<std::vector<double>> image (height);
image[0].resize(width);
image[1].resize(width);
image[2].resize(width);
image[3].resize(width);
image[4].resize(width);
std::iota(image[0].begin(), image[0].end(), 1); // Giving non-trivial values
std::iota(image[1].begin(), image[1].end(), 1);
std::iota(image[2].begin(), image[2].end(), 1);
std::iota(image[3].begin(), image[3].end(), 1);
std::iota(image[4].begin(), image[4].end(), 1);

Мы используем непрерывный std::vector для изображения, но это просто для упрощения доступа к строкам (оператор нижнего индекса []) в этом примере.

Итак, мы хотим создать std::vector и заполните его значениями!
Но здесь мы застреваем: у каждого варианта есть обратная сторона…

std::vector<double> res1 (image.size()*width);
for (size_t i = 0; i < image.size(); ++i)
    std::copy(image[i].begin(), image[i].end(), res1.begin()+i*width);

Этот пример можно хорошо распараллелить, но создание вектора (res1(image.size()*width);) инициализирует его элементы, что является дорогостоящей операцией, когда изображение становится очень большим.

Переходим к известному решению «reserve & push_back»:

std::vector<double> res2;
res2.reserve(width*height);
for (size_t i = 0; i < height; ++i)
    for (size_t j = 0; j < width; ++j)
        res2.push_back(image[i][j]);

Здесь два минуса: push_back не распараллеливается, и каждый стоит проверка/увеличение размера на каждом шагу. То же самое касается assign решение.
Мы ограничены тем, что для большей безопасности size член std::vector не имеет общедоступного сеттера, поэтому мы не можем «зарезервировать, установить размер и скопировать данные»что, по сути, мы и хотим сделать.

Итак, вот идея иметь быструю копию:

template <typename T>
class serializedImage
{
public:
    serializedImage(const std::vector<std::vector<T>>& vectors) :
        data   (nullptr),
        width  (vectors[0].size()),
        height (vectors.size()),
        size   (vectors[0].size()*vectors.size())
    {
        data = new T[size]; // 'new T[size]()' would initialize
#pragma omp parallel for
        for (size_t i = 0; i < height; ++i)
            std::copy(vectors[i].begin(), vectors[i].end(), data+i*width);
    }

    ~serializedImage() { delete[] data; data = nullptr; }

private:
    T* data; size_t width; size_t height; size_t size;
};

Для удобочитаемости код был сокращен до минимума без проверок безопасности (пустой ввод, операторы, пространство имен и т. д.).

Итак, мы здесь! Из большой серии больших векторов у нас есть способ эффективно выровнять их, используя без лишних затрат И в параллельно мода. И мы уважаем РАИИ с очень простой конструкцией!

serializedImage<double> res3 (image);

альтернативный
Источник: Гифи

Кстати, я попытался сделать ctor выражением Fold, которое использует переменное количество входных векторов (например, size = (vectors.size()+...);) но пока не удалось, если кто сможет, было бы интересно посмотреть.

Ссылки по теме:

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *