3

Вопрос

Когда я искал инструменты конвейерной буферизации в * NIX, я увидел предложения по использованию buffer , mbuffer или pv . Тем не менее, первые два не всегда находятся в официальном репозитории дистрибутивов (например, Arch), в то время как pv (начиная с 1.6.0) имеет ошибку, которая препятствует этой функциональности. В нескольких других вопросах я вижу упоминания о том, что dd используется в качестве буферов, и я хотел бы изучить его, потому что dd всегда там. Тем не менее, ни один из них не является достаточно сложным, чтобы иметь реальный смысл, поэтому здесь я прошу "правильный" способ его использования.

Вопросы , указанные dd включают https://unix.stackexchange.com/questions/345072/can-dd-be-used-to-add-a-buffer-to-a-pipe и https://unix.stackexchange.com/ вопросы / 21918 / полезности к буферу-ан-неограничена-количество-о-данных-в-трубопроводе

Для простоты тестирования ниже приведен сценарий тестирования с некоторыми комментариями о моих собственных экспериментах. Подробности будут объяснены после перечисления кода. Пожалуйста, убедитесь, что у вас установлен pv и как минимум 256M памяти перед запуском!

#!/bin/sh

producer() {
    while [ 1 ]; do
    dd if=/dev/zero bs=64M count=1 iflag=fullblock status=none
    sleep 4
    done
}

buffer() {
    # Works, but long
    # Must at least fill 32M before consumer starts
    # So, must choose small obs and chain more to look
    # more like a proper "buffer"
    dd obs=32M status=none | \
        dd obs=32M status=none| \
        dd obs=32M status=none| \
        dd obs=32M status=none
    # Doesn't work, producer rate limited
    #dd bs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd obs=128M status=none 
    # Doesn't work, producer rate limited
    #dd ibs=128M status=none 
    # Doesn't work, producer must fill buffer, then
    # must wait until buffer is empty
    #dd bs=128M status=none iflag=fullblock
}

consumer() {
    pv --rate-limit 1M -q | dd of=/dev/null status=none
}

producer | pv -cN produce | buffer | pv -cN consume | consumer

Здесь производитель производит 64 МБ данных каждые 4 секунды с буфером 128 МБ, в то время как потребитель потребляет с постоянной скоростью 1 МБ / с. Конечно, это означает, что буфер будет переполнен довольно быстро, но это должно четко показывать эффекты. В идеале, до того как буфер заполнится (при третьем производстве), мы должны увидеть постоянное потребление 1 МБ / с, а приросты производительности дают данные по 64 МБ каждый. "Правильный" вывод выглядит так:

  produce:  128MiB 0:00:07 [   0 B/s] [  <=>                                                       ]
  consume: 7.25MiB 0:00:07 [1.01MiB/s] [       <=>                                                 ]

Здесь рабочее решение показано следующим образом:

dd obs=32M status=none | \
    dd obs=32M status=none| \
    dd obs=32M status=none| \
    dd obs=32M status=none

Это строится путем разделения необходимого буфера 128 МБ на 4 блока. Да, каждый блок должен заполняться до того, как данные будут переданы на следующий уровень, но, поскольку 32 МБ меньше пакета 64 МБ, он работает для этого теста, как если бы это был настоящий буфер. Теперь есть некоторые проблемы.

  1. В реальных приложениях у нас нет мгновенного пакета данных, поэтому куски должны быть маленькими, но не слишком маленькими. Это означает, что будет длинная цепочка команд dd
  2. Что если EOF обнаружен до достижения отметки 32 МБ? Будет ли этот блок потерян? Я тестировал с помощью dd if=test.txt| dd obs=1M | dd obs=1M | dd of=test2.txt и сравниваемый результат. Оказывается, это не проблема. Таким образом, использование его для резервного копирования не повредит данные.
  3. Сколько накладных расходов это создает?
  4. Есть ли более элегантный способ добиться того же самого, грамотно расставив параметры?

Есть несколько других попыток, включенных в сценарий, и они не работают, как объяснено в комментариях. И я попытался использовать фоновые процессы FIFO +, который дает тот же результат.

PS. Как вы знаете, буферизация канала весьма полезна при резервном копировании A в B, особенно когда A - жесткий диск, у которого есть время поиска. Так что я бы сделал что-то вроде этого:

tar cSpf - <path> -C <root path> | <a large buffer> | <some parallel compressor> \
| <small buffer if compressor is generally slow and B have seek time> \
| dd bs=<several GB if B is not an SSD> iflag=fullblock oflag=direct of=<archive.tar.??>

1 ответ1

0

Я добавляю свой собственный ответ. Это может быть не лучшим, но это нормально.

предосторожность

Это написано впереди после многих испытаний.

Не связывайте слишком много DD для буферизации, иначе все ядра вашего процессора могут блокироваться при IO, и ваш компьютер зависнет, даже если у вас останется куча памяти!

Особенно ядовито, если у вас сломан медленный внешний USB-накопитель, которому для чтения / записи нужна нелепая интенсивность ввода-вывода.

Примеры

Я в основном исчерпал все комбинации вариантов DD. Один DD кажется невозможным для этой задачи, поскольку он не может выполнять асинхронный ввод-вывод. В противном случае в цепочке DD-буфера самый большой блок должен быть заполнен, прежде чем он начнет действовать как FIFO. Итак, если вас не волнует начальная задержка при заполнении трубы ... Цепочка из двух работ дд. Я надеюсь, что кто-то еще может предложить более элегантное решение, но вот пример использования.

Пример 1: перенос всех файлов с сильно фрагментированного жесткого диска A (дрожание времени отклика) на сильно фрагментированный жесткий диск B (дрожание), используя XZ в качестве алгоритма сжатия (медленный) параллельно (дрожание, если вы фактически используете компьютер) (отказ от ответственности: Я пишу это из своей головы, поэтому мелкие детали могут быть неправильными. Используйте на свой риск):

tar -cpSf - -C /mnt/A . | \
  dd obs=1024M | dd obs=1024M | \
  xz -T 0 -c | \
  dd obs=1024M | dd obs=1024M | \
  dd bs=512M iflag=fullblock of=/mnt/B/A.tar.xz

Добавить pv чтобы увидеть скорость. Здесь xz начинается только после того, как данные 1GB считаны из A(если у него меньше 1GB, то он заканчивается). Аналогично, запись на диск в B начинается только после того, как данные из 1 ГБ поступают из xz. Этот код дает 2 ГБ буфера между tar и xz и 2 ГБ между xz и записью. Значение bs=512M в конце не является действительно необходимым, но я обнаружил, что большой (> 64M) размер блока дает лучшую среднюю скорость записи, особенно на жестких дисках USB. Я предполагаю, что это также создает меньше фрагментов, если диск B используется (не подтверждено).

Пример 2 Задача: скопировать гигантский файл с сильно фрагментированного диска A на сильно фрагментированный диск B.

dd if=/mnt/A/file obs=<half of cache you want> | dd bs=<half of cache> iflag=fullblock oflag=direct of=/mnt/B/file

Это одна из самых простых форм, которые я могу найти. Если файл достаточно гигантский, начальное время, используемое для заполнения кэша, должно быть незначительным. Между тем, он читает / пишет асинхронно, и, надеюсь, группирует достаточно записей, чтобы получить некоторую последовательную производительность. Я полагаю, что SSD не будет заботиться о размере блока.

Пример 3 Благодаря Kamil Maciorowski у меня теперь есть следующее в моем .zshrc:

buffer() {
    if [ "$2" -gt 0 ] ; then
        dd status=none obs="$1" | buffer "$1" $(($2-1))
    else 
        cat 
    fi
}

Теперь, если вам нужно 3 блока 512M буфера, цепной buffer 512M 3 в вашем конвейере. Как правило, если ваша работа достаточно велика для вашей пропускной способности (например, Копируя / сжимая 100 ГБ + данные при средней скорости 100 МБ / с), меньший блок не дает никаких преимуществ, кроме более быстрого заполнения канала (что не имеет значения, поскольку это время мало). Я заметил, что если вы вставите слишком много блоков, процессор может быть настолько загружен во время ввода-вывода, что команда замораживает весь компьютер.

Теперь Пример 1 становится

tar -cpSf - -C /mnt/A . | \
buffer 1024M 2 | \
xz -T 0 -c | \
buffer 1024M 2 | \
dd bs=512M iflag=fullblock of=/mnt/B/A/tar.xz

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .