Отчёт об ошибке 342056 в баг-трекере KDE лежит без решения с : копирование папки на 15 ГБ из мелких файлов занимало в KDE от пяти до десяти часов против примерно двадцати минут у rsync. В KIO 6.29 из обмена между приложением и внутрипроцессным обработчиком убрали сокет, а ещё две правки с пакетным копированием находятся на рассмотрении. На наборе из 5000 файлов по 4 КБ медианное время копирования снизилось с 1616 до 88 мс при 81 мс у cp. Замеры и разбор причин опубликованы в блоге KDE .
Откуда взялись расходы на каждый файл
Через KIO проходят почти все файловые операции в программах KDE – от копирования и вставки в Dolphin до открытия адресов sftp:// и smb:// так, будто это локальные папки. Архитектура заложена во времена KDE 2, около 2000 года: ввод-вывод выносился не в поток, а в отдельный процесс. Обработчик запускается под конкретный протокол и связан с приложением через сокет.
Обработчик KIO (worker, прежнее название kioslave) – компонент, который реализует работу с конкретным протоколом и скрывает его особенности от приложения. Отдельный обработчик отвечает за локальные файлы, отдельные – за smb://, sftp:// и другие адреса.
Пригодных потоков в Linux тогда не было: библиотека NPTL появилась только в ядре 2.6 в 2003 году, поэтому связка из процесса и сокета оставалась переносимым и устойчивым вариантом. Для сетевых протоколов она такой и осталась – аварийное завершение обработчика smb:// не роняет диспетчер файлов.
Плата за устойчивость – сериализация каждого запроса и ответа с проходом через цикл обработки событий на обеих сторонах. Для протокола file:// эти расходы ничего не дают: на другом конце нет недоверенной сети, только локальный диск. В 2022 году в Frameworks 5.95 вошла правка с запуском обработчиков в потоке, после которой обработчик локальных файлов работает потоком внутри приложения; остальные протоколы по соображениям устойчивости остались вынесенными в отдельные процессы.
Профилирование копирования 5000 мелких файлов в KIO 6.28 единственного узкого места не выявило. Каждый файл упирался в очередь коротких системных вызовов: чтение таблицы точек монтирования из /proc/self/mountinfo, открытие источника и приёмника, обмен командой с обработчиком через внутренний сокет. На сокет приходилось около 15% времени блокировок, размазанного тонкими полосками по обоим потокам, поскольку эти расходы оплачивал каждый из тысяч файлов.
Отказ от сокета между потоком и приложением
Даже после перехода на поток обработчик и приложение продолжали общаться через пару сокетов, сериализуя каждую команду так, будто это по-прежнему разные процессы. Для внутрипроцессного режима это была чистая накладная нагрузка.
В 6.29 обмен идёт напрямую в памяти. За команды отвечает новый ThreadConnectionBackend, а буфер с данными передаётся соседнему потоку без сериализации и без системного вызова, то есть чтение файлов внутри процесса обходится без копирования данных. Это самый крупный единичный скачок в замерах, и изменение уже принято в основную ветку.
Пакетное копирование и пакетная фаза stat
После этого каждая команда стала дешёвой, но их по-прежнему много: копирование N файлов – это N отдельных задач file_copy, каждая со своим проходом через планировщик и своим обменом с обработчиком. На мелких файлах общее время определяет именно фиксированная нагрузка на файл, а не объём данных.
В предложенном изменении добавлен режим, в котором целая пачка обычных локальных файлов уходит от CopyJob к обработчику одной командой. Копирование идёт через copy_file_range, а на Btrfs и XFS – через reflink. Сигнал прогресса, учёт байтов и запись для отмены сохраняются у каждого файла, а всё, что нельзя сделать вслепую – занятый приёмник, нечитаемый источник, решение о переименовании или пропуске, – возвращается на обычный путь по одному файлу. Условие входа в пакетный режим намеренно осторожное, чтобы зависшая точка монтирования не заблокировала всю пачку.
copy_file_range – системный вызов Linux, который копирует данные между двумя файловыми дескрипторами внутри ядра, без передачи в пространство пользователя и обратно. На файловых системах с копированием при записи он даёт возможность обойтись reflink – ссылкой на те же блоки данных вместо их физического дублирования (описание системного вызова).
Перед копированием для каждого источника вызывается stat, и это ещё один обмен на файл. Вторая правка переносит и эту фазу на общий внутрипроцессный канал; в таблицах ниже она обозначена как batch-copy-stat.
5000 файлов по 4 КБ: с 1616 до 88 мс

Замеры сделаны на ноутбуке с процессором Intel Core i7-1365U 13-го поколения, сборка RelWithDebInfo без включённых средств проверки sanitizer, копирование на реальный раздел ext4 – то есть быстрый путь через reflink недоступен и copy_file_range переносит настоящие байты. У каждой версии KIO в замерах свои libKIOCore и свой обработчик kio_file. Приведена медиана пяти прогонов (трёх для крупного набора) после отброшенного прогрева, так что кэши у всех версий прогреты одинаково. Нижней границей взята команда:
cp -r --preserve=mode,timestamps
| Версия | Медианное время |
|---|---|
| cp -r (coreutils) | 81 мс |
| KIO 6.28.0 | 1616 мс |
| KIO 6.29-dev (master) | 396 мс |
| KIO 6.29-dev + batch-copy | 88 мс |
| KIO 6.29-dev + batch-copy-stat | 93 мс |
Мелкие файлы – главный результат. KIO 6.28 отставал от cp примерно в 20 раз, ровно в той пропорции, о которой говорилось в отчёте 2014 года. Удаление сокета для внутрипроцессных обработчиков вместе с отказом от опроса файловой системы приёмника на каждом файле сократило время с примерно 1,6 до 0,4 с. Пакетное копирование доводит результат до 88 мс: это уровень cp и примерно в 18 раз быстрее 6.28.
Варианты batch-copy и batch-copy-stat в этом тесте равны. Копируется один каталог, его содержимое приходит одним списком, и пакетному stat нечего объединять. Выигрыш проявляется в другом случае – при копировании большого набора выбранных по отдельности файлов, каждый из которых иначе стоил бы отдельного обмена. Убирается сам обмен, а не вызов stat, поэтому эффект тем заметнее, чем дешевле обходится stat, а не чем медленнее диск.
1000 файлов по 5 МБ: разница в пределах 15%
| Версия | Медианное время |
|---|---|
| cp -r (coreutils) | 1312 мс |
| KIO 6.28.0 | 1997 мс |
| KIO 6.29-dev (master) | 1687 мс |
| KIO 6.29-dev + batch-copy | 1506 мс |
| KIO 6.29-dev + batch-copy-stat | 1454 мс |
Крупные файлы были закрыты и до этой работы: время упирается в перенос байтов, поэтому даже 6.28 держался недалеко от cp, а оптимизация расходов на файл почти не меняет цифру – пакетный режим укладывается примерно в 15% от cp. Ради мелких файлов вся работа и затевалась.
Небольшое преимущество за cp почти наверняка сохранится и дальше, и это заложено в конструкцию: KIO не ограничивается переносом байтов. Ведётся отчёт о прогрессе по каждому файлу, операцию можно приостановить и отменить, сохраняется запись для отмены действия, переносятся права доступа и отметки времени, разрешаются конфликты имён, а открытые окна диспетчера файлов получают уведомление об изменениях. Задача формулировалась иначе: локальное копирование должно перестать быть поводом уходить в терминал.
Когда быстрый путь не включается
Цифры получены на одном ноутбуке, на одном разделе ext4 и с прогретыми кэшами – они пригодны для сравнения версий, но не как абсолютные значения. На Btrfs и XFS в пакетном режиме вместо копирования применяется reflink, что полностью меняет картину для крупных файлов.
Пакетный быстрый путь включается только для обычного локального копирования на отзывчивой файловой системе. Сеть, FAT и NTFS, а также конфликты, требующие диалога, обрабатываются прежним способом – по одному файлу и без изменений.
Заключение
Из трёх изменений в основную ветку принято одно – отказ от сокета для внутрипроцессных обработчиков; оно войдёт в KIO 6.29. Пакетное копирование и пакетная фаза stat остаются на рассмотрении и нацелены на выпуск после 6.29, поэтому соответствующие строки таблиц стоит читать как предварительный результат, а не как то, что можно поставить сегодня.
Практический эффект появляется там, где счёт идёт на тысячи мелких файлов: именно такой случай описан в отчёте 2014 года. При работе с крупными файлами и при копировании по сети разницы ждать не приходится: здесь цифры почти не изменились.