Импорт больших файлов / массивов с помощью Mathematica - PullRequest
21 голосов
/ 23 сентября 2011

Я работаю с Mathematica 8.0.1.0 на 32-битной платформе Windows7.Я пытаюсь импортировать данные с

Import[file,”Table”]

, который работает нормально, пока файл (массив в файле) достаточно мал.Но для больших файлов (38 МБ) / массива (9429 раз 2052) я получаю сообщение:

No more memory available. Mathematica kernel has shut down. Try quitting other applications and then retry.

На моей 64-битной платформе Windows7 с большей оперативной памятью я могу импортировать большие файлы, но я думаю, что у меня будетодна и та же проблема однажды, когда файл вырос / массив имеет больше строк.

Итак, я пытаюсь найти решение для импорта больших файлов.После поиска в течение некоторого времени я увидел здесь похожий вопрос: Способ работы с большими файлами данных в Wolfram Mathematica .Но, похоже, мои знания по математике недостаточно хороши, чтобы адаптировать предложенные OpenRead, ReadList или аналогичные моим данным (см. здесь файл примера).Проблема в том, что мне нужна для остальной части моей программы информация о массиве в файле, такая как Dimensions, Max / Min в некоторых столбцах и строках, и я делаю операции над некоторыми столбцами и каждой строкой.Но когда я использую, например, ReadList, я никогда не получаю ту же информацию о массиве, что и при импорте (возможно, потому, что я делаю это неправильно).

Может кто-нибудь здесь дать мне какой-нибудь совет?Буду признателен за любую поддержку!

1 Ответ

34 голосов
/ 23 сентября 2011

По какой-то причине текущая реализация Import для типа Table (табличные данные) довольно неэффективна в отношении памяти.Ниже я предпринял попытку несколько исправить эту ситуацию, в то же время используя многоуровневые возможности импорта Mathematica (через ImportString).Для разреженных таблиц представлено отдельное решение, которое может привести к очень значительной экономии памяти.

Общее решение с эффективным использованием памяти

Вот гораздо более эффективная функция памяти:

Clear[readTable];
readTable[file_String?FileExistsQ, chunkSize_: 100] :=
   Module[{str, stream, dataChunk, result , linkedList, add},
      SetAttributes[linkedList, HoldAllComplete];
      add[ll_, value_] := linkedList[ll, value];           
      stream  = StringToStream[Import[file, "String"]];
      Internal`WithLocalSettings[
         Null,
         (* main code *)
         result = linkedList[];
         While[dataChunk =!= {},
           dataChunk = 
              ImportString[
                 StringJoin[Riffle[ReadList[stream, "String", chunkSize], "\n"]], 
                 "Table"];
           result = add[result, dataChunk];
         ];
         result = Flatten[result, Infinity, linkedList],
         (* clean-up *)
         Close[stream]
      ];
      Join @@ result]

Здесь я сопоставляю это со стандартом Import для вашего файла:

In[3]:= used = MaxMemoryUsed[]
Out[3]= 18009752

In[4]:= 
tt = readTable["C:\\Users\\Archie\\Downloads\\ExampleFile\\ExampleFile.txt"];//Timing
Out[4]= {34.367,Null}

In[5]:= used = MaxMemoryUsed[]-used
Out[5]= 228975672

In[6]:= 
t = Import["C:\\Users\\Archie\\Downloads\\ExampleFile\\ExampleFile.txt","Table"];//Timing
Out[6]= {25.615,Null}

In[7]:= used = MaxMemoryUsed[]-used
Out[7]= 2187743192

In[8]:= tt===t
Out[8]= True

Вы можете видеть, что мой код примерно в 10 раз эффективнее памяти, чем Import, хотяне намного медленнее.Вы можете контролировать потребление памяти, регулируя параметр chunkSize.Ваша результирующая таблица занимает около 150 - 200 МБ ОЗУ.

РЕДАКТИРОВАТЬ

Еще более эффективна для разреженных таблиц

Я хочу показать, какможет сделать эту функцию еще в 2-3 раза более эффективной с точки зрения памяти во время импорта, а также еще на порядок более эффективной с точки зрения конечной памяти, занимаемой вашей таблицей, используя SparseArray -s.Степень увеличения эффективности использования памяти во многом зависит от того, насколько разреженной является ваша таблица.В вашем примере таблица очень разреженная.

Анатомия разреженных массивов

Мы начнем с общепринятого API для создания и деконструкции объектов SparseArray:

ClearAll[spart, getIC, getJR, getSparseData, getDefaultElement, makeSparseArray];
HoldPattern[spart[SparseArray[s___], p_]] := {s}[[p]];
getIC[s_SparseArray] := spart[s, 4][[2, 1]];
getJR[s_SparseArray] := Flatten@spart[s, 4][[2, 2]];
getSparseData[s_SparseArray] := spart[s, 4][[3]];
getDefaultElement[s_SparseArray] := spart[s, 3];
makeSparseArray[dims : {_, _}, jc : {__Integer}, ir : {__Integer}, 
     data_List, defElem_: 0] :=
 SparseArray @@ {Automatic, dims, defElem, {1, {jc, List /@ ir}, data}};

Некоторые краткие комментарии в порядке.Вот пример разреженного массива:

In[15]:= 
ToHeldExpression@ToString@FullForm[sp  = SparseArray[{{0,0,1,0,2},{3,0,0,0,4},{0,5,0,6,7}}]]

Out[15]= 
Hold[SparseArray[Automatic,{3,5},0,{1,{{0,2,4,7},{{3},{5},{1},{5},{2},{4},{5}}},
{1,2,3,4,5,6,7}}]]

(я использовал цикл ToString - ToHeldExpression для преобразования List[...] и т. Д. В FullForm обратно в {...} для удобства чтения).Здесь {3,5} - это, очевидно, размеры.Далее 0, элемент по умолчанию.Далее идет вложенный список, который мы можем обозначить как {1,{ic,jr}, sparseData}.Здесь ic дает общее количество ненулевых элементов при добавлении строк - поэтому сначала 0, затем 2 после первой строки, вторая добавляет еще 2, а последняя добавляет еще 3.В следующем списке, jr, приведены позиции ненулевых элементов во всех строках, поэтому они равны 3 и 5 для первой строки, 1 и 5 для второй и 2,4 и 5 для последнего.Нет никакой путаницы относительно того, где и где начинается и заканчивается строка, поскольку это может быть определено списком ic.Наконец, у нас есть sparseData, который представляет собой список ненулевых элементов, которые читаются построчно слева направо (порядок такой же, как для списка jr).Это объясняет внутренний формат, в котором SparseArray -ы хранят свои элементы, и, мы надеемся, проясняет роль указанных выше функций.

Код

Clear[readSparseTable];
readSparseTable[file_String?FileExistsQ, chunkSize_: 100] :=
   Module[{stream, dataChunk, start, ic = {}, jr = {}, sparseData = {}, 
        getDataChunkCode, dims},
     stream  = StringToStream[Import[file, "String"]];
     getDataChunkCode := 
       If[# === {}, {}, SparseArray[#]] &@
         ImportString[
             StringJoin[Riffle[ReadList[stream, "String", chunkSize], "\n"]], 
             "Table"];
     Internal`WithLocalSettings[
        Null,
        (* main code *)
        start = getDataChunkCode;
        ic = getIC[start];
        jr = getJR[start];
        sparseData = getSparseData[start];
        dims = Dimensions[start];
        While[True,
           dataChunk = getDataChunkCode;
           If[dataChunk === {}, Break[]];
           ic = Join[ic, Rest@getIC[dataChunk] + Last@ic];
           jr = Join[jr, getJR[dataChunk]];
           sparseData = Join[sparseData, getSparseData[dataChunk]];
           dims[[1]] += First[Dimensions[dataChunk]];
        ],
        (* clean - up *)
        Close[stream]
     ];
     makeSparseArray[dims, ic, jr, sparseData]]

Тесты и сравнения

Вот начальный объем используемой памяти (свежее ядро):

In[10]:= used = MemoryInUse[]
Out[10]= 17910208

Мы называем нашу функцию:

In[11]:= 
(tsparse= readSparseTable["C:\\Users\\Archie\\Downloads\\ExampleFile\\ExampleFile.txt"]);//Timing
Out[11]= {39.874,Null}

Итак, это та же скорость, что и readTable.Как насчет использования памяти?

In[12]:= used = MaxMemoryUsed[]-used
Out[12]= 80863296

Я думаю, это довольно примечательно: мы когда-либо использовали вдвое больше памяти, чем файл на диске, занимающий сам себя.Но, что еще более примечательно, конечное использование памяти (после завершения вычислений) значительно сократилось:

In[13]:= MemoryInUse[]
Out[13]= 26924456

Это потому, что мы используем SparseArray:

In[15]:= {tsparse,ByteCount[tsparse]}
Out[15]= {SparseArray[<326766>,{9429,2052}],12103816}

Наша таблица занимает всего 12 МБ ОЗУ.Мы можем сравнить его с нашей более общей функцией:

In[18]:= 
(t = readTable["C:\\Users\\Archie\\Downloads\\ExampleFile\\ExampleFile.txt"]);//Timing
Out[18]= {38.516,Null}

Результаты будут такими же, как только мы преобразуем нашу разреженную таблицу обратно в нормальную:

In[20]:= Normal@tsparse==t
Out[20]= True

, тогда как обычная таблица занимает гораздо большепробел (кажется, что ByteCount переполняет занятую память примерно в 3-4 раза, но реальная разница все еще по крайней мере на порядок):

In[21]:= ByteCount[t]
Out[21]= 619900248
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...