파일의 시작 부분에서 압축 해제하는 것보다 더 효율적인 작업을 수행 할 수 있는지 궁금했습니다. 대답은 '아니오'인 것으로 보입니다. 그러나 일부 CPU (Skylake) zcat | tail
에서는 CPU를 최대 클럭 속도로 올리지 않습니다. 아래를 참조하십시오. 사용자 지정 디코더는이 문제를 피하고 파이프 쓰기 시스템 호출을 저장할 수 있으며 ~ 10 % 더 빠를 수 있습니다. 전원 관리 설정을 조정하지 않으면 Skylake에서 ~ 60 % 더 빠릅니다.
skipbytes
함수를 사용하여 사용자 정의 zlib로 할 수있는 최선의 방법 은 압축 해제 블록을 실제로 재구성하는 작업을 수행하지 않고 압축 블록의 기호를 구문 분석하여 끝까지 얻는 것입니다. 동일한 버퍼를 덮어 쓰고 파일에서 앞으로 이동하기 위해 zlib의 일반 디코드 기능을 호출하는 것보다 훨씬 빠를 수 있습니다 (아마도 2 배 이상). 그러나 나는 누군가가 그런 기능을 썼는지 모른다. (그리고 디코더가 특정 블록에서 다시 시작할 수 있도록 파일을 특별히 작성하지 않으면 실제로 작동하지 않는다고 생각합니다).
그 것 때문에 나는 그들을 디코딩하지 않고 공기를 빼다 블록을 건너 뛸 수있는 방법이 있었다 기대했다 훨씬 더 빨리. 허프만 트리는 각 블록의 시작 부분에 전송되므로 모든 블록의 시작 부분부터 디코딩 할 수 있습니다 (제 생각에는). 오, 나는 디코더 상태가 허프만 트리 이상이라고 생각합니다. 이것은 이전 32kiB의 디코딩 된 데이터이기도하며 기본적으로 블록 경계를 넘어서 재설정되거나 잊혀지지 않습니다. 동일한 바이트가 계속 반복해서 참조 될 수 있으므로 거대한 압축 파일에서 문자 그대로 한 번만 나타날 수 있습니다. (예를 들어, 로그 파일에서 호스트 이름은 압축 사전에서 항상 "핫"상태를 유지하며 모든 인스턴스는 첫 번째 이름이 아닌 이전 이름을 참조합니다).
이 zlib
매뉴얼 에서는 압축 스트림을 그 시점 까지 검색하려면 Z_FULL_FLUSH
호출 deflate
할 때 사용해야한다고 말합니다 . 그것은 "압축 상태를 재설정"하므로, 그것 없이는 역 참조가 이전 블록으로 들어갈 수 있다고 생각합니다. 따라서 zip 파일이 때때로 전체 플러시 블록으로 작성되지 않는 한 (모든 1G 또는 압축에 무시할만한 영향을 줄 수있는 것처럼), 처음보다 원하는 지점까지 디코딩 작업을 더 많이 수행해야한다고 생각합니다. 생각. 나는 당신이 아마 어떤 블록의 시작에서 시작할 수 없을 것 같아요.
나머지 부분은 원하는 첫 번째 바이트가 포함 된 블록의 시작 부분을 찾고 거기에서 디코딩하는 것이 가능할 것이라고 생각하는 동안 작성되었습니다.
그러나 불행히도 Deflate 블록의 시작은 압축 블록의 길이를 나타내지 않습니다 . 압축 불가능한 데이터는 16 비트 크기의 바이트 단위 인 압축되지 않은 블록 형식으로 코딩 할 수 있지만 압축 블록은 그렇지 않습니다. RFC 1951은 형식을 매우 읽기 쉽게 설명합니다 . 동적 허프만 코딩을 사용하는 블록은 블록 앞쪽에 트리가 있으므로 (압축 해제 기는 스트림에서 탐색 할 필요가 없으므로) 컴프레서는 기록하기 전에 전체 (압축 된) 블록을 메모리에 보관해야합니다.
최대 역 참조 거리는 32kiB에 불과하므로 컴프레서는 압축되지 않은 데이터를 메모리에 많이 보관할 필요는 없지만 블록 크기를 제한하지는 않습니다. 블록의 길이는 여러 메가 바이트 일 수 있습니다. (이것은 디스크를 파싱하지 않고 현재 블록의 끝을 찾을 수 있다면 디스크로의 자기 탐색, 메모리로의 순차적 읽기 및 RAM에서 데이터 건너 뛰기에도 디스크 탐색이 가치가 있기에 충분합니다).
zlib은 가능한 한 블록을 만든다 :
Marc Adler에 따르면 , zlib는 심볼 버퍼가 채워질 때만 새로운 블록을 시작한다. 기본 설정은 16,383 개의 심볼 (리터럴 또는 매치)이다
나는 seq
(매우 중복적이고 따라서 큰 테스트는 아니지만) 출력을 pv < /tmp/seq1G.gz | gzip -d | tail -c $((1024*1024*1000)) | wc -c
gzpping했지만 Skylake i7-6700k에서 3.9GHz의 DDR4-2666 RAM으로 ~ 62 MiB / s의 압축 데이터로 실행됩니다. 246MiB / s의 압축 해제 된 데이터 memcpy
입니다. 캐시 크기에 비해 너무 큰 블록 크기의 경우 ~ 12GiB / s의 속도 와 비교할 때 급격한 변화 입니다.
(함께 energy_performance_preference
기본으로 설정 balance_power
하는 대신 balance_performance
, 스카이 레이크의 내부 CPU 총재는 압축 된 데이터의 S / ~ 43 MiB 크기 만 2.7GHz의에서 실행하기로 결정합니다. 내가 사용 sudo sh -c 'for i in /sys/devices/system/cpu/cpufreq/policy[0-9]*/energy_performance_preference;do echo balance_performance > "$i";done'
을 조정할 수 있습니다. 아마 빈번한 시스템 호출이 실제 CPU 바인딩처럼 보이지 않는다 전원 관리 장치로 작업하십시오.)
TL : DR : 디스크 zcat | tail -c
가 매우 느린 경우가 아니면 고속 CPU에서도 CPU가 바인드 됩니다. gzip은 실행 한 CPU의 100 %를 사용하고 (에 따라 시계 당 1.81 개의 명령을 실행 perf
) tail
실행 한 CPU의 0.162를 사용했습니다 (0.58 IPC). 그렇지 않으면 시스템이 대부분 유휴 상태였습니다.
Meltdown 문제를 해결하기 위해 KPTI가 기본적 으로 활성화 된 Linux 4.14.11-1-ARCH를 사용하고 있으므로 모든 write
시스템 호출 gzip
이 이전보다 비쌉니다.
데 시크 내장에 unzip
또는 zcat
(여전히 정기적으로 사용하여 zlib
디코딩 기능) 모든 파이프 쓰기를 절약 할 수 및 전체 클럭 속도로 작동하는 스카이 레이크 CPU를 얻을 것입니다. (일부 종류의로드에 대한이 다운 클럭킹은 Intel Skylake 이상에서 고유하며, CPU에서 수행하는 작업에 대한 더 많은 데이터가 있고 더 빠르게 증가 / 감소 할 수 있기 때문에 OS에서 CPU 주파수 의사 결정을 오프로드합니다. 일반적으로 좋지만 여기서는 더 보수적 인 주지사 설정으로 Skylake가 최고 속도로 올라가지 않습니다.
원하는 시작 바이트 위치에 도달 할 때까지 L2 캐시에 맞는 버퍼를 다시 작성하면 시스템 호출이 발생하지 않을 수 있습니다. 아마 10 % 일지 모르지만 난 여기서 숫자를 만들어 내고 있습니다. zlib
캐시 풋 프린트의 크기와 모든 시스템 호출에서 TLB 플러시 (및 따라서 uop- 캐시 플러시)가 KPTI를 사용할 때 얼마나 많은지 알기 위해 세부적으로 프로파일 링하지 않았습니다 .
gzip 파일 형식에 탐색 색인을 추가하는 몇 가지 소프트웨어 프로젝트가 있습니다 . 검색 가능한 압축 파일을 생성 할 수있는 사람이 없다면 도움이되지 않지만 다른 독자들도 도움이 될 수 있습니다.
아마도 어느 쪽도이 프로젝트의 인덱스 때 만 작동하도록 설계하고 있기 때문에, 인덱스없이 공기를 빼다 스트림을 통해 생략하는 방법을 알고있는 디코드 기능이없는 것입니다 사용할 수 있습니다.