대용량 파일의 중간 부분 읽기

대용량 파일의 중간 부분 읽기

1TB 파일이 있습니다. 바이트 12345678901에서 바이트 19876543212까지 읽고 100MB RAM이 있는 컴퓨터의 stdout에 저장하고 싶습니다.

이를 수행하기 위해 Perl 스크립트를 쉽게 작성할 수 있습니다. sysread는 700MB/s를 제공하지만(양호함) syswrite는 30MB/s만 제공합니다. 나는 좀 더 효율적인 것을 원합니다. 가급적이면 모든 Unix 시스템에 설치되고 1GB/s를 제공할 수 있는 것을 원합니다.

나의 첫 번째 생각은 다음과 같습니다.

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

그러나 이는 그리 효율적이지 않습니다.

편집하다:

syswrite 오류를 어떻게 측정했는지 모르겠습니다. 이는 3.5GB/s를 제공합니다.

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

그리고 yes | dd bs=1024k count=10 | wc악몽을 피하세요.

답변1

블록 크기가 작기 때문에 속도가 느립니다. 최신 GNU dd(코어틸스 v8.16+), 가장 쉬운 방법은 skip_bytescount_bytes옵션을 사용하는 것입니다.

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

고쳐 쓰다

fullblock위에 추가된 옵션@길스 답변. 처음에는 이것이 암시적인 것일 수도 있다고 생각했지만 count_bytes그렇지 않았습니다.

언급된 문제는 아래의 잠재적인 문제입니다. dd어떤 이유로든 읽기/쓰기 호출이 중단되면 데이터가 손실됩니다. 대부분의 경우에는 그럴 가능성이 없습니다(파이프가 아닌 파일에서 읽기 때문에 확률이 줄어듭니다).


and 옵션 dd없이 a를 사용하는 것이 더 어렵습니다 .skip_bytescount_bytes

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

다양한 블록 크기를 시도해 볼 수도 있지만 그 효과는 극적이지 않습니다. 바라보다 -dd의 bs 매개변수에 가장 적합한 값을 결정하는 방법이 있습니까?

답변2

bs=1dd한 번에 한 바이트씩 읽고 쓰라고 지시합니다 . 각 read및 호출에는 오버헤드가 발생하므로 write속도가 느려집니다. 좋은 성능을 위해서는 더 큰 블록 크기를 사용하십시오.

적어도 Linux에서는 전체 파일을 복사했을 때 다음을 발견했습니다.cp그리고 cat보다dd, 더 큰 블록 크기를 지정하더라도 마찬가지입니다.

파일 의 일부만 tail복사 하려면 head. Linux에 대한 빠른 벤치마크에 따르면 아마도 파이프 때문에 속도가 느린 것으로 나타났습니다.head -ctail -chead -cdd

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

문제 dd신뢰할 수 없습니다. 부분 데이터를 복사할 수 있습니다.. 내가 아는 한 dd일반 파일을 읽고 쓸 때는 안전합니다.dd는 언제 데이터 복사에 적합합니까? (또는 read() 및 write()가 부분적인 경우)- 하지만신호에 의해 방해받지 않는 한. GNU coreutils를 사용하면 플래그를 사용할 수 있지만 fullblock이식 가능하지 않습니다.

또 다른 문제 dd는 건너뛴 바이트 수와 전송된 바이트 수가 모두 블록 크기의 배수여야 하기 때문에 유효한 블록 수를 찾기가 어렵다는 것입니다. 여러 호출을 사용할 수 있습니다 dd. 하나는 첫 번째 부분 블록을 복사하고, 하나는 정렬된 블록의 대부분을 복사하고, 다른 하나는 마지막 부분 블록을 복사합니다. - 참조그레이엄의 대답쉘 조각을 얻으십시오. 하지만 스크립트를 실행할 때 해당 플래그를 사용하지 않는 한 모든 데이터가 복사 fullblock되도록 기도해야 한다는 점을 잊지 마세요 . 복사가 완료되지 않은 경우 0이 아닌 상태가 반환되므로 오류를 쉽게 감지할 수 있지만 이를 수정할 수 있는 실질적인 방법은 없습니다.dddd

POSIX는 쉘 수준에서 더 나은 것을 제공하지 않습니다. 내 제안은 작은 전용 C 프로그램을 작성하는 것입니다(구현하는 내용에 따라 dd_done_right또는 tail_head라고 부를 수 있습니다 mini-busybox).

답변3

그리고 dd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

또는 다음을 사용하십시오 losetup.

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

그런 다음 dd... cat장치를 반복합니다.

답변4

다음을 수행할 수 있습니다.

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

그것이 정말로 필요한 전부입니다. 더 이상은 아닙니다. 첫째, 일반 파일 입력을 거의 즉시 초과 dd count=0 skip=1 bs=$block_size1할 수 있습니다 . lseek()기회 없음데이터 손실또는 다른 사실이 아닌 주장이 무엇이든 어디서부터 시작하고 싶은지 찾아보세요. 파일 설명자는 쉘이 소유하고 는 dd이를 상속받기 때문에 커서 위치에 영향을 주어 단계별로 진행할 수 있습니다. 정말 간단합니다. 작업에 이보다 더 적합한 표준 도구는 없습니다 dd.

일반적으로 이상적인 64k 블록 크기를 사용합니다. 일반적인 믿음과는 달리, 블록 크기가 크다고 해서 작업 속도가 빨라지는 것은 아닙니다 dd. 반면에 작은 버퍼도 좋지 않습니다. dd데이터가 메모리에 복사되고 다시 복사될 때까지 기다리거나 시스템 호출을 기다릴 필요가 없도록 시스템 호출에서 시간을 동기화해야 합니다. 따라서 다음 작업이 마지막 작업을 기다릴 필요가 없도록 충분한 시간이 걸리도록 하고 read(), 필요한 것보다 더 많이 버퍼링하지 않도록 하려고 합니다.

그래서 첫 번째는 dd시작 위치로 점프합니다. 이것이 필요하다시간. 이 시점에서 표준 입력을 읽고 싶은 다른 프로그램을 호출할 수 있으며 필요한 바이트 오프셋에서 직접 읽기 시작합니다. 다른 사람 dd한테 읽어달라고 했더니((interval / blocksize) -1)표준 출력에 대한 블록 수를 계산합니다.

마지막으로 해야 할 일은 모듈러스를 복사하는 것입니다.(그렇다면)이전 분할 작업. 그게 다야.

그런데, 사람들이 증거도 없이 면전에서 사실을 진술한다면 믿지 마세요. 예, dd짧은 읽기가 가능합니다(정상적인 블록 장치에서 읽을 때는 이런 일이 발생할 가능성이 낮지만 이름이 붙여진 것입니다). 이는 블록 장치 외부에서 읽은 스트림을 적절하게 버퍼링하지 않는 경우 dd에만 가능합니다 . 예를 들어:

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

두 경우 모두 dd복사가 필요합니다모두자료. 첫 번째 시나리오가 가능합니다.(가능성은 낮지만 cat)복사된 출력 블록 중 일부는 표준으로 dd인해 "$num" 바이트와 동일합니다.dd오직명령줄에서 버퍼가 특별히 요청되면 무엇이든 버퍼링될 수 있습니다. bs=을 나타냅니다최고블록 크기 때문에목적dd실시간 I/O입니다 .

dd두 번째 예에서는 전체 쓰기가 발생할 때까지 출력 블록 크기와 버퍼 읽기를 명시적으로 지정합니다 . 이는 입력을 기반으로 하는 블록에 영향을 미치지 않지만 count=이를 위해서는 다른 블록만 필요합니다 dd. 그렇지 않으면 제공된 오류 메시지는 무시되어야 합니다.

관련 정보