Линус Торвальдс разрабатывает оптимизацию чтения файлов в ядре Linux с помощью Claude Code. Основной коммит экспериментальной ветки noref-filemap-read-with-llm датирован . Изменения устраняют конкуренцию потоков за счётчик ссылок при чтении небольших фрагментов одного файла из страничного кеша. На ноутбуке Торвальдса многопоточный тест заметно ускорился, однако серия патчей остаётся экспериментальной и содержит нерешённые проблемы.
Как Торвальдс использовал Claude Code
На новую ветку в репозитории torvalds/linux.git обратил внимание ресурс Phoronix. Майкл Ларабель заметил её, ожидая Linux 7.3-rc7: обычно Торвальдс ведёт в этом репозитории основную ветку master, поэтому появление отдельной рабочей ветки привлекло внимание.
В описании серии Торвальдс сообщил, что Claude Code под его руководством подготовил код, тестовые программы, разбор перечисленных проблем и первый вариант сопроводительного текста. Измерения Торвальдс провёл на собственном компьютере. Версию модели он не указал.
Claude Code – ИИ-инструмент Anthropic для разработки, который читает файлы проекта, вносит изменения и запускает команды. Он работает в терминале и других средах разработки.
Это продолжение прежних экспериментов Торвальдса с ИИ. В августе он использовал такой помощник при отладке драйвера Intel Xe, а ранее поручил Google Antigravity создание Python-визуализатора для своего проекта AudioNoise. В июле Торвальдс также заявил, что Linux не относится к анти-ИИ-проектам, при этом обязательным применение таких инструментов не становится.
Что мешает нескольким потокам читать один файл
При обычном чтении из кеша ядро получает ссылку на folio, а после копирования данных освобождает её. Если несколько процессорных ядер постоянно читают один и тот же небольшой участок файла, они конкурируют за обновление общего счётчика. В результате часть времени уходит на синхронизацию, хотя сами данные уже находятся в памяти.
Серия из восьми патчей откладывает окончательное освобождение folio до завершения периода ожидания RCU. Пока читатель находится в защищённом участке, память не передают новому владельцу: данные можно копировать непосредственно в пользовательский буфер без увеличения и уменьшения счётчика ссылок. Дополнительный буфер на стеке и повторная проверка после копирования этому варианту не нужны.
RCU (Read-Copy Update) – механизм синхронизации, позволяющий отделить удаление объекта из структуры данных от освобождения его памяти. Перед освобождением ядро дожидается завершения ранее начавшихся защищённых чтений.
Новый способ чтения не ограничивается короткими запросами. Однако при отсутствии нужных данных в кеше, необходимости обновить состояние folio или ошибке доступа к пользовательской памяти ядро продолжает операцию прежним способом. Для folio удаляемого inode, hugetlb и swap cache отложенное освобождение не применяется; последние два случая не обслуживаются через filemap_read().
Изменения затрагивают и другие операции: передача folio из кеша новому владельцу через splice нарушила бы гарантию сохранности данных. Поэтому серия убирает этот способ передачи, а FUSE и virtio_console копируют данные.
Результаты чтения из кеша
Торвальдс тестировал изменения на ноутбуке с Intel Core i7-8565U: четыре ядра, восемь потоков, общий кеш L3 и файловая система ext4. База сравнения – Linux 7.3-rc6 с дополнительными исправлениями, коммит 0c2669a9f4a1. Каждый поток использовал собственный файловый дескриптор и выполнял pread() по 16 байт с нулевого смещения одного файла размером в страницу.
Защиты от процессорных уязвимостей и CONFIG_HARDENED_USERCOPY на стенде были отключены. Ноутбук снижал частоту при нагреве, поэтому автор рекомендует сравнивать затраты на одну операцию, а не только число чтений в секунду. Все приведённые ниже результаты – его собственные измерения.
| Режим | Инструкции ядра на чтение, база | Инструкции ядра на чтение, патчи | Такты ядра на чтение, база | Такты ядра на чтение, патчи | Млн чтений/с, база | Млн чтений/с, патчи |
|---|---|---|---|---|---|---|
| 1 поток | 973 | 873 | 452–454 | 408–409 | 7,9–8,1 | 8,8–8,9 |
| 8 потоков | 988–989 | 864 | 2937–2975 | 725–726 | 9,9–10,4 | 30–33 |
| 4 потока, по одному на ядро | 979–980 | 873–876 | 1318–1401 | 417–438 | 8,9–11,6 | 25–26 |
В восьмипоточном режиме пропускная способность выросла примерно втрое, а затраты в тактах сократились примерно вчетверо. При одном потоке на ядро расходы с патчами оказались лишь на 2–7% выше однопоточного режима. Остаточная разница при восьми потоках связана с работой двух потоков SMT на каждом ядре. Более крупные и многосокетные системы Торвальдс не тестировал.
Дополнительные замеры показывают, что результат зависит от нагрузки. В таблице ниже меньшее число тактов означает меньшие затраты ядра на одно чтение.
| Нагрузка | Такты ядра на чтение, база | Такты ядра на чтение, патчи |
|---|---|---|
| 16 байт, отдельный файл у каждого из 8 потоков | 827 | 730–732 |
| 16 байт, отдельный файл, 1 поток | 449–456 | 409–411 |
| 16 байт, 1 поток, отсутствующая пользовательская страница | 4117–4188 | 6543–6593 |
| 64 КиБ, 16 ошибок доступа к страницам | 57478–58073 | 59648–59822 |
| 64 КиБ, 1 поток | 7334–7346 | 6917–6929 |
| 64 КиБ, 8 потоков, один файл | 17948–18051 | 10925–11146 |
| 1 МиБ, 1 поток | 166544–171752 | 161315–166941 |
При обращении к отсутствующей пользовательской странице новый способ сначала получает исключение с отключённой обработкой страничных ошибок, затем передаёт чтение прежнему коду. Это добавляет одно исключение на операцию и замедляет такие запросы. Для чтения по 64 КиБ число инструкций сократилось на 5%, а по 1 МиБ – выросло на 6%.
Для чтения по 1 МиБ восемью потоками автор не сравнивает такты: частоты процессора между запусками различались. По времени на операцию при отдельных файлах результаты были сопоставимыми: 637–721 мкс без патчей и 616–660 мкс с ними. При общем файле два запуска дали 257–280 мкс и 215–230 мкс соответственно.
В более ранней сборке число событий Load HITM, измеренных через perf c2c при восьми потоках, сократилось с 30 538 до 101. Эти данные относятся к предыдущему варианту копирования и не полностью совпадают с текущей реализацией.
Отложенное освобождение памяти требует доработки
Освобождённые folio некоторое время остаются в очередях каждого процессора. Когда в очереди ожидает больше 1024 страниц, седьмой патч ускоряет завершение периода RCU; восьмой помогает соответствующей рабочей задаче получить процессорное время. Порог 1024 Торвальдс называет предварительным выбором.
В нагрузке, где каждый процесс циклически выделял и освобождал участки по 64 МиБ в memfd, средний объём ожидающей памяти снизился следующим образом. Измерения проводились каждые 2 мс; при четырёх и более процессах суммарный темп выделения и освобождения составлял около 22 ГиБ/с.
| Процессы | Среднее с патчами 1–6, МиБ | Среднее с патчами 1–8, МиБ | Пик с патчами 1–8, МиБ |
|---|---|---|---|
| 1 | 148 | 6,6 | 59 |
| 4 | 565 | 15 | 128 |
| 8 | 503 | 70 | 249 |
| 16 | 540 | 177 | 870 |
С первыми шестью патчами страницы ожидали 20–25 мс, а при четырёх и более процессах пиковый объём достигал 1,0–1,2 ГиБ. Две дополнительные правки снизили накопление памяти, сохранив темп работы нагрузки. При четырёх процессах ускоренное ожидание RCU запускалось около 4600 раз в секунду. Аналогичный цикл записи и обнуления файлов ext4 со скоростью 7–9 ГиБ/с снизил средний объём ожидающей памяти со 124 до 12 МиБ.
В виртуальной машине с 2 ГиБ памяти без swap тест Кирилла Шутемова, где четыре процесса выполняли цикл memfd, а ещё один 20 раз обращался к 1400 МиБ анонимной памяти, вызывал OOM killer при четырёх и восьми виртуальных процессорах с патчами 1–6. С полной серией тест завершался при одном, двух, четырёх и восьми процессорах без прямого освобождения памяти под давлением.
Для более ранней сборки Торвальдс также приводит два замера: цикл fadvise(DONTNEED) со скоростью освобождения 850 МиБ/с оставлял в ожидании максимум 28 МиБ при опросе каждые 12 мс; обнуление файла размером 256 МиБ – 208 МиБ, которые освобождались в течение 25 мс. До тестов с момента загрузки ноутбука отложенно освободили 3323 страницы, а для 3,29 млн страниц отсрочку не применяли из-за удаления inode.
Что ещё нужно проверить
Торвальдс перечислил проблемы, которые пока мешают считать серию готовой:
- Аллокатор не учитывает память, ожидающую завершения RCU. Дополнительные патчи сокращают очереди, но не исправляют сам учёт; при медленном завершении периода ожидания жёсткого ограничения их размера нет.
- Выделение непрерывных областей через alloc_contig_range() может завершаться ошибкой -EBUSY. В текущем виде это регрессия для CMA.
- Новый флаг страницы требует проверки конфигураций с MAXSMP, балансировкой NUMA и учётом неактивных страниц: по предварительному расчёту он занимает один из двух битов refs у MGLRU.
- Нужно оценить последствия отказа от SPLICE_F_MOVE для страниц кеша и явно обработать сценарий с vmsplice(SPLICE_F_GIFT), который сейчас блокируется побочным действием проверки FUSE.
- Для s390 не разобрано взаимодействие нового чтения с переводом folio в защищённое состояние.
- Не измерены расходы вне чтения: блокировки при отложенном освобождении, работа kworker и влияние ускоренных периодов RCU на другие задачи. Изменение планирования требует оценки сопровождающих планировщика.
- Аргументы о порядке операций ещё не проверены специальным тестом модели памяти; не все реализации migrate_folio изучены на предмет корректности нового сценария.
- Инструменты, считающие события trace_mm_filemap_get_pages и вызовы folio_mark_accessed(), будут видеть меньше попаданий в кеш: новый способ чтения их обходит.
Тест evict-race не обнаружил повреждённых чтений в 30-секундных запусках примерно по 100 млн операций. Проверка partial-fault также завершилась без ошибок за 1,15 млн чтений. Контрольный тест более ранней сборки с намеренно нарушенным отложенным освобождением обнаружил 12 и 138 неправильных чтений в двух запусках; для текущего варианта этот контроль не повторяли.
Проверки ограничились указанными тестами, виртуальной машиной, сборкой одной конфигурации x86-64 с GCC и работой ноутбука на этом ядре. В конфигурации включены CONFIG_DEBUG_VM, отложенное вытеснение и вытесняемая RCU; lockdep, CONFIG_DEBUG_ATOMIC_SLEEP и KASAN отключены. Итоговый коммит собирается без предупреждений. Более раннюю форму первых шести патчей собирали по отдельности, а последнюю из них – также с Clang; седьмой патч отдельно от восьмого не собирали.
Не проверены xfstests, разделение folio во время нового чтения, другие сценарии нехватки памяти, 32-битные системы, сборка и компоновка всего ядра с Clang, CONFIG_HARDENED_USERCOPY, другие компьютеры и модели вытеснения. Упомянутые сбои generic/095 и generic/741 относятся к попытке оптимизации 2024 года, а не к результатам нынешней серии.
Патчи доступны в отдельной ветке. Phoronix допускает их включение в период приёма изменений для Linux 7.4, но подтверждённого решения о версии выпуска и сроках нет.
Вывод
Оптимизация показывает, насколько дорого может обходиться синхронизация при параллельном чтении уже закешированных данных. Для приложений с такой нагрузкой результаты перспективны, однако регрессии и неполные проверки пока не позволяют переносить их на обычную работу Linux. Практический эффект для пользователей станет понятнее после доработки серии и испытаний на других системах.
Claude