Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Накладные расходы стандартных контейнеров


Автор: W4FhLF 7.3.2012, 17:39
Есть у меня в программе двумерный массив, который хранит некоторые значения (POD типы), допустим double. Известно, что размер массива по одному из измерений не превышает максимальное значение unsigned short.
Строятся эти массивы один раз и новые элементы в них добавляются нечасто. А вот обращение к ij-ому элементу осуществляется десятки и сотни миллионов раз.

Проблема в том, что в большинстве случаев заполнены они только на 30-70%
Т.е. массивы могут занимать приличный объём (например 10000*10000*sizeof(double) = 800 мб) и экономия получается весьма ощутима если отбросить пустые элементы.

Ну думаю дай засуну их в какой-нибудь стандартный контейнер типа std::map<unsigned, double> или std::unordered_map<unsigned, double>. В конце-концов у последнего сложность О(1), что выглядит совсем как массив, хотя на практике конечно же медленнее. В качестве ключа всегда можно сделать: key = j + i*cols_num, где i,j - строка и столбец массива, соответственно.

Каково же было моё удивление, когда я обнаружил, что контейнеры эти занимают в ~10 раз больше памяти, чем массив. Т.е. даже массив, который заполнен только на 30%, будет занимать в 3 раза меньше памяти, чем map или unordered_map. Это в Visual C++ 2010.

Размер элемента в контейнерах можно выразить как:
(sizeof(key) + sizeof(value) + factor)
Этот фактор зависит от реализации библиотеки, в моём случае составляет порядка 60 байт на одну запись, что совсем неприемлемо.

Кто какие решения может предложить? 

Автор: boostcoder 7.3.2012, 18:57
Цитата(W4FhLF @  7.3.2012,  17:39 Найти цитируемый пост)
я обнаружил, что контейнеры эти занимают в памяти в ~10 раз больше памяти, чем массив

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

Цитата(W4FhLF @  7.3.2012,  17:39 Найти цитируемый пост)
Кто какие решения может предложить?

а в чем вопрос?

Автор: W4FhLF 7.3.2012, 19:11
Вопрос как хранить полупустой массив (неважно, двумерный или одномерный, т.к. любой многомерный массив может быть представлен как одномерный) оптимально с точки зрения памяти и случайного доступа к элементам. 

Автор: boostcoder 7.3.2012, 19:17
т.к. массив полупустой, сэкономить можно на размере выделяемом под элемент.
решение влоб - создать массив указателей, и выделять память каждому указателю только если он не пустой.
таким образом, можно сэкономить по 4 байта на элемент в случае хранения указателей на double.
время доступа идеальное.

Автор: rumit7 7.3.2012, 19:28
Цитата(W4FhLF @ 7.3.2012,  19:11)
Вопрос как хранить полупустой двумерный массив оптимально с точки зрения памяти и случайного доступа к элементам.

Может быть так http://evgeny-lazin.blogspot.com/2011/06/blog-post.html

Автор: W4FhLF 7.3.2012, 19:30
Цитата

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


Таким образом ничего не экономится, даже наоборот.

Добавлено через 4 минуты и 4 секунды
rumit7, это не будет работать на массивах размером больше sizeof(int)*8

Добавлено через 5 минут и 42 секунды
Точнее работать будет, если использовать что-то вроде std::bitset или свой вариант, но тогда накладные расходы на доступ будут огромные, хотя по памяти этот вариант выглядит интересно. 

Автор: rumit7 7.3.2012, 19:59
Цитата(W4FhLF @ 7.3.2012,  19:30)
rumit7, это не будет работать на массивах размером больше sizeof(int)*8

Добавлено @ 19:36
Точнее работать будет, если использовать что-то вроде std::bitset или свой вариант, но тогда накладные расходы на доступ будут огромные, хотя по памяти этот вариант выглядит интересно.

Я сомневаюсь на счет огромных расходов. Во всяком случае, если std::bitset окажется столь медленным, то можно и свою реализацию заточить под данный проект. Если так подумать, то что такое свой bitset? Это массив 32-битных масок (на 64-битных системах можно и 64-битные маски хранить сразу). Нам нужны две операции: 1) доступ к конкретному биту (т.е. смещение внутри массива масок + битовая операция + иногда обновление маски) и 2) постоянный контроль кол-ва установленных бит для всего массива масок (ну это обычный целочисленный счетчик).

Я думаю, выжав максимум можно приблизиться к оригинальному времени массива.. ну во всяком случае для двумерного массива - не проиграть слишком много..

Автор: feodorv 7.3.2012, 20:22
Цитата(rumit7 @  7.3.2012,  20:59 Найти цитируемый пост)
постоянный контроль кол-ва установленных бит для всего массива масок (ну это обычный целочисленный счетчик)

Чешу тыкву со времён http://forum.vingrad.ru/forum/topic-347698.html. Не пойму, почему дело ограничивается обычным целочисленным счётчиком:
Цитата

Для получения индекса i-го элемента в массиве, мы должны выполнить следующие операции:
    int bit = 1 << i; 
    if (mask & bit == 0) return null;
    int index = numberOfSetBits(mask & (bit - 1));
    return array[index]; 

bit - всегда степень двойки, а (bit-1) - двоичное число из i единиц. Таким образом, нужно считать число установленных бит в позициях (0,i-1) (для i > 0), а это для битового набора произвольной длины задача не столь тривиальная. В общем, одним целочисленным счётчиком не отделаться...

Автор: W4FhLF 7.3.2012, 20:24
rumit7, для массива размером 1000000 в котором только половина значений нелувые у тебя будет 15625 64х битных числа, которые будут хранить маску и собственно массив из 500000 ненулевых элементов. Нужно обратить к i-му элементу. Как быстро вычислить его позицию в массиве ненулевых элементов?

Автор: borisbn 7.3.2012, 20:30
Цитата(W4FhLF @  7.3.2012,  17:39 Найти цитируемый пост)
Каково же было моё удивление, когда я обнаружил, что контейнеры эти занимают в ~10 раз больше памяти, чем массив.

Debug или Release ?

Автор: feodorv 7.3.2012, 20:36
Между прочим, в http://forum.vingrad.ru/index.php?showtopic=347698&view=findpost&p=2464927 была дана ссылка не некую библиотеку sparcelib++. Я её не смотрел, но, может, она решит проблему?

Автор: W4FhLF 7.3.2012, 20:45
borisbn, release конечно.

Добавлено через 2 минуты и 53 секунды
Цитата(feodorv @ 7.3.2012,  20:36)
Между прочим, в http://forum.vingrad.ru/index.php?showtopic=347698&view=findpost&p=2464927 была дана ссылка не некую библиотеку sparcelib++. Я её не смотрел, но, может, она решит проблему?

Судя по названию речь идёт о разряженных массивах. То есть когда количество ненулевых элементов много меньше общего числа элементов.

Автор: feodorv 7.3.2012, 21:29
Цитата(W4FhLF @  7.3.2012,  21:45 Найти цитируемый пост)
То есть когда количество ненулевых элементов много меньше общего числа элементов. 

О "много меньше" там ни слова. Более того, даже если все элементы матрицы ненулевые, то всё равно библиотекой можно пользоваться (будут накладные расходы по памяти и доступу). 
Хранение данных там чуть более своеобразно, чем http://forum.vingrad.ru/index.php?showtopic=347698&view=findpost&p=2463818. В любом случае, пользоваться самой библиотекой смысла нет (уж больно она математизирована), но ведь можно подсмотреть, как она устроена smile

Добавлено через 6 минут и 53 секунды
Всё же, возвращаясь к постановке задачи:

Цитата(W4FhLF @  7.3.2012,  18:39 Найти цитируемый пост)
новые элементы в них добавляются нечасто. А вот обращение к ij-ому элементу осуществляется десятки и сотни миллионов раз.

Проблема в том, что в большинстве случаев заполнены они только на 30-70%


Насколько важна экономия памяти по сравнению со снижением скорости доступа? По-моему, лучше простого массива здесь ничего не придумаешь...

Автор: W4FhLF 7.3.2012, 22:03
feodorv, с форматами хранения разряженных матриц я знаком. Они эффективны когда бОльшая часть элементов == 0, к тому же время доступа там в лучшем случае логарифмическое. 

По поводу того, что лучше двумерного массива ничего не найти я догадывался ещё до создания этой. smile

Автор: feodorv 7.3.2012, 22:46
Цитата(W4FhLF @  7.3.2012,  23:03 Найти цитируемый пост)
По поводу того, что лучше двумерного массива ничего не найти я догадывался ещё до создания этой. 

Ага, понятно  smile 

Меня смущает (даже больше чем "объём vs доступ") то, что там линейные массивы, размером в число ненулевых элементов. При добавлении нового ненулевого элемента можно нарваться на перезаказ памяти нехилого размера (а непрерывного участка памяти такого размера может и не оказаться в наличии после нескольких перезаказов...)

Если же заранее известен размер двумерного массива, то ничего перезаказывать не нужно.

Цитата(W4FhLF @  7.3.2012,  23:03 Найти цитируемый пост)
к тому же время доступа там в лучшем случае логарифмическое

Гм. А где оно не логарифмическое?)))

Автор: W4FhLF 7.3.2012, 23:14
Цитата(feodorv @  7.3.2012,  22:46 Найти цитируемый пост)
Меня смущает (даже больше чем "объём vs доступ") то, что там линейные массивы, размером в число ненулевых элементов. При добавлении нового ненулевого элемента можно нарваться на перезаказ памяти нехилого размера


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

Цитата(feodorv @  7.3.2012,  22:46 Найти цитируемый пост)
Гм. А где оно не логарифмическое?))) 


В хеш-таблице например. Это теория, на практике по моим тестам оно лишь в несколько раз меньше времени доступа к элементам std::map, которое в теории логарифмическое.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)