计算大文件中的行数


71

我通常使用大约20 Gb大小的文本文件,并且发现自己经常对给定文件中的行数进行计数。

我现在要做的就是 cat fname | wc -l,而且需要很长时间。有什么解决方案会更快吗?

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

我希望该解决方案像解决方案一样简单,只需一条线即可wc -l,但不确定其可行性。

有任何想法吗?


每个节点都已经有该文件的副本吗?
伊格纳西奥·巴斯克斯

谢谢。是。但是要访问许多节点,我使用的LSF系统有时会显示出令人讨厌的等待时间,这就是为什么理想的解决方案是在一个节点中使用hadoop / mapreduce但可以使用其他节点(然后增加等待时间)的原因可能会比仅用猫厕所的方法放慢速度)
Dnaiel 2012年

3
wc -l fname可能更快。您也可以尝试一下vim -R fname是否更快(它应该告诉您启动后的行数)。
ott-- 2012年

1
:你可以用一头猪脚本这里做它看到我的答复stackoverflow.com/questions/9900761/...
嫩Rotem公司-Gal一盎司

更快一点是要记住对cat规则的无用用法
arielf

Answers:


106

尝试: sed -n '$=' filename

猫也是不必要的:wc -l filename用您现在的方式就足够了。


嗯,很有趣。地图/缩小方法会有所帮助吗?我假设如果将所有文件都保存为HDFS格式,然后尝试使用map / reduce来计算行数会更快,不是吗?
丹尼尔(Dnaiel)2012年

@lvella。这取决于它们的实现方式。以我的经验来看sed,速度更快。也许,进行一些基准测试可以更好地理解它。
2012年

@KingsIndian。确实,刚刚尝试过sed,它在3Gb文件中比wc快3倍。谢谢KingsIndian。
Dnaiel 2012年

32
@Dnaiel如果我想我会说您wc -l filename先运行,然后您运行sed -n '$=' filename,则在第一次运行时wc必须从磁盘读取所有文件,因此可以将其完全缓存在您可能大于3Gb的内存中,因此sed接下来可以更快地运行。我自己在具有6Gb RAM的计算机上对一个4Gb文件进行了测试,但是我确保文件已经在缓存中了。得分:sed-0m12.539s,wc -l-0m1.911s。所以wc要快6.56倍。重做实验,但在每次运行前清除缓存,它们都花了大约58秒才能完成。
lvella 2012年

1
使用sed的此解决方案具有不需要行尾字符的附加优点。WC计数结束的换行符(“\ n”),所以如果你有,比如说,在文件中的一行没有\ n,则WC将返回0的sed将正确返回1
SevakPrime

14

限制速度的因素是存储设备的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请求,则可以执行这样的拆分,并运行许多并行作业,但是可以使用相同的文件路径,您仍然可以提高速度。

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


8

在多核服务器上,使用并行GNU并行计算文件行数。在打印每个文件的行数之后,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读取文件的答案。
sudo

8

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

time grep -c $ my_file.txt;

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

time wc -l my_file.txt;

真实的0m37.57s用户0m33.48s sys 0m3.97s

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

实际0m38.22s用户0m28.05s sys 0m10.14s

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

实际0m23.38s用户0m20.19s sys 0m3.11s

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

真实0m19.90s用户0m16.76s sys 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以获取运行时。
Javad

刚刚意识到我已经在基于AIX的系统上执行这些测试,并且它不支持我期望的方式的time关键字
Pramod Tiwari

FWIW,我认为您不能指望这些时间在所有操作系统的“ wc -l”上保持一致,对我而言,计数1.1gb日志文件中的行数要比awk快。塞德很慢。感谢您显示选项!
彼得·特纳

我完全同意你的观点。当然,这在很大程度上取决于这些实用程序在不同OS上的优化。我不确定这些小型实用程序如何设计成不同的风格。感谢您提出这个观点。
Pramod Tiwari

6

如果您的数据位于HDFS上,则最快的方法可能是使用hadoop流。Apache Pig的COUNT UDF在袋子上运行,因此使用单个化简器来计算行数。相反,您可以在一个简单的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的结果将存储在一个单独的文件中。行的最终计数是所有减速器返回的数字之和。您可以按以下方式获得行的最终计数:

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

4

我知道这个问题已经有好几年了,但是根据Ivella的最后一个想法,这个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#给我错误的结果。然后,我尝试使用此脚本,并给我大约一百万行的正确结果。感谢+1
Sanket Thakkar

实际上,在第一行中,您实际上不是头而是尾。以及为什么1,取1000,最后再乘回去。如果行或多或少是随机的,则使用1行calc会给您更精确的结果。问题是记录集分布不均。然后,这个数字没什么看头:(
АлексейЛещук

3

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

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

然后,您将对此数据运行map reduce程序,实质上是在map阶段对每个块的行进行计数,然后将这些块的行数减少为整个文件的最终行数。

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


2

我不确定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速度较慢。
Arnaud Potier

1
Python可以胜任,但 肯定不能...read().split("\n")。为此sum(1 for line in open("mybigfile.txt")) ,您将拥有更好的幼稚方法(即不从HDFS设置中获得任何好处)
jsbueno 2015年

2

如果您的瓶颈是磁盘,那么如何读取磁盘就很重要。dd if=filename bs=128M | wc -l很多快于wc -l filenamecat filename | wc -l我的机器有一个硬盘和快速的CPU和RAM。您可以尝试使用块大小,并查看dd报告的吞吐量。我把它提高到了1GiB。

注意:有一个关于是否有些争论cat或者dd是更快的。我所声称的是dd,根据系统的不同,它可能会更快,并且这对我来说是正确的。自己尝试一下。


1

如果您的计算机装有python,则可以从外壳程序中尝试使用此命令:

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

这使用 python -c传递一个命令,该命令基本上是读取文件,然后按“换行符”进行拆分,以获取换行数或文件的总长度。

@BlueMoon的

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

使用上面的:

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

7
对20GB文件中的每个\ n进行python解析似乎是尝试执行此操作的非常慢的方法。
mikeschuld 2014年

1
与使用sed相比,解决方案糟透了。
PureW

1
问题不是python解析“ \ n”-sed和wc都必须这样做。可怕的是,将所有内容都读入内存,然后他们要求Python在每个“ \ n”处拆分数据块(不仅复制内存中的所有数据,而且还要为每行执行相对昂贵的对象创建)
jsbueno

python -c "print(sum(1 for line in open('text.txt'))"python中将是更好的解决方案,因为它不会将整个文件读入内存,但是sed或wc会是更好的解决方案。
zombieguru


0

让我们假设:

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

那么您真的想将文件分成多个部分,在多个节点上并行计数部分,然后从那里总结结果(这基本上是@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

您是否不需要原始文件的行数来决定如何对它进行分区?
亚历克斯·雷诺兹

不。它是按字节划分的,而不是行。
Ole Tange

0

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

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

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

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

C.将A / B计算为大约的行数。

它遵循Nico的答案,但不是计算一行的长度,而是计算前几行的平均长度N

注意:我假设使用ASCII文本文件,因此我希望Pythonlen()函数返回chars作为字节数。

将此代码放入文件中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亿行。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.