developer tip

큰 파일의 줄 수

copycodes 2020. 11. 20. 09:04
반응형

큰 파일의 줄 수


나는 일반적으로 ~ 20Gb 크기의 텍스트 파일로 작업하며 주어진 파일의 줄 수를 매우 자주 계산합니다.

지금 내가하는 방식은 단지 cat fname | wc -l이며 매우 오래 걸립니다. 훨씬 더 빠른 해결책이 있습니까?

Hadoop이 설치된 고성능 클러스터에서 작업합니다. 지도 축소 접근 방식이 도움이 될 수 있는지 궁금합니다.

솔루션이 솔루션처럼 한 줄로 실행되는 것처럼 간단하기를 원 wc -l하지만 얼마나 실행 가능한지 잘 모르겠습니다.

어떤 아이디어?


시험: sed -n '$=' filename

또한 고양이는 불필요합니다 : wc -l filename현재의 방식으로 충분합니다.


제한 속도 요소는 저장 장치의 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 파일을 4Kb * 655360 = 2684354560 바이트 = 2.5GB의 4 개의 동일한 청크로 분할했습니다. 각 작업에 하나씩 제공됩니다. 파일의 크기에 따라 스크립트를 설정하여 수행 할 수 있습니다. 파일 및 실행할 병렬 작업의 수. 또한 실행 결과, 쉘 스크립트 기능이 부족하여 수행하지 않은 작업을 요약해야합니다.

파일 시스템이 RAID 또는 분산 파일 시스템과 같은 많은 장치간에 큰 파일을 분할하고 병렬화 할 수있는 I / O 요청을 자동으로 병렬화 할 수있을만큼 똑똑하다면, 많은 병렬 작업을 실행하지만 다음을 사용하여 분할을 수행 할 수 있습니다. 동일한 파일 경로이고 여전히 약간의 속도 향상이있을 수 있습니다.

편집 : 나에게 발생한 또 다른 아이디어는 파일 내부의 줄 크기가 같으면 파일 크기를 줄 크기로 나누어 정확한 줄 수를 얻을 수 있다는 것입니다. 단일 작업에서 거의 즉시 수행 할 수 있습니다. 평균 크기가 있고 라인 수를 정확히 신경 쓰지 않지만 추정을 원하는 경우 동일한 작업을 수행하고 정확한 작업보다 훨씬 더 빠르게 만족스러운 결과를 얻을 수 있습니다.


다중 코어 서버에서 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

내 테스트에 따라 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


데이터가 HDFS에있는 경우 가장 빠른 방법은 Hadoop 스트리밍을 사용하는 것입니다. Apache Pig의 COUNT UDF는 백에서 작동하므로 단일 감속기를 사용하여 행 수를 계산합니다. 대신 다음과 같이 간단한 hadoop 스트리밍 스크립트에서 감속기 수를 수동으로 설정할 수 있습니다.

$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으로 설정했지만이 매개 변수를 조정할 수 있습니다. 지도 축소 작업이 완료되면 각 감속기의 결과가 별도의 파일에 저장됩니다. 행의 최종 개수는 모든 감속기에서 반환 된 숫자의 합계입니다. 다음과 같이 최종 행 수를 얻을 수 있습니다.

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

하둡은 본질적으로 @Ivella가 제안하는 것과 유사한 작업을 수행하는 메커니즘을 제공합니다.

Hadoop의 HDFS (분산 파일 시스템)는 20GB 파일을 고정 크기의 블록으로 클러스터 전체에 저장합니다. 블록 크기를 128MB로 구성한다고 가정하면 파일은 20x8x128MB 블록으로 분할됩니다.

그런 다음이 데이터에 대해 맵 축소 프로그램을 실행하여 기본적으로 각 블록 (맵 단계에서)의 라인 수를 세고이 블록 라인 수를 전체 파일의 최종 라인 수로 줄입니다.

성능에 관해서는 일반적으로 클러스터가 클수록 성능이 향상되지만 (더 많은 wc가 병렬로 실행되고 더 많은 독립 디스크에서 실행 됨) 작업 오케스트레이션에 약간의 오버 헤드가 있습니다. 즉, 더 작은 파일에서 작업을 실행해도 실제로 더 빨리 생성되지는 않습니다. 로컬 화장실을 실행하는 것보다 처리량


나는 질문은 지금 몇 살 알지만에 확대 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. 필자의 경우 (약 6GB, 데이터베이스에서 내보내기) 실제 행 수와의 편차는 3 %에 불과했지만 약 1000 배 빠르게 실행되었습니다. 그건 그렇고, 첫 번째 줄에는 열 이름이 있고 실제 데이터는 두 번째 줄에서 시작 되었기 때문에 첫 번째가 아닌 두 번째 줄을 기준으로 사용했습니다.


파이썬이 더 빠르지 잘 모르겠습니다.

[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

If your bottleneck is the disk, it matters how you read from it. dd if=filename bs=128M | wc -l is a lot faster than wc -l filename or cat filename | wc -l for my machine that has an HDD and fast CPU and RAM. You can play around with the block size and see what dd reports as the throughput. I cranked it up to 1GiB.

Note: There is some debate about whether cat or dd is faster. All I claim is that dd can be faster, depending on the system, and that it is for me. Try it for yourself.


If your computer has python, you can try this from the shell:

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

This uses python -c to pass in a command, which is basically reading the file, and splitting by the "newline", to get the count of newlines, or the overall length of the file.

@BlueMoon's:

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

Using the above:

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

find  -type f -name  "filepattern_2015_07_*.txt" -exec ls -1 {} \; | cat | awk '//{ print $0 , system("cat " $0 "|" "wc -l")}'

Output:


Let us assume:

  • Your file system is distributed
  • Your file system can easily fill the network connection to a single node
  • You access your files like normal files

then you really want to chop the files into parts, count parts in parallel on multiple nodes and sum up the results from there (this is basically @Chris White's idea).

Here is how you do that with GNU Parallel (version > 20161222). You need to list the nodes in ~/.parallel/my_cluster_hosts and you must have ssh access to all of them:

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" }'
}

Use as:

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

참고URL : https://stackoverflow.com/questions/12716570/count-lines-in-large-files

반응형