【问题标题】:Count lines in large files计算大文件中的行数
【发布时间】:2012-09-24 20:35:15
【问题描述】:

我通常处理约 20 Gb 大小的文本文件,我发现自己经常计算给定文件中的行数。

我现在的做法只是cat fname | wc -l,而且需要很长时间。有没有更快的解决方案?

我在安装了 Hadoop 的高性能集群中工作。我想知道 map reduce 方法是否有帮助。

我希望解决方案像 wc -l 解决方案一样简单,就像一行运行一样,但不确定它是否可行。

有什么想法吗?

【问题讨论】:

  • 是否每个节点都已经拥有该文件的副本?
  • 谢谢。是的。但是为了访问许多节点,我使用了一个 LSF 系统,它有时会表现出相当烦人的等待时间,这就是为什么理想的解决方案是在一个节点中使用 hadoop/mapreduce 但可以使用其他节点(然后添加等待时间可能会比 cat wc 方法慢)
  • wc -l fname 可能更快。你也可以试试vim -R fname,如果这样更快(它应该告诉你启动后的行数)。
  • 你可以用猪脚本来做,看我的回复:stackoverflow.com/questions/9900761/…
  • 记住useless use of cat规则要快一些。

标签: linux mapreduce


【解决方案1】:

试试:sed -n '$=' filename

cat 也是不必要的:wc -l filename 以你现在的方式就足够了。

【讨论】:

  • 嗯,很有趣。 map/reduce 方法会有帮助吗?我假设如果我以 HDFS 格式保存所有文件,然后尝试使用 map/reduce 计算行数会快得多,不是吗?
  • @lvella。这取决于它们是如何实现的。根据我的经验,我看到sed 更快。也许,一些基准测试可以帮助更好地理解它。
  • @KingsIndian。确实,刚刚尝试过 sed,它在 3Gb 文件中比 wc 快 3 倍。感谢 KingsIndian。
  • @Dnaiel 如果我猜我会说你先运行wc -l filename,然后运行sed -n '$=' filename,所以在第一次运行时 wc 必须从磁盘读取所有文件,所以它可以完全缓存在您可能大于 3Gb 的内存上,因此 sed 可以在接下来更快地运行。我自己在具有 6Gb RAM 的机器上使用 4Gb 文件进行了测试,但我确保该文件已经在缓存中;得分:sed - 0m12.539s,wc -l - 0m1.911s。所以wc 快了 6.56 倍。重做实验,但在每次运行前清除缓存,它们都需要大约 58 秒才能完成。
  • 这个使用 sed 的解决方案有一个额外的优势,即不需要行尾字符。 wc 计算行尾字符(“\n”),所以如果文件中有一行没有 \n,那么 wc 将返回 0。sed 将正确返回 1。
【解决方案2】:

限制速度因素是存储设备的 I/O 速度,因此在简单的换行符/模式计数程序之间进行更改将无济于事,因为这些程序之间的执行速度差异可能会被较慢的磁盘所抑制/存储/无论你有什么。

但是,如果您在磁盘/设备之间复制了相同的文件,或者文件分布在这些磁盘之间,您当然可以并行执行该操作。我不具体了解这个 Hadoop,但假设您可以从 4 个不同的位置读取 10gb 的文件,您可以运行 4 个不同的行计数过程,每个过程都在文件的一部分中,并将它们的结果总结起来:

$ dd bs=4k count=655360 if=/path/to/copy/on/disk/1/file | wc -l &
$ dd bs=4k skip=655360 count=655360 if=/path/to/copy/on/disk/2/file | wc -l &
$ dd bs=4k skip=1310720 count=655360 if=/path/to/copy/on/disk/3/file | wc -l &
$ dd bs=4k skip=1966080 if=/path/to/copy/on/disk/4/file | wc -l &

注意每个命令行上的&,所以所有的都将并行运行; dd 在此处的工作方式类似于 cat,但允许我们指定要读取多少字节(count * bs 字节)以及在输入开头跳过多少字节(skip * bs 字节)。它以块为单位工作,因此需要将bs 指定为块大小。在此示例中,我将 10Gb 文件划分为 4 个 4Kb * 655360 = 2684354560 字节 = 2.5GB 的相等块,为每个作业分配一个,您可能需要根据文件的大小设置一个脚本来为您执行此操作文件和您将运行的并行作业的数量。您还需要总结执行结果,因为我缺乏 shell 脚本能力。

如果您的文件系统足够智能,可以在多个设备(如 RAID 或分布式文件系统等)之间拆分大文件,并自动并行化可并行化的 I/O 请求,您可以进行这样的拆分,运行许多并行作业,但使用相同的文件路径,您仍然可能会获得一些速度提升。

编辑: 我想到的另一个想法是,如果文件中的行大小相同,则可以通过将文件大小除以行大小来获得确切的行数,均以字节为单位。您几乎可以在一项工作中立即完成。如果您有平均大小并且不完全关心行数,但想要估计,您可以执行相同的操作并比精确操作更快地获得令人满意的结果。

【讨论】:

    【解决方案3】:

    根据我的测试,我可以验证 Spark-Shell(基于 Scala)比其他工具(GREP、SED、AWK、PERL、WC)快得多。这是我在一个有 23782409 行的文件上运行的测试结果

    time grep -c $ my_file.txt;
    

    真正的 0m44.96s 用户 0m41.59s 系统 0m3.09s

    time wc -l my_file.txt;
    

    真正的 0m37.57s 用户 0m33.48s 系统 0m3.97s

    time sed -n '$=' my_file.txt;
    

    真正的 0m38.22s 用户 0m28.05s 系统 0m10.14s

    time perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt;

    真正的 0m23.38s 用户 0m20.19s 系统 0m3.11s

    time awk 'END { print NR }' my_file.txt;
    

    真正的 0m19.90s 用户 0m16.76s 系统 0m3.12s

    spark-shell
    import org.joda.time._
    val t_start = DateTime.now()
    sc.textFile("file://my_file.txt").count()
    val t_end = DateTime.now()
    new Period(t_start, t_end).toStandardSeconds()
    

    res1: org.joda.time.Seconds = PT15S

    【讨论】:

    • 您可以在命令前加上time 来获取运行时。
    • 刚刚意识到我有基于 AIX 的系统,我在该系统上执行这些测试,它不支持我期望的时间关键字
    • FWIW,我认为您不能指望这些时间在所有操作系统中保持一致“wc -l”对我来说比 awk 快,计算 1.1gb 日志文件上的行数。 Sed虽然很慢。感谢您显示选项!
    • 我完全同意你的看法。这肯定很大程度上取决于这些实用程序在不同操作系统上的优化。我不确定这些小型实用程序是如何设计成不同风格的。感谢您提出这种观点。
    【解决方案4】:

    在多核服务器上,使用GNU parallel 并行计算文件行数。打印每个文件的行数后, bc 将所有行数相加。

    find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc
    

    为了节省空间,您甚至可以压缩所有文件。以下行解压缩每个文件并并行计算其行数,然后对所有计数求和。

    find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc
    

    【讨论】:

    • 好主意。我正在使用这个。如果磁盘瓶颈是一个问题,请参阅我关于使用dd 而不是wc 读取文件的答案。
    【解决方案5】:

    如果您的数据位于 HDFS 上,那么最快的方法可能是使用 hadoop 流。 Apache Pig 的 COUNT UDF 对包进行操作,因此使用单个 reducer 来计算行数。相反,您可以在一个简单的 hadoop 流脚本中手动设置 reducer 的数量,如下所示:

    $HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar -Dmapred.reduce.tasks=100 -input <input_path> -output <output_path> -mapper /bin/cat -reducer "wc -l"
    

    请注意,我手动将减速器的数量设置为 100,但您可以调整此参数。一旦 map-reduce 工作完成,每个 reducer 的结果都会存储在一个单独的文件中。最终的行数是所有 reducer 返回的数字的总和。您可以按如下方式获得最终的行数:

    $HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc
    

    【讨论】:

      【解决方案6】:

      我知道这个问题已经有几年的历史了,但是在 Ivella's last idea 上扩展,这个 bash 脚本估计几秒钟或更短的时间内一个大文件的行数通过测量一条线的大小并从中推断:

      #!/bin/bash
      head -2 $1 | tail -1 > $1_oneline
      filesize=$(du -b $1 | cut -f -1)
      linesize=$(du -b $1_oneline | cut -f -1)
      rm $1_oneline
      echo $(expr $filesize / $linesize)
      

      如果您将此脚本命名为lines.sh,则可以调用lines.sh bigfile.txt 以获取估计的行数。在我的情况下(大约 6 GB,从数据库导出),与真实行数的偏差仅为 3%,但运行速度快了大约 1000 倍。顺便说一句,我使用第二行而不是第一行作为基础,因为第一行有列名,而实际数据从第二行开始。

      【讨论】:

      • 对于我尝试过的所有答案 (i) cat filename | wc -l # 给我错误的答案 (ii) sed -n '$=' filename #给我错误的结果。然后我尝试使用这个脚本并给了我大约 100 万行的正确结果。谢谢+1
      • 你实际上可以在第一行不做头部而是做尾部。以及为什么是 1,取 1000,最后乘以它。如果行或多或少是随机的,它将为您提供比使用 1 行计算更精确的结果。问题是记录集是否分布不佳。那么这个数字一文不值:(
      【解决方案7】:

      Hadoop 本质上提供了一种机制来执行类似于@Ivella 所建议的事情。

      Hadoop 的 HDFS(分布式文件系统)将占用您的 20GB 文件,并将其以固定大小的块形式保存在集群中。假设您将块大小配置为 128MB,文件将被拆分为 20x8x128MB 块。

      然后,您将对这些数据运行一个 map reduce 程序,基本上计算每个块的行数(在 map 阶段),然后将这些块行数减少为整个文件的最终行数。

      至于性能,通常集群越大,性能越好(更多 wc 并行运行,在更多独立磁盘上),但是作业编排有一些开销,这意味着在较小的文件上运行作业不会实际上比运行本地 wc 产生更快的吞吐量

      【讨论】:

        【解决方案8】:

        我不确定 python 是否更快:

        [root@myserver scripts]# time python -c "print len(open('mybigfile.txt').read().split('\n'))"
        
        644306
        
        
        real    0m0.310s
        user    0m0.176s
        sys     0m0.132s
        
        [root@myserver scripts]# time  cat mybigfile.txt  | wc -l
        
        644305
        
        
        real    0m0.048s
        user    0m0.017s
        sys     0m0.074s
        

        【讨论】:

        • 你实际上是在显示python在这里比较慢。
        • Python 可以完成这项工作,但当然不能使用 ...read().split("\n") 。将其更改为sum(1 for line in open("mybigfile.txt")) ,您就有了更好的幼稚方法(即没有从 HDFS 设置中获得任何优势)
        【解决方案9】:

        如果您的瓶颈是磁盘,那么您如何从中读取数据很重要。 dd if=filename bs=128M | wc -lwc -l filenamecat filename | wc -l很多,因为我的机器有 HDD 和快速 CPU 和 RAM。您可以调整块大小并查看dd 报告的吞吐量。我把它调高到 1GiB。

        注意:关于 catdd 是否更快存在一些争论。我所声称的是dd 可以更快,这取决于系统,而且它适合我。自己试试吧。

        【讨论】:

          【解决方案10】:

          如果你的电脑有 python,你可以从 shell 试试这个:

          python -c "print len(open('test.txt').read().split('\n'))"
          

          这使用python -c 传入一个命令,该命令基本上是读取文件,并由“换行符”分割,以获得换行符的数量,或文件的总长度。

          @BlueMoon's:

          bash-3.2$ sed -n '$=' test.txt
          519
          

          使用上面的:

          bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
          519
          

          【讨论】:

          • 对 20GB 文件中的每个 \n 进行 python 解析似乎是一种非常缓慢的尝试方式。
          • 与使用 sed 相比,解决方案很糟糕。
          • 问题不在于 python 解析 "\n" - sed 和 wc 都必须这样做。可怕的是_将所有内容读入内存,然后他们要求 Python 在每个“\n”处拆分数据块(不仅复制内存中的所有数据,而且还为每一行执行相对昂贵的对象创建)跨度>
          • python -c "print(sum(1 for line in open('text.txt'))"python 中会是更好的解决方案,因为它不会将整个文件读入内存,但 sed 或 wc 会是更好的解决方案。
          【解决方案11】:
          find  -type f -name  "filepattern_2015_07_*.txt" -exec ls -1 {} \; | cat | awk '//{ print $0 , system("cat " $0 "|" "wc -l")}'
          

          输出:

          【讨论】:

            【解决方案12】:

            我有一个 645GB 的文本文件,早期的精确解决方案(例如wc -l)都没有在 5 分钟内返回答案。

            相反,这是一个 Python 脚本,它计算一个大文件中的近似行数。 (我的文本文件显然有大约 55 亿行。)Python 脚本执行以下操作:

            A.计算文件中的字节数。

            B.读取文件中的前N 行(作为示例)并计算平均行长度。

            C.将 A/B 计算为近似的行数。

            它遵循Nico's answer 的行,但不是取一行的长度,而是计算第一行N 的平均长度。

            注意:我假设一个 ASCII 文本文件,所以我希望 Python len() 函数将字符数作为字节数返回。

            将此代码放入文件line_length.py

            #!/usr/bin/env python
            
            # Usage:
            # python line_length.py <filename> <N> 
            
            import os
            import sys
            import numpy as np
            
            if __name__ == '__main__':
            
                file_name = sys.argv[1]
                N = int(sys.argv[2]) # Number of first lines to use as sample.
                file_length_in_bytes = os.path.getsize(file_name)
                lengths = [] # Accumulate line lengths.
                num_lines = 0
            
                with open(file_name) as f:
                    for line in f:
                        num_lines += 1
                        if num_lines > N:
                            break
                        lengths.append(len(line))
            
                arr = np.array(lengths)
                lines_count = len(arr)
                line_length_mean = np.mean(arr)
                line_length_std = np.std(arr)
            
                line_count_mean = file_length_in_bytes / line_length_mean
            
                print('File has %d bytes.' % (file_length_in_bytes))
                print('%.2f mean bytes per line (%.2f std)' % (line_length_mean, line_length_std))
                print('Approximately %d lines' % (line_count_mean))
            

            使用N=5000 像这样调用它。

            % python line_length.py big_file.txt 5000
            
            File has 645620992933 bytes.
            116.34 mean bytes per line (42.11 std)
            Approximately 5549547119 lines
            

            所以文件中有大约 55 亿行。

            【讨论】:

              【解决方案13】:

              让我们假设:

              • 您的文件系统是分布式的
              • 您的文件系统可以轻松地将网络连接填充到单个节点
              • 您可以像访问普通文件一样访问您的文件

              那么你真的想把文件分成几部分,在多个节点上并行计算部分,然后总结那里的结果(这基本上是@Chris White 的想法)。

              以下是使用 GNU Parallel(版本 > 20161222)的方法。您需要列出~/.parallel/my_cluster_hosts 中的节点,并且您必须拥有ssh 对所有节点的访问权限:

              parwc() {
                  # Usage:
                  #   parwc -l file                                                                
              
                  # Give one chunck per host                                                     
                  chunks=$(cat ~/.parallel/my_cluster_hosts|wc -l)
                  # Build commands that take a chunk each and do 'wc' on that                    
                  # ("map")                                                                      
                  parallel -j $chunks --block -1 --pipepart -a "$2" -vv --dryrun wc "$1" |
                      # For each command                                                         
                      #   log into a cluster host                                                
                      #   cd to current working dir                                              
                      #   execute the command                                                    
                      parallel -j0 --slf my_cluster_hosts --wd . |
                      # Sum up the number of lines                                               
                      # ("reduce")                                                               
                      perl -ne '$sum += $_; END { print $sum,"\n" }'
              }
              

              用作:

              parwc -l myfile
              parwc -w myfile
              parwc -c myfile
              

              【讨论】:

              • 你不需要原始文件的行数来决定如何分区吗?
              • 没有。它是按字节划分的,而不是行。
              【解决方案14】:

              随着较慢的 IO 回落到 dd if={file} bs=128M | wc -l,在收集数据以供 wc 处理时提供了极大的帮助。

              我也遇到过

              https://github.com/crioux/turbo-linecount

              太棒了。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-03-29
                • 2012-09-09
                • 2019-02-06
                • 1970-01-01
                • 2011-11-17
                • 2011-10-24
                相关资源
                最近更新 更多