【问题标题】:Import big files/arrays with mathematica使用mathematica 导入大文件/数组
【发布时间】:2011-11-23 10:58:16
【问题描述】:

我在 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。 但似乎我的数学知识不足以使建议的 OpenRead、ReadList 或类似的数据适应我的数据(参见 here 示例文件)。 问题是我需要文件中数组的其余程序信息,例如某些列和行中的Dimensions、Max/Min,并且我正在对某些列和每一行进行操作。 但是当我使用例如ReadList,我从来没有得到与 Import 相同的数组信息(可能是因为我做错了)。

有人可以给我一些建议吗?我会感谢每一个支持!

【问题讨论】:

  • 您真的是说在读取只有 38 MB 的文件时会出现此错误吗?
  • @Nasser 这是一种用于表格数据的特殊紧凑格式,类似于二进制格式。毫不奇怪,当解压缩到 Mathematica 的符号数据结构(列表)中时,它需要更多的内存。但是,这也不能解释如此巨大的内存要求 - 请参阅下面的答案。
  • 我在导入这个测试文件时没有任何问题。一次读入的数据的ByteCount是619,900,248,所以大约620 MB。这可能会导致您的问题。
  • @Sjoerd 我确实遇到了问题 - 在某些时候它需要超过 2GB 的 RAM,您可以通过使用系统监视器看到。由于我使用的是具有 6Gb RAM 的 64 位系统,因此这不是致命的。但我相信这对于运行其他一些东西的 32 位系统来说可能是一个杀手。 IMO,这是当前Import 实施中的一个明显缺陷-请参阅我的答案以获取替代方案。至于ByteCount,也是骗人的,因为final table实际占用的内存比ByteCount所指的少3倍左右。
  • @Leonid 你说得对,在Import 进程中确实存在内存使用高峰。

标签: wolfram-mathematica


【解决方案1】:

由于某种原因,Table 类型(表格数据)的当前实现 Import 非常占用内存 - 效率低下。下面我尝试在一定程度上纠正这种情况,同时仍然重用 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。我们获得内存效率提升的程度很大程度上取决于您的表的稀疏程度。在您的示例中,该表非常稀疏。

稀疏数组的剖析

我们从一个用于构造和解构 SparseArray 对象的通用 API 开始:

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}};

一些简短的 cmets 是有序的。这是一个示例稀疏数组:

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 给出了所有行中非零元素的位置,因此第一行为35,第二行为15245 最后一个。这里没有关于哪一行开始和结束的混淆,因为这可以由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

【讨论】:

  • 奇怪,Import 生成的数据结构在我的电脑上根据ByteCount 需要 619,900,216,但是如果我使用MaxmemoryUsed 测量它只需要 181,224,272 字节。我认为采用MaxmemoryUsed 路线并不能让您准确了解数据的内存需求。我的意思是,所有的垃圾收集都是在你背后完成的。
  • @Sjoerd 确实如此。我不知道为什么。另请参阅我上面的评论。
  • 确实,Table 既慢又极低内存效率。对于同类(即每个表条目的数据类型相同)数据,我通常使用ReadList。它速度更快,内存效率更高
  • @rcollyer 实际上,仅仅防止中止是不够的。还必须防止异常,然后必须在代码完成后重新抛出异常。所以,情况比较复杂。对我们来说幸运的是,这个函数(宏)已经存在:它是Internal`WithLocalSettings,Daniel Lichtblau 在这里描述了它:groups.google.com/group/comp.soft-sys.math.mathematica/…。根据您的建议,我将在这里使用它。
  • WithLocalSettings 在这里工作,但它在一般使用中有限制。例如,它不能正确处理ThrowCatch(例如Catch[Internal`WithLocalSettings[Print["start"], Throw[23], Print["cleanup"]]])。清理发生了,但“捕获”被打乱了。在 MMa 中处理堆栈展开是一项棘手的工作。见Reliable clean-up in Mathematica
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 2013-02-15
  • 2011-09-07
  • 2022-10-05
相关资源
最近更新 更多