Ответ 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