Подтвердить что ты не робот

Импорт больших файлов/массивов с математикой

Я работаю с математикой 8.0.1.0 на платформе Windows 7 32bit. Я пытаюсь импортировать данные с помощью

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, я никогда не получаю такую ​​же информацию о массиве, как у меня с импортом (вероятно, потому, что я делаю это не так).

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

4b9b3361

Ответ 1

По какой-то причине текущая реализация 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] := [email protected][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]:= 
[email protected]@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 -s сохраняют свои элементы и, надеюсь, уточняет роль вышеперечисленных функций.

Код

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, [email protected][dataChunk] + [email protected]];
           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]:= [email protected]==t
Out[20]= True

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

In[21]:= ByteCount[t]
Out[21]= 619900248