문맥
나는 각각 약 300K의 표 형식으로 날짜가 지정된 수천 개의 zip 파일을 포함하는 디렉토리를 가지고 있습니다 YYYYMMDD_hhmmss.zip
. 각 zip 파일에는 약 400개의 xml 파일이 있으며 각 파일의 크기는 약 3K입니다.
질문
zip 파일의 날짜 범위 내에서 특정 문자열을 검색하고 찾을 수 있어야 합니다.
현재 (보통이지만) 솔루션
나는 다음 줄을 가지고 있습니다
find /home/mydir/ -type f | sort | \
awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/" | \
xargs -n 1 -P 10 zipgrep "my search string"
요점은
- 내 1000개 파일 디렉터리의 모든 파일 나열
- 이 파일 목록 정렬
- 주어진 날짜를 기준으로 파일 범위 검색(이
awk
명령은 첫 번째 일치하는 문자열 뒤의 줄과 두 번째 일치하는 문자열 앞의 줄만 인쇄합니다) - 단일 파일에 해당하는 각 결과 줄을 다음으로 전달합니다.
zipgrep
질문
24코어 시스템에 10개의 프로세스가 있어도 이 코드 한 줄은 매우 느리게 실행됩니다. zipgrep
명령 때문에 느리다고 생각 하지만, 개선 방법을 알 만큼 똑똑하지는 않습니다. 이렇게 해야할지 모르겠지만 동료가 이 스크립트보다 빠르게 실행되는 Java 도구를 작성했기 때문에 조금 당황스럽습니다. 가능하다면 이것을 뒤집고 싶습니다. 그렇다면 이 경우 이 명령을 더 빠르게 만드는 방법을 아는 사람이 있습니까? 아니면 어떤 부분을 개선하시겠습니까?
답변1
쉽게 개선할 수 있는 구간이 하나 있는데, 가장 느린 구간은 아닙니다.
find /home/mydir/ -type f | sort | \ awk "/xml_20140207_000016.zip/,/xml_20140207_235938.zip/"
이는 먼저 모든 파일을 나열한 다음 파일 이름을 정렬하고 관심 있는 파일을 추출하므로 약간 낭비입니다. find
정렬을 시작하려면 먼저 명령을 완료해야 합니다.
먼저 관심 있는 파일만 나열하거나 최소한 가능한 가장 작은 상위 집합을 나열하는 것이 더 빠릅니다. names 에 대해 보다 세밀한 필터가 필요한 경우 find
awk로 파이프하되 정렬하지는 마세요. awk 및 기타 줄별 필터는 한 줄씩 처리할 수 있지만 정렬에는 전체 입력이 필요합니다.
find /home/mydir/ -name 'xml_20140207_??????.zip' -type f | \
awk 'match($0, /_[0-9]*.zip$/) &&
(time = substr($0, RSTART+1, RLENGTH-5)) &&
time >= 16 && time <= 235938' |
xargs -n 1 -P 10 zipgrep "my search string"
가장 분명하게 차선책인 부분은 zipgrep입니다. 쉘 프로그래밍의 한계로 인해 성능을 향상시키는 쉬운 방법은 없습니다. zipgrep 스크립트는 아카이브의 파일 이름을 나열하고 grep
각 파일의 내용을 하나씩 호출하는 방식으로 작동합니다. 이는 zip 아카이브의 모든 파일이 계속해서 구문 분석된다는 것을 의미합니다. Java 프로그램(또는 Perl, Python, Ruby 등)은 파일을 한 번만 처리하여 이를 방지할 수 있습니다.
쉘 프로그래밍을 고수하려면 zipgrep을 사용하는 대신 각 zip을 마운트해 볼 수 있습니다.
… | xargs -n1 -P2 sh -c '
mkdir "mnt$$-$1";
fuse-zip "$1" "mnt$$-$1";
grep -R "$0" "mnt$$-$1"
fusermount -u "mnt$$-$1"
' "my search string"
병렬 처리는 그다지 도움이 되지 않습니다. 대부분의 설정에서 제한 요소는 CPU 시간이 아니라 디스크 I/O 대역폭입니다.
벤치마킹한 적은 없지만, 가장 큰 개선 분야는 언어에서 zipgrep의 더욱 강력한 구현을 사용하는 것이라고 생각합니다.
답변2
몇 가지 빠른 생각;
- 모든 파일이 하나의 디렉토리에 있으면 삭제할 수 있습니다
find
sort
파일 이름 규칙은 날짜별로 정렬되므로 해당 비트 도 필요하지 않습니다 .이 두 부분이 해결되면 날짜 범위가 알려진 경우 awk 대신 간단한 파일 이름 glob을 사용할 수 있습니다. 예를 들어(쉘이 이라고 가정
bash
):하루 동안의 모든 파일
echo xml_20140207_*.zip | xargs -n 1 -P 10 zipgrep "my search string"
2014년 2월 7일 또는 2월 10일 15:00~18:00 사이에 생성된 파일:
echo xml_201402{07,10}_1{5..7}*.zip | xargs -n 1 -P 10 zipgrep "my search string"
답변3
병목 현상이 발생하는 위치가 명확하지 않습니다. 파일을 읽고 있다고 가정해 봅시다. 스토리지 시스템에 따라 처리하기 전에 전체 파일을 읽는 것이 더 빠를 수도 있습니다. 이는 파일에 대해 여러 번의 검색 시도가 이루어진 경우 특히 그렇습니다 zipgrep
. 파일이 메모리에 완전히 저장되지 않은 경우 디스크가 검색을 수행할 때까지 기다립니다.
find ... | parallel -j1 'cat {} >/dev/null; echo {}' | parallel zipgrep "my search string"
위의 cat
코드는 한 번에 하나의 파일을 메모리 캐시에 넣은 다음 zipgrep
CPU당 한 번에 하나씩 파일을 실행한 다음 메모리 캐시에서 읽습니다.
저는 RAID 시스템을 사용했는데 10개의 파일을 병렬로 읽는 것이 한 번에 1개의 파일을 읽거나 30개의 파일을 병렬로 읽는 것보다 6배 더 빨랐습니다. 이 RAID 시스템에서 위의 작업을 실행해야 -j1
한다면 -j10
.
대신 GNU Parallel을 사용하면 xargs
출력 혼합으로부터 자신을 보호할 수 있습니다 (참조http://www.gnu.org/software/parallel/man.html#DIFFERENCES-BETWEEN-xargs-AND-GNU-Parallel).