如何在lua中读取大文件(> 1GB)?

Posted

技术标签:

【中文标题】如何在lua中读取大文件(> 1GB)?【英文标题】:How to read large files (>1GB) in lua? 【发布时间】:2016-10-03 12:34:22 【问题描述】:

我是 Lua 的新手(用于 Torch7 框架)。我有一个大小约为 1.4GB 的输入功能文件(文本文件)。简单的 io.open 函数在尝试打开此文件时会引发错误“内存不足”。在浏览用户组和文档时,我发现它可能是 Lua 限制。有解决方法吗?还是我在读取文件时做错了什么?

local function parse_file(path)
    -- read file
    local file = assert(io.open(path,"r"))
    local content = file:read("*all")
    file:close()

    -- split on start/end tags.
    local sections = string.split(content, start_tag)
    for j=1,#sections do
        sections[j] = string.split(sections[j],'\n')
        -- remove the end_tag
        table.remove(sections[j], #sections[j])
    end 
    return sections
end

local train_data = parse_file(file_loc .. '/' .. train_file)

编辑:我试图读取的输入文件包含我想训练我的模型的图像特征。这个文件是有序的(start-tag ...contents...end-tagstart-tag ...等等...),所以如果我可以加载这些就可以了部分(开始标签到结束标签)一次一个。但是,我希望所有这些部分都加载到内存中。

【问题讨论】:

您确定在io.open 之后弹出“内存不足”吗?这似乎不对。但是,您可以分块读取文件吗?你真的需要内存中的hole文件吗注意file:read("*all")中的*在lua 5.3中已经过时了(我不知道torch使用哪个版本) Torch 使用有内存限制的 LuaJIT。参见例如***.com/questions/35155444/…. @pschulz : 在执行local content = file:read("*all") 时弹出内存不足错误。 好吧,这似乎是合理的。请把这件事弄清楚。我当然知道你的意思是在调用 io 时。 阅读,但你永远不知道。但同样,您真的必须一次读取整个文件吗? @pschulz :对于不清楚的问题,我深表歉意。在完成这项任务本身时,我正在深入了解 Lua。我试图读取的输入文件包含我想训练我的模型的图像特征。这个文件是有序的( ...contents... ...等等...),所以如果我可以加载这些就可以了一节一节。但是,我希望所有这些部分都加载到内存中。这会让事情变得更清楚吗?我正在相应地编辑问题。谢谢:) 【参考方案1】:

事实证明,解决加载大文件问题的最简单方法是将 Torch 升级到 Lua5.2 或更高版本!正如 torch7-google-group 上的 Torch 开发人员所建议的那样。

cd ~/torch
./clean.sh
TORCH_LUA_VERSION=LUA52 ./install.sh

从 5.2 版本开始不再存在内存限制!我已经对此进行了测试,效果很好!

参考:https://groups.google.com/forum/#!topic/torch7/fi8a0RTPvDo


另一种可能的解决方案(更优雅且类似于@Adam 在他的回答中建议的)是使用逐行读取文件并使用张量或tds 来存储数据,因为这使用了 Luajit 之外的内存。代码示例如下,感谢 Vislab。

local ffi = require 'ffi'
-- this function loads a file line by line to avoid having memory issues
local function load_file_to_tensor(path)
  -- intialize tensor for the file
  local file_tensor = torch.CharTensor()
  
  -- Now we must determine the maximum size of the tensor in order to allocate it into memory.
  -- This is necessary to allocate the tensor in one sweep, where columns correspond to letters and rows correspond to lines in the text file.
  
  --[[ get  number of rows/columns ]]
  local file = io.open(path, 'r') -- open file
  local max_line_size = 0
  local number_of_lines = 0
  for line in file:lines() do
    -- get maximum line size
    max_line_size = math.max(max_line_size, #line +1) -- the +1 is important to correctly fetch data
    
    -- increment the number of lines counter
    number_of_lines = number_of_lines +1
  end
  file:close() --close file
  
  -- Now that we have the maximum size of the vector, we just have to allocat memory for it (as long there is enough memory in ram)
  file_tensor = file_tensor:resize(number_of_lines, max_line_size):fill(0)
  local f_data = file_tensor:data()
  
  -- The only thing left to do is to fetch data into the tensor. 
  -- Lets open the file again and fill the tensor using ffi
  local file = io.open(path, 'r') -- open file
  for line in file:lines() do
    -- copy data into the tensor line by line
    ffi.copy(f_data, line)
    f_data = f_data + max_line_size
  end
  file:close() --close file

  return file_tensor
end

从这个张量中读取数据既简单又快捷。例如,如果您想读取文件中的第 10 行(将在张量的第 10 位),您可以简单地执行以下操作:

local line_string = ffi.string(file_tensor[10]:data()) -- this will convert into a string var

一个警告:这将占用更多的内存空间,并且对于一些行比另一行长得多的情况可能不是最佳的。但是,如果您没有内存问题,甚至可以忽略这一点,因为将张量从文件加载到内存时速度非常快,并且可能会在此过程中为您节省一些白发。

参考:https://groups.google.com/forum/#!topic/torch7/fi8a0RTPvDo

【讨论】:

【参考方案2】:

我从来没有需要读取这么大的文件,但是如果您的内存不足,您可能需要逐行读取它。经过一些快速研究,我从 lua 网站上找到了这个:

buff = buff..line.."\n"

buff 是一个 50,020 字节的新字符串,而现在的旧字符串 > 垃圾。经过两个循环周期后,buff 是一个 50,040 字节的字符串,并且有两个旧字符串总共产生了超过 100 KB 的垃圾。因此,Lua 非常正确地决定现在是运行其垃圾收集器的好时机,因此它释放了这 100 KB。问题是这将每两个周期发生一次,因此 Lua 将在完成循环之前运行其垃圾收集器 2000 次。即使完成了所有这些工作,它的内存使用量也将是文件大小的三倍左右。更糟糕的是,每个连接都必须将整个字符串内容(50 KB 并且还在增长)复制到新字符串中。

因此,即使您逐行读取并每次都使用这样的连接,加载大文件似乎也会使用大量内存:

local buff = ""  
while 1 do  
    local line = read()  
    if line == nil then break end  
    buff = buff..line.."\n"  
end  

然后他们提出了一个更节省内存的过程:

  function newBuffer ()
      return n=0     -- 'n' counts number of elements in the stack
  end  

  function addString (stack, s)
    table.insert(stack, s)       -- push 's' into the top of the stack
    for i=stack.n-1, 1, -1 do
      if string.len(stack[i]) > string.len(stack[i+1]) then break end
      stack[i] = stack[i]..table.remove(stack)
    end
  end

  function toString (stack)
    for i=stack.n-1, 1, -1 do
      stack[i] = stack[i]..table.remove(stack)
    end
    return stack[1]
  end

这比以前占用更少的内存。所有信息来自: http://www.lua.org/notes/ltn009.html 希望对您有所帮助。

【讨论】:

当心:旧代码。没有tinserttremove 等。但这个想法仍然有效。 有点。将完整文件读入内存的最节省内存的方法仍然是file:read("*a")。仅当您可以让 GC 收集以前的块时,读取较小的块才有意义。 @siffiejoe :所以使用file:read("*a") 读取文件是最好的选择吗?即一个人根本无法在 Lua 中读取大文件? :// @Adam :感谢您的详细回答。但我认为这对我的情况没有帮助。从您收集此信息的页面上,它显示`要读取整个文件,您可以使用“*all”选项,它会立即读取它。但有时你没有这么简单的解决方案。那么,唯一的解决方案是为您的问题提供更有效的算法。所以即使是帖子也符合@sifijow 所说的内容?对此我还不是很清楚,如有错误请指正。 @NightFury13: file:read("*a") 是最好的选择如果你需要整个文件内容作为内存中的单个字符串。如果file:read("*a") 对您失败,则意味着您无法一次存储整个文件。我建议您对文件内容使用迭代器接口,以便您可以一个接一个地拉入一个元素。

以上是关于如何在lua中读取大文件(> 1GB)?的主要内容,如果未能解决你的问题,请参考以下文章

如何逐行读取大文件?

如何使用 lua 读取文件夹名称并将它们放入表列表中

如何在Lua中读取整个文件

Lua如何从文件中读取数据

Unity使用xlua读取lua数据表性能分析

[专栏作家] 使用xlua读取lua数据表性能分析