Так dd вы ещё не использовали: исследование скорости чтения и записи

y02lt-fdhu5fltxuid4nwjx4umu.jpeg

Недавно, я вновь побывал в роли технического эксперта, когда занимался переводом книги «Understanding Software Dynamics» от Richard L. Sites. В ходе работы над главой — про скорость работы с жёстким диском, мне поступил вопрос от коллеги: каким образом можно просто и быстро измерить скорость чтения и записи твердотельных носителей информации, в разрабатываемых в компании устройствах? При этом стояла задача реализовать всё это наиболее простыми способами, чтобы они были переносимы между совершенно разными платформами и архитектурами. Носители же информации могут быть любыми: USB Flash, eMMC, SD, NAND и прочее, прочее. Единственное, что их объединяет — это Linux.

Задача захватила меня с головой…Тем более, когда я понимал, что решение не такое тривиальное, как хотелось бы. При этом нужно дать какой-то простой и переносимый инструмент для его реализации.

Решение, с одной стороны, очевидное — это утилита dd. Но так ли просто измерить скорость твердотельного диска, а я уж не говорю о настоящем жёстком диске? В попытке ответить на этот вопрос моему коллеге, у меня получилось настоящее исследование, которым и хочу с вами поделиться.

Поскольку при тестировании диска нужно производить как операции записи на диск, так и операции чтения, то дабы сократить объём статьи в два раза, буду приводить примеры только операции записи на диск, как наиболее тяжёлые, для чтения будет всё аналогично, только будут другие цифры. Но всё что я тут ниже буду рассказывать, относится и к операциям чтения, просто цифры будут немного другие.

Пару слов об утилите dd


Для тех, кто не знаком с этой утилитой или не пользуется *nix системами краткий ликбез про эту команду. Конечно, лучше, чем в вики не скажешь, поэтому позволю несколько цитат оттуда:

dd (data definition) — программа UNIX, предназначенная как для копирования, так и для конвертации файлов. Название унаследовано от оператора DD (Data Definition) из языка JCL.
Название утилиты dd иногда в шутку расшифровывают, как «disk destroyer», «data destroyer», «delete data» или «добей диск», так как утилита позволяет производить низкоуровневые операции на жёстких дисках — при малейшей ошибке (такой, как реверс параметров if и of) можно потерять часть данных на диске (или даже все данные). Есть и более «уважительное» прозвище — «disk duplicator», потому что на практике основное её применение — это копии, образы и бэкапы разделов.

Я всё же настоятельно рекомендую прочитать man dd перед её использованием, поскольку её возможности достаточно обширны, но кратко описание команды можно также взять с вики:

Базовые параметры

dd [--help] [--version] [status] [if=файл] [of=файл] [ibs=байты] [obs=байты] [bs=байты] [cbs=байты] [skip=блоки] [seek=блоки] [count=блоки] [conv={ascii, ebcdic, ibm, block, unblock, lcase, ucase, swab, noerror, notrunc, sync}]

  • status=progress — отображает статистику передачи, возможны 3 варианта 'none', 'noxfer', 'progress' GNU Coreutils 8.24+ (Ubuntu 16.04 and newer).
  • if=файл — читает данные из файла вместо стандартного ввода.
  • of=файл — пишет данные в файл вместо стандартного вывода.
  • bs=n — размер блока.
  • ibs=nn и obs=nn — задаёт, сколько байтов нужно считывать или записывать за раз.
  • count=n — сколько блоков скопировать.
  • seek=n — сколько блоков пропустить от начала в выходном файле перед копированием.
  • skip=n — сколько блоков пропустить от начала во входном файле перед копированием.
  • conv=фильтр, фильтр — применить фильтры конвертации. Типы фильтров:
    • ascii — сконвертировать в ASCII из EBCDIC…
    • ebcdic — …и наоборот.
    • block — выравнивание блоков.
    • lcase — преобразовать к нижнему регистру.
    • ucase — преобразовать к верхнему регистру.
    • swab — менять местами пары байт.
    • noerror — игнорировать ошибки ввода-вывода.

Простой пример утилиты dd — это как сделать копию главной загрузочной записи MBR жёсткого диска:

dd if=/dev/hda of=bootloader.mbr bs=512 count=1


ВАЖНО: При работе с утилитой dd нужно соблюдать большую внимательность, потому что ей легко и просто уничтожить все данные на жёстком диске. Недавно, вышеупомянутый коллега, таким образом снёс себе систему. Да, чего уж там скрывать, я сам неоднократно стирал совсем не те диски. Поэтому обычно по десять раз всё проверяю.

Резюмируя, утилита позволяет читать и записывать данные как на диск, так и в файл. А поскольку в операционных системах семейства *nix всё есть файл, то это практически равнозначно! Хотя нет, и ниже поясню почему.

Можно ли dd использовать для тестирования скорости записи и что не так с этим тестом?


Для начала продемонстрирую один эксперимент, демонстрирующий разницу скорости работы утилиты dd.

Смотрите, я хочу создать файл, заполненный нулями. Для этого вычитаю файл-устройство /dev/zero и запишу его в регулярный файл на диске.

Отличие будет в количестве данных считываемых за раз: в первом случае я буду читать 1 байт за раз, а в другом случае 1МиБ. Кстати, особенность операционной системы линукс состоит в том, что она не гарантирует запись на диск после окончания операции dd. Данные просто были скопированы в ядро, поэтому после утилиты dd нужно выполнить команду sync для того, чтобы данные попали на диск из буфера ядра.

Критерием выполнения операции будет общее время исполнения dd и sync.

В результате получится две команды:

Для первого случая, читаем 1 байт за раз и записываем миллион штук:

time $(dd if=/dev/zero of=test.raw bs=1 count=1M && sync)


Во втором случае читаем 1 мегабайт за раз и пишем один раз:

time $(dd if=/dev/zero of=test.raw bs=1M count=1 && sync)


Вроде бы незначительная разница в двух командах, и многие не глядя делают первый вариант. Но какая разница в скорости выполнения. Даже скриншотом приложу, чтобы вы не думали, что я мухлюю:

jk3brjmw5ozv3lxht6ws9yrnfbi.png

Сравните две цифры: 1,910 секунды и 0,01 секунды! Разница в скорости в сто девяносто один раз!!!

А причина достаточно простая, жёсткий диск — это блочное устройство и работа с ним ведётся блоками данных. Если вы читаете или пишете (как в случае выше) 1 байт, вы всё равно записываете блок данных, который больше 1 байта, а может быть размером от 512 и более байт, которое кратно 512 байтам.

Блочные устройства помечаются в папке /dev/ буквой «b»:

zg9qtcc4npwzp05dzrovjel4ric.png


Как вы понимаете, размер блока определяется особенностями работы конкретного физического устройства и реализацией драйвера внутри ядра.

Но ради всех дисков и решений погружаться в дебри кода ядра и схемотехники дисков совсем не хочется, а хочется понять при каком размере блока можно получить максимальную скорость записи на диск.

Таким образом, мы подходим к основной части исследования: какой оптимальный размер блока для записи на диск?

Какой размер блока оптимален для записи на дисковое устройство?


Ответ не совсем очевиден: он зависит от аппаратных особенностей реализации устройства, для памяти типа NAND — это будет блок одного размера (вероятнее размером со страницу), для жёстких дисков другого.

Поэтому я определился с подопытным кроликом. Для этих варварских экспериментов, которые дико снизят ресурс любого флеш-накопителя, решил принести в жертву неиспользуемую флешку на 16 ГБ (обращаю внимание, что реальный её размер 14,32 ГиБ).

0h4hixqzd5_m1sc1g4-gf9lb18w.jpeg
Жертвенный носитель

Для того чтобы не лопатить вручную команды, набросал простенький скриптик на питоне, на который я потратил два дня, который запускает команду dd. Да, можно было непосредственно писать в файл прямо с питона, но у меня была задача проверить именно эту утилиту. С исходным файлом кода скрипта можно ознакомиться на гитхабе.

Смысл его в том, что он по очереди перебирает размер блока (bs) от минимального 512 байт до максимального 16МиБ, и записывает данные в указанный пользователем файл.

Практика показала, что оптимально для исследований размер записи не менее 1 ГиБ, и количество записей — не менее 9 (разброс показаний погрешности).

Таким образом, если флешка у нас является файл-устройством /dev/sdg, то команда для тестирования будет выглядеть следующим образом:

sudo python3 dd_speed_test.py -o /dev/sdg -s 1024 -n 9 --logfile="disk_test.txt"


Прошу вас, обязательно проверьте файл-устройство, куда вы пишете, иначе вы потеряете ваши данные!

Поскольку данных много, то я решил брать от них два параметра: среднее и медианное значение. Более доверяю медианному, потому что оно ближе к реальности. Конечно, если бы это было научное исследование, тут хорошо бы учитывать разброс значений, чтобы понимать максимум и минимум. Но не буду перегружать статью. В результате этого эксперимента получил такую табличку:

y0plw7vnlctfkajqcps_d0gfkmg.png


Ничто так не украшает статью, как хороший график, который построен следующим скриптом.

1kmbmaumg39jg1mhrnnbpbmuiya.png
Время записи 1 ГиБ данных на диск в зависимости от размера блока данных

Видно, что самая быстрая запись идёт блоком — размером 1 киБ, а самая медленная — блоком размером 512 байт (хотя, казалось бы). Размер, который я использую в быту — 1 МиБ, вполне себе находится в допуске самых быстрых.

Обращаю внимание, что эти данные — результаты эмпирического исследования скорости записи на конкретную флешку, хотя, разумеется, с твердотельными накопителями графики, вероятнее всего, будут схожими.

Вывод главы: наиболее оптимальным блоком данных, при непосредственной записи на диск оказался размер в 1 киБ.

А теперь интересный вопрос, а можно ли измерять скорость записи на диск, не стирая данные на нём, а записывая в файл в файловой системе?

Особенности организации записи файла на диск


Для того чтобы показать, как делается запись данных на диск с файловой системой, я сделаю виртуальное файл-устройство и буду его конвертировать в картинку. Если цвет белый — это байт 0xFF, а если какой-либо другой, то значит отличные от 0xFF.
Для удобства, размер картинки с разрешением 1536х1024 выбрал наиболее приближённой к размеру дискетки: 1536*1024=1572864 байт.

Итак, создаю тестовый RAW-файл, заполняя его 0xFF (белый цвет).

time dd if=/dev/zero bs=1572864 count=1 | LC_ALL=C tr "\000" "\377" | dd of=test.raw


Создаю файловую систему в этом файле

mkfs.fat test.raw -n TEST_FAT


При этом это реальная файловая система в файле, в чём можно убедиться командой file:

file test.raw 
test.raw: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, root entries 512, sectors 3072 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 3, sectors/track 16, reserved 0x1, serial number 0x1f307932, label: "TEST_FAT   ", FAT (12 bit)


Если сейчас этот RAW-файл с файловой системой конвертнуть в картинку, следующей командой:

convert -size 1536x1024 -depth 8 gray:test.raw fat.png


то мы увидим, что в начале были сделаны какие-то записи структуры файлов. В картинке в GIMP специально сделал синюю рамку, чтобы была лучшая наглядность.

3xftshvj86kn14puypbnpruvjhi.png
Преобразованная виртуальная файловая система в изображение

Чёрная полоска вначале этой белой картинки и есть файловая система, которая даже занимает какое-то место.

Теперь можно примонтировать файловую систему.

udisksctl loop-setup -f test.raw
udisksctl mount -b /dev/loop0


У меня после первой команды монтируется автоматом в Linux Mint, поэтому вторая не требуется.
Можно посмотреть информацию об этом файл-устройстве:

df -a /dev/loop0
Файл.система   1K-блоков Использовано Доступно Использовано% Cмонтировано в
/dev/loop0          1516            0     1516            0% /media/dlinyj/TEST_FAT


Обратите внимание, что свободно 1516 блоков, что на 20 блоков по одному килобайту меньше, чем размер созданного файла. Вероятнее всего, 20 КиБ занимает файловая система.

А теперь самое главное, ради чего мы здесь собрались, записываем тестовый файл размером 1 МиБ в эту виртуальную файловую систему.

dd if=/dev/zero of=/media/dlinyj/TEST_FAT/test bs=1M count=1 && sync


И смотрим, как же выглядит файловая система после записи. Просто смотреть на картинку после записи особого смысла нет, поэтому сделаю diff полученного файла с тем, который был до записи:

convert -size 1536x1024 -depth 8 gray:test.raw fat_and_file.png
compare fat.png fat_file.png diff.png


Результирующее изображение произведённых изменений:

9oqaodp-tpjiuudy4qs2dzmfi-k.png
Изменения, внесённые в файловую систему виртуального устройства

Большой красный прямоугольник — это как раз наши записанные 1МиБ данных, а тонкая красная полоска — это записи в файловой системе о том, где хранятся эти кластеры данных.

Почему это важно? Потому что на запись каждого блока, надо читать файловую систему и редактировать таблицу данных файловой системы и записывать её обратно. То есть, идёт на каждую порцию данных дополнительные накладные расходы на правку файловой системы. И это означает, что запись на диск в файл должна быть медленнее, чем непосредственная запись на диск.

Но так ли это на самом деле? Ответ вас может сильно удивить, и он удивил даже меня. Она может быть даже быстрее (и будет быстрее, если неверно выбрать размер блока). И сейчас объясню почему.

Измерение скорости записи на диск, при работе с файлом


Прежде чем делать выводы, давайте также проведём исследование с созданием файла на флешке, с использованием утилиты dd и проверим, какой же размер блока будет оптимальным.

Для начала, после зверских опытов с флешкой, создадим на ней файловую систему. Первично диск нужно разметить командой fdisk:

rgdklysguw2ubapvpcp0jirp8wu.png

После этого, во вновь созданном разделе создаю файловую систему FAT32. И монтирую её.

mkfs.fat -F 32 /dev/sdg1 -n TEST_FLASH
udisksctl mount -b /dev/sdg1


Что ж, настало время теста:

python3 dd_speed_test.py -o /media/dlinyj/TEST_FLASH/testfile -s 1024 -n 9 --logfile="file_test.txt"


Результаты такого тестирования представлены в таблице ниже.

ps-asuzofio7-bzhdc7jx1xqwww.png


И конечно, красивый график, куда ж без него.

b6amnscy0lkc-1bthsbeiv0vvxg.png
Время записи в регулярный файл на диске в зависимости от размера блока

Видно, что чем больше размер блока, тем быстрее идёт запись. Но внимательный читатель посмотрел на числа, и всё встаёт на свои места, если наложить график времени непосредственной записи на диск — на график записи в файл.

iwyxa3nyl1rdwtjlwb95vuc5424.png
Сравнение времени записи прямой на диск и в файл в зависимости от размера блока

Можно увидеть, что по сути — размер bs никак не влияет на скорость записи в файл! А скорость записи показывает практически самые лучшие показатели, как при непосредственной записи на диск.

Почему же так получается, и можно ли тестировать таким образом скорость записи на диск? Всё достаточно просто: происходит кеширование внутри ядра, в том числе записей в файловую систему. И потом запись происходит на самой оптимальной скорости на диск, прямо из буфера ядра. Как можно убедиться, на 1, 2 и 4 киБ таки проигрывает максимальной скорости записи на диск.

А можно ли получить реальную скорость записи, когда запись производится непосредственно? Да, отключив буферизацию.

Отключаю буферизацию записи


Отключить кеширование можно с помощью команды hdparam, с помощью опции -W, которая согласно документации, позволяет управлять размером буфера.

-W Get/set the IDE/SATA drive´s write-caching feature.


Сделаем его равным нулю:

sudo hdparm -W0 /dev/sdc

/dev/sdc:
 setting drive write-caching to 0 (off)
SG_IO: bad/missing sense data, sb[]:  f0 00 05 00 00 00 00 14 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
SG_IO: bad/missing sense data, sb[]:  f0 00 05 00 00 00 00 14 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
SG_IO: bad/missing sense data, sb[]:  f0 00 05 00 00 00 00 14 00 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 write-caching = not supported


Ну и произведём тестирование с отключенным буфером (бедная флешка):

python3 dd_speed_test.py -o /media/dlinyj/TEST_FLASH/disk_file -s 1024 -n 9 --logfile="cache_off.txt"

В результате получаются цифры намного более близкие к реальности, чем в предыдущем тесте.

jipuzdbjx_0q9hcv3zbmjpsqlyq.png


Ну и конечно же график.

euixqwwszuvcgelpdb5gzyijgai.png
Время записи в файл на диске при отключенном кешировании

Ну вот, совсем другие числа. Отличаются практически в три раза!

Ну и нагляднее всего увидеть все три варианта на одном графике. Чтобы вы не запутались:

  • Первая группа — это просто запись в файл.
  • Вторая группа — это запись с отключенным кешированием.
  • Третья — это непосредственная запись на диск.


lxmt1rbjp10jqxfq-wbxgabhloq.png
Сравнение времени записи всех возможных вариантов при разной величине блока данных

Как можно увидеть, что самый быстрый способ записать данные на диск — это непосредственная запись с оптимальным размером блока.

Вывод


Да, я прекрасно понимаю, что есть куча хороших утилит, для измерения скорости работы с жёстким диском. И уверен, что под Линукс есть много хорошего ПО. При этом — они более качественно, а главное более адаптивно к ядру и железу — проведут тестирования оборудования.

Однако, все эти утилиты нужно искать, собирать, проверять тестировать. А когда хочется здесь и сейчас, утилита dd вполне может показать в общих чертах относительные параметры, с которыми производилась запись. Но, оценивать придётся не те числа, с какой скоростью была произведена запись, а время выполнения команды, вместе с командой sync и помнить, что sync синхронизирует не только ваши данные, а вообще все буфера ядра. В общем и целом, неизвестных в этом вопросе пока может быть даже больше, чем известных.

Отвечая на главный вопрос коллеги: можно ли измерить скорость чтения-записи, работая с файлом?

Отвечаю: скорее нет, чем да. Слишком большая погрешность кеширования. В целом — это не самый оптимальный способ измерения скорости работы с диском. Поэтому, если точность параметров не нужна и допустимо измерение в попугаях (такое часто бывает), этим способом можно пользоваться.

Полезные ссылки


  1. Репозиторий проекта к этой статье, если вы вдруг захотите повторить эти опыты.
  2. Если вы захотите писать свой тестер жёстких дисков, лучше использовать этот код. Это лучше, чем писать кривые непереносимые обёртки над dd.
  3. Linux and Unix Test Disk I/O Performance With dd Command — Весьма неплохая статья на английском, где подробно расписано, почему всё так получается и как оно работает. По сути, то что я тут рассказал, только в более сжатом виде.


Если вам интересна металлообработка, всякие DIY штуки, погроммирование и linux, то вы можете следить за мной ещё в телеграмме.

Возможно, захочется почитать и это:
b5pjofdoxth14ro-rjsrn7sbmiy.png

© Habrahabr.ru