使用mathematica导入大文件/数组

时间:2011-09-23 07:42:03

标签: wolfram-mathematica

我在Windows7 32位平台上使用mathematica 8.0.1.0。我尝试用

导入数据
Import[file,”Table”]
只要文件(文件中的数组)足够小,

就可以正常工作。但是对于更大的文件(38MB)/阵列(9429乘2052),我收到消息:

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

在具有更多主内存的Windows7 64位平台上,我可以导入更大的文件,但我认为有一天,当文件增长/数组有更多行时,我会遇到同样的问题。

所以,我试图找到一个导入大文件的解决方案。搜索了一段时间后,我在这里看到了一个类似的问题:Way to deal with large data files in Wolfram Mathematica。 但似乎我的mathematica知识不足以使建议的OpenRead,ReadList或类似的数据适应我的数据(参见here示例文件)。 问题是我需要文件中数组的其余程序信息,例如某些列和行中的Dimensions,Max / Min,我正在对某些列和每一行进行操作。  但是,当我使用例如ReadList,我从来没有得到与我使用Import相同的数组信息(可能是因为我以错误的方式进行)。

有人可以给我一些建议吗?我很感激所有的支持!

1 个答案:

答案 0 :(得分:34)

出于某种原因,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

您可以看到我的代码的内存效率比Import大10倍,而速度却慢得多。您可以通过调整chunkSize参数来控制内存消耗。您生成的表占用大约150 - 200 MB的RAM。

修改

为稀疏表格提高效率

我想说明一下如何在导入过程中使这个功能的内存效率提高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给出了所有行中非零元素的位置,因此第一行3515 }表示第二个,245表示最后一个。关于哪一行在此处开始和结束的位置没有混淆,因为这可以由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, 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 MB的RAM。我们可以将它与我们更普遍的功能进行比较:

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