Đọc giữa một tập tin lớn


19

Tôi có một tệp 1 TB. Tôi muốn đọc từ byte 12345678901 đến byte 19876543212 và đặt nó trên đầu ra tiêu chuẩn trên một máy có RAM 100 MB.

Tôi có thể dễ dàng viết một kịch bản perl làm điều này. sysread cung cấp 700 MB / s (cũng tốt), nhưng syswrite chỉ cung cấp 30 MB / s. Tôi muốn một cái gì đó hiệu quả hơn, tốt nhất là thứ gì đó được cài đặt trên mọi hệ thống Unix và có thể phân phối theo thứ tự 1 GB / s.

Ý tưởng đầu tiên của tôi là:

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

Nhưng điều đó không hiệu quả.

Chỉnh sửa:

Tôi không biết làm thế nào tôi đo syswrite sai. Điều này mang lại 3,5 GB / 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

và tránh yes | dd bs=1024k count=10 | wccơn ác mộng.


lệnh của bạn vớibs=1M iflag=skip_bytes,count_bytes
frostschutz

Câu trả lời:


21

Điều này là chậm vì kích thước khối nhỏ. Sử dụng một GNU gần đây dd( coreutils v8.16 + ), cách đơn giản nhất là sử dụng các tùy chọn 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"

Cập nhật

fullblocktùy chọn được thêm ở trên theo câu trả lời @Gilles . Lúc đầu tôi nghĩ rằng nó có thể được ngụ ý bởi count_bytes, nhưng đây không phải là trường hợp.

Các vấn đề được đề cập là một vấn đề tiềm ẩn dưới đây, nếu ddcác cuộc gọi đọc / ghi bị gián đoạn vì bất kỳ lý do nào thì dữ liệu sẽ bị mất. Điều này không có khả năng trong hầu hết các trường hợp (tỷ lệ cược giảm đi phần nào vì chúng tôi đang đọc từ một tệp chứ không phải đường ống).


Sử dụng ddkhông có skip_bytescount_bytescác tùy chọn khó khăn hơn:

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

Bạn cũng có thể thử nghiệm với các kích thước khối khác nhau, nhưng mức tăng sẽ không đáng kể. Xem - Có cách nào để xác định giá trị tối ưu cho tham số bs cho dd không?


@Graeme sẽ không phương pháp thứ hai thất bại nếu bskhông phải là một yếu tố của skip?
Steven Penny

@StevenPenny, không chắc chắn những gì bạn đang nhận được, nhưng skiplà một số khối, không phải byte. Có lẽ bạn đang bối rối vì skip_bytesđược sử dụng trong ví dụ đầu tiên có nghĩa skip bằng byte ở đó?
Graeme

Bạn bs4,096, đó có nghĩa là bạn không thể bỏ qua một cách chính xác hơn là 4,096byte
Steven Penny

1
@StevenPenny, đây là lý do tại sao có ba lần chạy khác nhau ddvới lần đầu tiên và lần cuối sử dụng bs=1để sao chép dữ liệu không bắt đầu hoặc kết thúc trên căn chỉnh khối.
Graeme

6

bs=1nói ddđể đọc và viết một byte mỗi lần. Có một chi phí cho mỗi readwritecuộc gọi, làm cho điều này chậm. Sử dụng kích thước khối lớn hơn cho hiệu suất tốt.

Khi bạn sao chép toàn bộ tệp, ít nhất là trong Linux, tôi đã tìm thấy tệp đó cpcatnhanh hơndd , ngay cả khi bạn chỉ định kích thước khối lớn.

Để chỉ sao chép một phần của tệp, bạn có thể dẫn tailvào head. Điều này đòi hỏi GNU coreutils hoặc một số triển khai khác head -cphải sao chép một số byte được chỉ định ( tail -ccó trong POSIX nhưng head -ckhông). Một điểm chuẩn nhanh trên Linux cho thấy điều này chậm hơn dd, có lẽ là do đường ống.

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

Vấn đề với ddnó không phải là đáng tin cậy: nó có thể sao chép dữ liệu cục bộ . Theo như tôi biết, ddcó an toàn khi đọc và ghi vào một tệp thông thường - xem Khi nào dd phù hợp để sao chép dữ liệu? (hoặc, khi được đọc () và ghi () một phần) - nhưng chỉ khi nó không bị gián đoạn bởi tín hiệu . Với GNU coreutils, bạn có thể sử dụng fullblockcờ, nhưng đây không phải là di động.

Một vấn đề khác ddlà khó có thể tìm thấy số khối hoạt động được, bởi vì cả số byte bị bỏ qua và số lượng byte được truyền cần phải là bội số của kích thước khối. Bạn có thể sử dụng nhiều lệnh gọi đến dd: một để sao chép khối một phần đầu tiên, một để sao chép phần lớn các khối được căn chỉnh và một để sao chép khối một phần cuối cùng - xem câu trả lời của Graeme cho đoạn trích vỏ. Nhưng đừng quên rằng khi bạn chạy tập lệnh, trừ khi bạn đang sử dụng fullblockcờ, bạn cần cầu nguyện rằng ddsẽ sao chép tất cả dữ liệu. ddtrả về trạng thái khác không nếu một bản sao là một phần, vì vậy thật dễ dàng để phát hiện lỗi, nhưng không có cách nào thực tế để sửa chữa nó.

POSIX không có gì tốt hơn để cung cấp ở cấp độ vỏ. Lời khuyên của tôi sẽ là viết một chương trình C có mục đích đặc biệt nhỏ (tùy thuộc vào chính xác những gì bạn thực hiện, bạn có thể gọi nó dd_done_righthoặc tail_headhoặc mini-busybox).


Wow, tôi chưa bao giờ biết yes | dd bs=1024k count=10 | wcvấn đề trước đây. Bẩn thỉu.
Ole Tange

4

Với dd:

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

Hoặc với losetup:

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

Và sau đó dd, cat... thiết bị lặp.


Điều này có vẻ rất trung tâm Linux. Tôi cũng cần mã tương tự để hoạt động trên AIX, FreeBSD và Solaris.
Ole Tange

0

Đây là cách bạn làm bạn có thể làm điều này:

   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

Đó là tất cả những gì thực sự cần thiết - nó không đòi hỏi nhiều hơn nữa. Ở vị trí đầu tiên dd count=0 skip=1 bs=$block_size1sẽ lseek()trên đầu vào tập tin thường xuyên thực tế ngay lập tức. Không có cơ hội bỏ lỡ dữ liệu hoặc bất kỳ thông tin sai sự thật nào khác được nói về nó, bạn chỉ có thể tìm kiếm trực tiếp đến vị trí bắt đầu mong muốn của bạn. Bởi vì bộ mô tả tệp được sở hữu bởi trình bao và ddchúng chỉ đơn thuần là kế thừa nó, nên chúng sẽ ảnh hưởng đến vị trí con trỏ của nó và vì vậy bạn chỉ cần thực hiện theo các bước. Nó thực sự rất đơn giản - và không có công cụ tiêu chuẩn nào phù hợp với nhiệm vụ hơn dd.

Điều đó sử dụng kích thước khối 64k thường là lý tưởng. Trái với niềm tin phổ biến, các khối lớn hơn không làm cho ddcông việc nhanh hơn. Mặt khác, bộ đệm nhỏ cũng không tốt. ddcần đồng bộ hóa thời gian của nó trong các cuộc gọi hệ thống để nó không cần phải chờ sao chép dữ liệu vào bộ nhớ và ra ngoài, mà còn để nó không phải chờ trong các cuộc gọi hệ thống. Vì vậy, bạn muốn nó mất đủ thời gian để người tiếp theo read()không phải đợi đến lần cuối, nhưng không quá nhiều đến mức bạn phải đệm ở kích thước lớn hơn mức cần thiết.

Vì vậy, ddbỏ qua đầu tiên đến vị trí bắt đầu. Điều đó không có thời gian. Bạn có thể gọi bất kỳ chương trình nào khác mà bạn thích vào thời điểm đó để đọc stdin của nó và nó sẽ bắt đầu đọc trực tiếp ở phần bù byte mong muốn của bạn. Tôi gọi một số khác ddđể đọc ((interval / blocksize) -1)các khối đếm đến thiết bị xuất chuẩn.

Điều cuối cùng cần thiết là sao chép mô-đun (nếu có) của hoạt động phân chia trước đó. Và đó là điều đó.

Nhân tiện, đừng tin điều đó, khi mọi người nói lên sự thật trên khuôn mặt của họ mà không có bằng chứng. Có, có thể ddthực hiện một lần đọc ngắn (mặc dù những điều như vậy là không thể khi đọc từ một thiết bị khối lành mạnh - do đó là tên) . Những điều như vậy chỉ có thể nếu bạn không đệm chính xác một ddluồng được đọc từ ngoài một thiết bị khối. Ví dụ:

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

Trong cả hai trường hợp ddsao chép tất cả các dữ liệu. Trong trường hợp đầu tiên, có thể (mặc dù không chắc cat) với một số khối đầu ra được ddsao chép sẽ bit bằng "$ num" byte bởi vì ddchỉ để đệm bất cứ thứ gì khi bộ đệm được yêu cầu cụ thể trên lệnh của nó- hàng. bs=đại diện cho kích thước khối tối đamục đích của ddi / o thời gian thực.

Trong ví dụ thứ hai, tôi chỉ định rõ ràng khối đầu ra và ddbộ đệm đọc cho đến khi hoàn thành ghi. Điều đó không ảnh hưởng đến count=việc dựa trên các khối đầu vào, nhưng bạn chỉ cần một khối khác dd. Bất kỳ thông tin sai lệch nào được cung cấp cho bạn nếu không nên được coi thường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.