Хорошо, посмотрите, что на самом деле делает ваш код:
topKeys = range(16384)
table = dict((k,defaultdict(int)) for k in topKeys)
Это создает диктат, содержащий 16384 defaultdict(int)
. У dict есть определенное количество служебных данных: сам объект dict находится между 60 и 120 байтами (в зависимости от размера указателей и ssize_t в вашей сборке.) Это просто сам объект; если значение dict не превышает пару элементов, данные представляют собой отдельный блок памяти размером от 12 до 24 байт и всегда заполнены между 1/2 и 2 / 3rds. И defaultdicts на 4 - 8 байтов больше, потому что у них есть эта дополнительная вещь для хранения. И интервалы по 12 байт каждая, и хотя они используются повторно, где это возможно, этот фрагмент не будет использовать большинство из них. Таким образом, реально, в 32-битной сборке этот фрагмент займет 60 + (16384*12) * 1.8 (fill factor)
байт для table
dict, 16384 * 64
байт для defaultdicts, которые он хранит в качестве значений, и 16384 * 12
байт для целых чисел. Так что это чуть более полутора мегабайт без сохранения чего-либо в ваших defaultdicts . И это в 32-битной сборке; 64-битная сборка будет в два раза больше.
Затем вы создаете простой массив, который на самом деле довольно консервативен с памятью:
dat = num.zeros((16384,8192), dtype="int32")
Это будет иметь некоторые накладные расходы для самого массива, обычные накладные расходы на объекты Python плюс размеры и тип массива и тому подобное, но это будет не более 100 байт, и только для одного массива. Тем не менее, он хранит 16384*8192
int32 в ваших 512Mb.
И тогда у вас есть довольно необычный способ заполнения этого массива:
for k in topKeys:
for j in keys:
dat[k,j] = table[k][j]
Сами два цикла не занимают много памяти, и они используют ее каждую итерацию. Однако table[k][j]
создает новое целое число Python для каждого запрашиваемого вами значения и сохраняет его в defaultdict . Созданное целое число всегда равно 0
, и так получилось, что оно всегда используется повторно, но при сохранении ссылки на него по-прежнему используется место в defaultdict: вышеупомянутые 12 байтов на запись, умноженные на коэффициент заполнения (между 1,66 и 2. ) Таким образом, вы получаете почти 3 ГБ реальных данных и 6 ГБ в 64-разрядной сборке.
Кроме того, из-за того, что вы добавляете данные, по умолчанию вы должны продолжать расти, что означает, что они должны продолжать перераспределение. Из-за внешнего интерфейса malloc Python (obmalloc) и того, как он выделяет меньшие объекты в своих собственных блоках, а также из-за того, как память процесса работает в большинстве операционных систем, это означает, что ваш процесс будет выделять больше и не сможет его освободить; на самом деле он не будет использовать все 11 ГБ, и Python будет повторно использовать доступную память между большими блоками для defaultdicts, но общее отображаемое адресное пространство будет 11 ГБ.