【问题标题】:Ruby: start reading at arbitrary point in large fileRuby:从大文件中的任意点开始读取
【发布时间】:2010-11-05 02:55:20
【问题描述】:

我有一些想要筛选的日志文件。内容正是您在日志文件中所期望的:许多单行逗号分隔的文本。这些文件每个大约有 4 个演出。其中一个需要 File.each_line 或 foreach 大约 20 分钟。

由于一个简单的 foreach 看起来......简单(而且速度很慢),我想如果我只能告诉它们从哪里开始,两个单独的线程可能能够处理同一个文件。但根据我(有限的)知识,我无法确定这是否可能。

有没有办法从任意行开始读取文件?

【问题讨论】:

    标签: ruby flat-file


    【解决方案1】:

    为了查看一次读取整个文件与逐行读取的差异,我测试了一个大约 99MB、超过 1,000,000 行的文件。

    greg-mbp-wireless:Desktop greg$ wc filelist.txt 
     1003002 1657573 99392863 filelist.txt
    

    我将以下循环放入一个 ruby​​ 文件中,并使用 time 命令从命令行运行它:

    IO.read(ARGV.first).lines { |l|
    }
    
    greg-mbp-wireless:Desktop greg$ time ruby test.rb filelist.txt 
    
    real    0m1.411s
    user    0m0.653s
    sys     0m0.169s
    

    然后我将其更改为逐行读取并计时:

    IO.readlines(ARGV.first) { |l|
    }
    
    greg-mbp-wireless:Desktop greg$ time ruby test.rb filelist.txt 
    
    real    0m1.053s
    user    0m0.741s
    sys     0m0.278s
    

    我不知道为什么,但逐行阅读更快。这可能与内存分配有关,因为在第一个示例中 Ruby 尝试将整个文件加载到 RAM 中,或者这可能是一个异常,因为我只对每个文件进行了一次测试。使用带有显式文件大小的 read 可能会更快,因为 Ruby 会提前知道需要分配多少。

    这就是我需要测试的全部内容:

    fcontent = ''
    File.open(ARGV.first, 'r') do |fi|
      fsize = fi.size
      fcontent = fi.read(fsize)
    end
    puts fcontent.size
    
    greg-mbp-wireless:Desktop greg$ time ruby test.rb filelist.txt 
    99392863
    
    real    0m0.168s
    user    0m0.010s
    sys     0m0.156s
    

    看起来知道需要阅读多少内容会有很大的不同。

    在字符串缓冲区的循环中添加回结果:

    File.open(ARGV.first, 'r') do |fi|
      fsize = fi.size
      fi.read(fsize).lines { |l| 
      }
    end
    
    greg-mbp-wireless:Desktop greg$ time ruby test.rb filelist.txt 
    
    real    0m0.732s
    user    0m0.572s
    sys     0m0.158s
    

    这仍然是一个改进。

    如果您使用队列并从负责读取文件的线程中提供它,然后从处理传入文本的任何处理中消耗队列,那么您可能会看到更高的整体吞吐量。

    【讨论】:

    • 锡人,我发现read(fsize).lines确实比readlines快很多,但是看来read一次只能读取到long最大值的字节数。如果 long 的大小只有 32 位,那么这种方法一次只能读取大约 1 GB,并且需要多次读取迭代才能有效地读取更大的文件。
    • 我怀疑底层操作系统无论如何都无法读取 1GB 块。对于单一的“啜饮”来说,这似乎是一个巨大的价值。尝试改变正在读取的块的大小并查看读取需要多长时间。赔率很好,它不会平滑扩展,并且较小的值最终可能会更快。如果没有底层操作系统 IO 驱动程序的源代码或完整文档,则需要进行试验。
    • 锡人,我怀疑你对性能的看法是对的,但我的意思是,在某些平台上,上述实现根本无法读取 4 GB 文件,无论快慢.它报告一个错误,它无法将 BigNumber(如果文件足够大,则为 fsize 的类型)转换为 long(这显然是 read 所需要的)。由于此转换问题,它需要在多次迭代中一次读取最大 1 GB。但是,是的,正如您所说,块大小可能不是 1 GB,并且尝试一次读取的字节数可能会导致进一步的性能提升。
    • 如果你读取一个文件,然后再次读取它,即使使用相同的技术,它也会快得多,因为 Linux 会在第一次读取时缓存文件的内容。后续读取不会触及磁盘,而是从 RAM 读取。为了进行正确的测试,您必须清除测试之间的所有缓存。使用# sync; echo 3 > /proc/sys/vm/drop_caches
    【解决方案2】:

    如果您想从文件中的特定行开始,我建议您只使用尾部。

    excerpt = `tail -m +5000 filename.log`
    

    这将为您提供 filename.log 从第 5000 行到文件末尾的内容。

    【讨论】:

    • 显然,这也是抓取文件特定位置的一种非常高效的方式。 :)
    • 对我(Ubuntu)来说是tail -n +5000 filename.log-n不是-m
    【解决方案3】:

    对于行,可能有点困难,但你可以在文件中查找到某个字节。

    IO#seek (link)IO#pos (link) 都允许您查找文件中的给定字节。

    【讨论】:

    • seekpos 之后使用readline 读取到当前行的末尾,并且文件将从该点开始继续读取完整的行。确保捕获 EOF,以防您位于文件末尾附近并且在 EOF 发生之前没有遇到行尾字符。
    【解决方案4】:

    如果您还没有尝试过 faster_csv,并且如果速度仍然太慢,请使用像这样在 c 中具有本机扩展的东西 - http://github.com/wwood/excelsior

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 2011-02-03
      • 1970-01-01
      相关资源
      最近更新 更多