$file1
에 저장하기 위해 추출하고 싶습니다 $file2
.
$file1
크기는 4GB이고 행 수는 약 2천만 개 이며 크기 $file2
는 약 140MB이며 ,
두 파일의 최대 행 길이는 1000보다 훨씬 낮습니다. 문자 .LC_ALL=C
$file1
\0
이 명령을 예상하지 못했습니다
parallel --pipepart -a $file1 grep -Ff $file2
많은 메모리를 소비하고 운영 체제에 의해 종료됩니다.
스레드 수를 제한하면 다음 명령이 작동합니다.
parallel --pipepart -j 8 -a $file1 grep -Ff $file2
마지막 명령의 경우 htop은 각 grep -Ff $file2
스레드가 계속해서 12.3GB의 메모리를 소비한다는 것을 보여줍니다. 나는 이 요구사항이 grep 에 의해 구축된 사전에서 나온 것이라고 가정합니다 $file2
.
이러한 필터를 보다 효율적으로 구현하려면 어떻게 해야 합니까?
답변1
그것은 덮여있다man parallel
https://www.gnu.org/software/parallel/man.html#예: -Grepping-n-lines-for-m-regular-expressions
예: n줄에서 m개의 정규식을 찾습니다.
정규식이 많은 대용량 파일을 grep하는 가장 간단한 솔루션은 다음과 같습니다.
grep -f regexps.txt bigfile
또는 정규식이 고정 문자열인 경우:
grep -F -f regexps.txt bigfile
CPU, RAM, 디스크 I/O라는 3가지 제한 요소가 있습니다.
RAM은 측정하기 쉽습니다. grep 프로세스가 사용 가능한 메모리의 대부분을 차지하는 경우(예: top 실행 시) RAM은 제한 요소입니다.
CPU는 측정하기도 쉽습니다. grep이 CPU의 90%를 초과하는 경우 CPU가 제한 요소이므로 병렬화가 속도를 높입니다.
디스크 I/O가 제한 요소인지 확인하기 어렵고 디스크 시스템에 따라 병렬화가 더 빨라질 수도 있고 느려질 수도 있습니다. 확실히 알 수 있는 유일한 방법은 테스트하고 측정하는 것입니다.
제한 요소: 메모리
큰 파일에 대한 일반 grep -f regexs.txt는 크기에 관계없이 작동하지만 regexps.txt가 너무 커서 메모리에 맞지 않으면 분할해야 합니다.
grep -F에는 약 100바이트의 RAM이 필요한 반면, grep에는 정규 표현식 1바이트당 약 500바이트의 RAM이 필요합니다. 따라서 regexps.txt가 RAM의 1%를 차지한다면 아마도 너무 큰 것입니다.
정규식을 고정 문자열로 변환할 수 있다면 그렇게 하십시오. 예를 들어, 대용량 파일에서 모두 다음과 같은 줄을 찾는 경우:
ID1 foo bar baz Identifier1 quux fubar ID2 foo bar baz Identifier2
그런 다음 regexps.txt를 다음에서 변환할 수 있습니다.
ID1.*Identifier1 ID2.*Identifier2
입력하다:
ID1 foo bar baz Identifier1 ID2 foo bar baz Identifier2
이렇게 하면 약 80% 더 적은 메모리를 사용하고 더 빠른 grep -F를 사용할 수 있습니다.
그래도 메모리에 맞지 않으면 다음을 수행할 수 있습니다.
parallel --pipepart -a regexps.txt --block 1M grep -Ff - -n bigfile | sort -un | perl -pe 's/^\d+://'
1M은 사용 가능한 메모리를 CPU 스레드 수(grep -F의 경우 200, 일반 grep의 경우 1000)로 나눈 값입니다. GNU/Linux에서는 다음을 수행할 수 있습니다:
free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 } END { print sum }' /proc/meminfo) percpu=$((free / 200 / $(parallel --number-of-threads)))k parallel --pipepart -a regexps.txt --block $percpu --compress \ grep -F -f - -n bigfile | sort -un | perl -pe 's/^\d+://'
중복된 줄과 잘못된 순서를 허용할 수 있다면 다음을 수행하는 것이 더 빠릅니다.
parallel --pipepart -a regexps.txt --block $percpu --compress \ grep -F -f - bigfile
제한 요소: CPU
CPU가 제한 요소인 경우 정규식을 병렬화해야 합니다.
cat regexp.txt | parallel --pipe -L1000 --round-robin --compress \ grep -f - -n bigfile | sort -un | perl -pe 's/^\d+://'
이 명령은 CPU당 grep을 시작하고 CPU당 한 번 큰 파일을 읽습니다. 그러나 이 작업은 병렬로 수행되므로 첫 번째 읽기를 제외한 모든 읽기가 RAM에 캐시됩니다. regexp.txt의 크기에 따라 -L1000 대신 --block 10m을 사용하는 것이 더 빠를 수도 있습니다.
일부 스토리지 시스템은 여러 블록을 병렬로 읽을 때 성능이 더 좋습니다. 이는 일부 RAID 시스템 및 일부 네트워크 파일 시스템에 해당됩니다. 대용량 파일을 병렬로 읽기:
parallel --pipepart --block 100M -a bigfile -k --compress \ grep -f regexp.txt
그러면 빅파일이 100MB 청크로 분할되고 각 청크에 대해 grep이 실행됩니다. bigfile과 regexp.txt를 병렬로 읽으려면 --fifo를 사용하여 두 가지를 결합하십시오.
parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \ \| parallel --pipe -L1000 --round-robin grep -f - {}
두 개 이상의 정규 표현식과 일치하면 한 줄이 중복될 수 있습니다.
더 큰 문제
문제가 너무 커서 해결할 수 없다면 아마도 Lucene을 사용할 준비가 된 것입니다.