Как стать автором
Обновить

Комментарии 88

Задайтесь вопросом: что самое плохое может случиться, если отключится питание машины и потеряются данные в локальной файловой системе? В большинстве случаев ответом будет ничего. Вы спроектировали свою систему как stateless и устойчивую к сбоям. Вы управляете серверами как cattle. Вы работаете с локальными файловыми системами как с временными устройствами. Поэтому если машина сбойнёт, вы создадите новую ей на замену.

так-то оно так, но оно работает только вы узнаете о порче данных (ну или вы перезаливаете данные после каждой нештатной перезагрузки).


поясню: из-за переупорядочивания запросов может оказаться, что N последних запросов успешно записались на диск, а один запрос перед ними — нет. после перезагрузки кратко проверяется состояние файловой системы/БД/etc, последние изменения выглядят нормальными, полный тест никто не делает — слишком долго, да, зачастую, и механизмы не предусмотрены. в результате, то, что часть запросов на запись «потерялась», может остаться незамеченными.

А в журнал они как записываются? Тоже будучи переупорядоченными?

Очень сильно зависит от настроек и типа БД.
В mysql за последний год я два раза ловил неремонтируемую базу данных, благо делаю бекапы нормальные.

Вообще мне интереснее был именно журнал ФС - с бд мне более-менее понятно как все работает, даже если переупорядочить. Точнее, как это не допустить: в продакшне мои сервера все стоят с noop io scheduler.

Scheduler тут вроде как вообще не особо важен(он влияет на block device). Все современные базы данных делают запись в несколько потоков и почти все — еще и в лог пишут в несколько потоков.
Ибо SSD в вариантах «пишем в один поток» и «пишем в 10 потоков» дают совсем разные результаты.
Журналы FS работают тоже неупорядочено кроме logfs и BTRFS(не со всеми настройками).
Вообще базы данных должны лог писать в другую FS как минимум. В идеале — на отдельный и более быстрый накопитель(NVME?)

М-м-м.... Я вот не очень уверен про несколько потоков в лог. Собственно, без обеспечения правильной последовательности данных в логе, никакого ACID не будет.

Я не говорю, что никто не научился так делать, но можно пожалуйста примеры - почитать, что я пропустил?

Ну и переупорядочивать запись, обеспечивая равномерный (или ещё какой, какой надо) отклик того самого block device (а ssd уже не block? а кто?) призван как раз io scheduler. А какой ещё уровень ОС переупорядочивает запись? Контроллер SSD может, но он не ОС...

Так происходит, к примеру, когда в mysql выставлено binlog_commit_wait_usec=50000
и write_threads больше 1.
порядок попадания в лог будет вполне случайный.

База данных может(и будет) переупорядочивать запись.
Сброс dirty buffer в файловой системе — тоже.
Просто потому, что так проще реализовать. Сброс будет выполнен по порядку размещения в памяти.

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

Интересно, почему медленный medoc?

Потому что криво написан. А ещё он оставляет за собой кэш хвосты в скрытой директории. И если не чистить, то ваш диск (системный) очень быстро заполнится.

Криво написан - это мне говорил человек, который общался с разрабами медка. Говорят такое легаси и bad practice, что уже надо с 0 писать. Ну понятно что с 0 ничего не будет, пока окончательно не умрёт

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

НЛО прилетело и опубликовало эту надпись здесь

В linux тоже создание процессов и массовое открытие/закрытие файлов не бесплатные(более дешевые, но не бесплатные) потому всегда нужно думать о ресурсах которые израсходует приложение. Даже в linux рано или поздно упрешься в их нехватку.

ulimit есть: если файловых дескрипторов мало (во многих дистрибах по дефолту 4096 доступно, хотя сейчас уже и не 90-е годы), можно лимит увеличить, файловые дескрипторы в таблице ядра лежат и потребляют память (а не CPU), чего на современных компьютерах много.

Не зря в wine esync сделан через файловые дескрипторы. Так проще всего оказалось управлять примитивами синхронизации в ntdll (через select/poll/epoll на группе fd), практически не нагружая wineserver, что дает приличный профит в многопоточных приложениях, включая реализации d3d9/10/11 через vulkan.

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

Современные ОС и их api уже настолько большие, что никто чисто физически уже не может быть в курсе о всех потенциальных проблемах, чтобы корректно писать код. Поэтому общепринято полагаться на то, что в используемых библиотеках кто-то уже всё сделал правильно. К сожалению, в реальной жизни это оказывается не так в большом количестве случаев.


А по работе с дисками и файловыми системами всё настолько сейчас плохо, что тут, наверное, уже можно многотомник написать про то, как делать не надо. При этом даже "правильные" решения всё равно работают просто адски медленно — и проблема не в дисках.


Вот у меня есть винт с ~15 млн файлами. Размер MFT там — 14,57 Гб. Этот диск вообще невозможно дефрагментировать — ни один софт, который я бы ни пробовал просто не в состоянии это сделать за время, измеряемое не годами.


И вот у меня есть самописная программа на .net, которая должна пробегать по этим файлам в поисках появившихся новых, о которых еще нет записей в базе. Ну, не суть важно зачем это нужно, дело не в этом. Дело в том, что только процесс сканирования папки с примерно 5-8 млн файлов (где и происходят основные добавления файлов) оптимизированным алгоритмом занимает от получаса до часа. И это мне всего лишь нужны имена файлов и их размеры! При этом если использовать стандартные api .net — процесс займет в 2 и более раз больше времени (они читаю больше данных, чем мне реально надо -> требуется больше времени). В чем заключается оптимизация? Да просто дергается напрямую функция FindNextFile из kernel32.dll. Причем, судя по всему, это — максимально доступная оптимизация в windows. Дальше только учиться вручную читать байтики из записей ntfs. Кстати, eplorer работает еще "быстрее" — ведь ему еще и на права доступа посмотреть надо.


Замечу, что прочитать все 14,57 Гб MFT с диска можно за время, которое измеряется минутами, никак не теми часами, которые требуются для сканирования содержимого папки в объективной реальности.


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

Всё начинает сильно тормозить, если у вас файлики лежат кучей в одной папке.
Если вы не раскидываете файлики в подпапки — попробуйте это делать, чтобы в каждой папке было не особо много файлов(~50 тысяч).
Например, можно создавать подпапки по первым двум-трём буквам имени файла. Если файлов всё равно много — создаёте в подпапке другие подпапки со следующими буквами.
Так гит хранит блобы, например.

Там куча папок, внутри подпапки, ну — уровней десять точно есть. Файлы лежат только в конечных папках в цепочке, внутри 60-300 штук, не более.


Вообще, проблема не в том, как файлы лежат. Проблема в том, что список файлов в 21ом году мы все еще получаем парой функций FindFirst/FindNext, идеология которых тянется из седой древности времен первых MSDOS, если не раньше. Оно в принципе не предназначено для какого-то оптимального по скорости чтения списка файлов. Поверх всего этого за 30 лет навернули десяток уровней кеширования, упреждающего чтения и слоев абстракций, но внутри — вот это вот древнее. Поэтому чтобы получить список содержимого папки надо делать кучу рандомных чтений по диску с десятком приседаний на каждый файл в угоду безопасности, вместо того, чтобы считать требуемые данные одним куском, еще и оптимизировав доступ с учетом особенностей движения головки диска...


И, кстати, насколько я помню — в линухах используют примерно такую же пару функций, со всеми вытекающими последствиями.




А так-то у меня достаточно хитрая задачка: мне надо искать папки, в которых есть дубли файлов. Т.е. есть папки с файлами на одном диске, надо проверить, что на втором диске (том самом, с кучей файлов) еще нет папки с такими же файлами. Ну там хеши считаются и т.п. Чтобы не считать их повторно — хеши от оригинальных файлов я в базу кладу, но так как эта папка пополняется не постоянно и наскоками — делать это в реальном времени через, например, драйвер-фильтр файловой системы смысла не имеет. Приходится сканировать список файлов и сверять с тем, что уже в базе есть. Чтобы повторно не пересчитывать — сначала смотрю на имя файла и размеры, если различаются или таких нет — пересчитываем хеши, обновляем в базе. Соответственно, входящие папки сверяем уже с базой, а не с живыми файлами с диска.


Так вот, главная задержка не в том, чтобы хеши у новых файлов посчитать. Нет! Главная проблема — просто перебрать список файлов. Если там вообще не будет новых файлов, то процесс занимает 30 минут минимум — ну, диск на 5400 оборотов + еще и фрагментированный, что тоже добавляет задержек. Но основные проблемы тут именно в базовых функциях ОС, не в диске. Даже если диск сильно фрагментировать, мы все равно получим скорости чтения порядка 20-30 мб/сек. И если предположить, что из тех 14,57 гб MFT 2/3 — наша папка, то даже при таких скоростях требуемый объем прочитается за 8-10 минут. Но MFT обычно лежит крупными кусками, т.е. у нас будет чтение на всей скорости диска, 90-180 мб/сек (смотря какой диск и где файл), т.е. процесс потенциально должен занимать 1-2 минуты, а не 30.

Можно пойти чуть глубже и использовать нативные API windows: NtCreateFile и NtQueryDirectoryFile, подсмотрев их использование в ReactOS. Не знаю насколько это быстрее, но есть вариант прочитать все имена файлов сразу. Правда, не факт, что это будет быстрее, random read'ов всё равно много будет

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


В любом случае, вы просто представьте, сколько в системе софта, которому надо получать списки файлов. И каждый делает это предельно неоптимальным способом, просто потому что он — стандартный! А задержки-то суммируются и накладываются друг на друга...

Вот что мне подсказали знающие люди:


Мне известно несколько проектов, которые заменили FindFirstFileExW(, FindExInfoBasic,, FindExSearchNameMatch,, FIND_FIRST_EX_LARGE_FETCH) на вызов NtQueryDirectoryFile() напрямую, и получили прирост производительности:


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

  • Нужно помнить, что багнутый драйвер может переполнить переданный в NtQueryDirectoryFile() буфер — Crash on VirtualPC (Virtual Machine Folder Sharing Driver):
    • основные моменты первого сообщения:
      So: we detected a heap corruption during freeing a block of heap memory — inside FindClose API call.

      8-byte header + 0x1000 requested bytes + 16-byte footer

      Library ntdll.dll checks footer bytes before freeing a heap block — values other than 0xAB mean that a buffer overflow has occurred

      After few calls to NtQueryDirectoryFile I noticed that the footer of our 0x1000-byte block of memory has been overwritten with four zeroes
    • продолжение:
      Now some details:
      As I checked in the code of mrxvpc.sys, the bug is specific to ntdll.dll!NtQueryDirectoryFile function, no other functions are affected. NtQueryDirectoryFile calls a filesystem driver, in our case — for shared drive letters — the buggy mrxvpc.sys, which may overflow data buffer supplied by NtQueryDirectoryFile.

    Следовательно, необходимо аккуратно выбирать местоположение буфера в памяти:
    • хранение буфера в стеке нити — это очень плохая идея;
    • при использовании VirtualAlloc() (гранулярность 64 KiB) — позаботиться о корректной обработке переполнения буфера;
    • при использовании готовых (сторонних) аллокаторов памяти — включить проверку переполнения буфера.
  • Если посмотреть на официальное описание функции NtQueryDirectoryFile() (MSDN Microsoft Docs), и сравнить его с описанием и кодом этой же функции в Windows Research Kernel (WRK v1.2), то внимание привлечет различие в описании параметра RestartScan:
    • Microsoft Docs (GitHub):
      RestartScan [in]
      Set to TRUE if the scan is to start at the first entry in the directory. Set to FALSE if resuming the scan from a previous call.
      When the NtQueryDirectoryFile routine is called for a particular handle, the RestartScan parameter is treated as if it were set to TRUE, regardless of its value. On subsequent NtQueryDirectoryFile calls, the value of the RestartScan parameter is honored.
    • WRK v1.2:
      RestartScan — Supplies a BOOLEAN value that, if TRUE, indicates that the
      scan should be restarted from the beginning. This parameter must be
      set to TRUE by the caller the first time the service is invoked.
      NtQueryDirectoryFile()BuildQueryDirectoryIrp():
          if (RestartScan) {
              irpSp->Flags = SL_RESTART_SCAN;
          }
      
      т.е. флаг SL_RESTART_SCAN устанавливается только при явном задании.

    Возможно, где-то дальше FileHandle проверяется на «новизну» (первое использование в «service»), и устанавливается SL_RESTART_SCAN, т.е. в WRK v1.2 дано некорректное описание. Но возможно, и что в какой-то момент поведение функции NtQueryDirectoryFile() изменилось (в Microsoft Docs описано новое поведение). К сожалению, основываясь только на информации из документации, не получится определить версию системы, в которой изменилось поведение:
    • в Microsoft Docs написоно только:
      Minimum supported client: Available starting with Windows XP.
    • WRK v1.2это NT 5.2:
      The Windows Research Kernel v1.2 contains the sources for the core of
      the Windows (NTOS) kernel and a build environment for a kernel that will run on
      x86 (Windows Server 2003 Service Pack 1) and
      amd64 (Windows XP x64 Professional)

    Можно посмотреть, как используют NtQueryDirectoryFile() в сторонних проектах:
    • ReactOS при первом вызове NtQueryDirectoryFile(…,TRUE)FindFirstFileExW()) следует описанию из WRK v1.2;
    • а упомянутые выше git-for-windows и vs-chromium — следуют описанию из Microsoft Docs, т.е. всегда вызывают NtQueryDirectoryFile(…,FALSE):

      То есть, возможно, они не смогут корректно работать ни под ReactOS, ни под WRK v1.2, ни …

      Возвращаясь к 18%, 30-40% приросту производительности — надеюсь, что при замерах производительности они проверили, что обработалось столько же файлов/директорий, сколько было при использовании FindFirstFileExW()



…, но есть вариант прочитать все имена файлов сразу.
FindNextFile() тоже читает (во внутренний буфер) сразу несколько «файловых записей» — столько, сколько поместится в буфер:






И ещё один часто задаваемый вопрос (обычно его задают те, кто пишет драйверы, т.е. те, кто часто работают с Zw* и Nt* функциями):
Почему FindFirstFileEx() запрашивает только одну «файловую запись» (NtQueryDirectoryFile() вызывается с (,,,,,,,,ReturnSingleEntry=TRUE,,))? То есть вместо того, чтобы прочитать всё содержимое небольшой директории в первом же вызове FindFirstFileEx() (за минимум 2 переключения контекста: user_space→kernel_space→user_space) приходится вызывать FindFirstFileEx();FindNextFile(); (за минимум 4 переключения контекста: user_space→kernel_space→user_space→kernel_space→user_space)⁉︎

Оказывается, что в прикладном ПО FindFirstFileEx() часто используется, чтобы определить, пуста ли директория. Поэтому для уменьшения задержки (если в директории есть файлы, то из MFT считывается только одна запись (соответствующая первому файлу), вместо чтения множества записей, раскиданных по разным местам внутри MFT) добавили эту оптимизацию. Также бонусом это уменьшило количество используемой памяти при рекурсивном обходе директорий, в случае, если в директории находится только одна поддиректория, например: <несколько директорий>\<одна директория>\<несколько директорий>\<одна директория>\….

А необходимость определить «пуста ли директория» чаще всего возникает при отображении TreeView элемента — нужно определить, показывать ли значок ⊞ рядом с папкой или нет.




Начиная с Windows 8¹ для user-space в Win32 появилась универсальная функция GetFileInformationByHandleEx() (директория — это тоже «файл»; некоторые возвращаемые ею структуры аналогичны структурам NtQueryDirectoryFile()) — см. раздел «Remarks»: любой FileInformationClass (File*Info), который имеет пару в виде класса File*RestartInfo, перечисляет содержимое дирректории.
Краткое описание всех FileInformationClass, включая поведение функции при их использовании, находится здесь.

Кстати, в описании структуры FILE_FULL_DIR_INFO описано отличие класса FileFullDirectoryInfo от FileIdBothDirectoryInfo с точки зрения задержек доступа к данным:
The FILE_FULL_DIR_INFO structure is a subset of the information in the FILE_ID_BOTH_DIR_INFO structure. If the additional information is not needed then the operation will be faster as it comes from the directory entry;

FILE_ID_BOTH_DIR_INFO contains information from both the directory entry and the Master File Table (MFT).

¹ — сама функция доступна начиная с Windows Vista (…, Windows Server 2003 и Windows XP), но некоторые классы (в частности FileFullDirectory*) доступны только начиная с Windows 8:
Windows Server 2008 R2, Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported before Windows 8 and Windows Server 2012
Но MFT обычно лежит крупными кусками

Не поменялось ли что в последних версиях ядра — не смотрел, но раньше для MFT была специальная резервная область, куда она росла, а потому доступ к ней был линейным. Но все это было хорошо, пока размер MFT не становился больше резервной области — после чего MFT росла так же, как и любой другой файл: по мере необходимости, в свободном на данный момент месте. Другой способ получить тот же эффект состоял в том, чтобы забить диск почти до упора: в некоторый момент файловая система жертвовала этой резервной областью, после чего MFT опять-таки росла, как получится, и могла сильно фрагментироваться.
Про фрагментацию MFT обычно можно было узнать из выдачи сторонней программы дефрагментации (вроде бы, в какой-то версии ее и встроенная показывала, но в Win7, например, я такого не увидел).
Даже если диск сильно фрагментировать, мы все равно получим скорости чтения порядка 20-30 мб/сек

Это — большой оптимизм, реально, по моим наблюдениям — чаще на порядок ниже.
Если файл сильно фрагментирован, то скорость его чтения упирается в IOPS (I/O operations per second) диска (т.к. требуется поиск), а с IOPS у пятитысячника довольно кисло, ориентировочно — где-то в районе 50. И если MFT росла по кластеру за раз, то даже при самом большом кластере 64К и прочих идеальных условиях скорость чтения одной только ее не может быть больше 3,2 МБ/сек. А содержимое папки — оно не в MFT хранится, а в индексе (и читается, небось, произвольным доступом), что скорости не прибавляет.
А если ещё и памяти под кэш маловато, чтобы MFT целиком оказалась в нем и никуда бы оттуда не уехала — все будет ещё медленнее и печальнее.
PS Я подобную проблему видел у себя в давние времена, лет 15 назад, только там масштабы бедствия были поменьше — не миллионы файлов а немногие сотни тысяч в одной папке, а потому и времена были сильно меньше часа. Но вот если попытаться открыть такую папку в файловом менеджере (не только в Explorer, но и, к примеру, в Far) — там все было плохо.

Ну вот в целом — всё круто же, прям наглядно видно торжество алгоритмов)


Дефрагментаторы все-таки обычно начинают процесс именно с mft поэтому у меня она, к счастью, более-менее крупными кусками. Но сильно это погоды не делает — я бы не сказал, что процесс сканирования стал быстрее после попыток дефрагментации. Но я специально это не мерял, конечно, так что может что-то там и есть, но, видимо, не сильно заметно.


немногие сотни тысяч в одной папке, а потому и времена были сильно меньше часа. Но вот если попытаться открыть такую папку в файловом менеджере (не только в Explorer, но и, к примеру, в Far) — там все было плохо.

Не, тут тоже весело, но причина попроще. Чтобы узнать кол-во файлов — надо просканить папку, чтобы ее просканить — надо получить все записи о всех файлах. Насколько я помню, другого способа узнать кол-во записей — нет. Это приводит к тому, что файловый менеджер должен выделять память в процессе сканирования папки — последовательно, относительно мелкими кусками (привет, фрагментация памяти!). Плюс само сканирование запрашивает много избыточной информации — даты создания/изменения, атрибуты, права и т.п. Всё это хранится в разных местах, все это надо прочитать, обработать, положить в память. А если сюда еще и вытеснение в своп добавить — ну, на старых системах и памяти было мало...


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

НЛО прилетело и опубликовало эту надпись здесь

В приложении Everything, как я понял, в итоге плюнули на это всё. Они получают низкоуровневый доступ к диску и сами парсят NTFS.

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

А что если скопировать прямо в проводнике на другой диск? Что-то вроде дефрагментации для бедных. Если будет копироваться по 200 файлов в секунду, то уйдут одни сутки.

Ну только это и остается, если задача именно дефрагментировать. Но в условиях наличия 15 млн файлов и постоянного постепенного пополнения их количества — смысла в дефрагментации, имхо, уже как-то не просматривается.


Кстати, бонус: берем чистый свежеотформатированный раздел. Берем системную функцию копирования — ну или проводник. Копируем на этот винт, ну, например, видео на 1,5-5 гб размером. На выходе получаем файл, побитый на несколько фрагментов и раскиданный по всей поверхности диска. Казалось бы, что мешает его положить в начало/конец диска одним куском? Ан нет...


Еще очень весело, если взять большой файл с крупными кусками данных, которые хорошо жмутся (недокачанный торрент?) и включить на нем галочку "сжимать содержимое". Файл будет порезан на кучу кусков, куски раскиданы по всему диску рандомно, дефрагментаторы как правило не в состоянии его собрать в какую-то более-менее компактную кучу — привет нелинейный рост фрагментации и далее, когда файл докачают/удалят.

А что за задача такая? Почему именно файловая система, а не база данных?

Ну хранить бинарные данные (картинки) в базе данных — как-то не очень хорошо. Кроме того мы автоматом лишаемся кучи всякого полезного софта, который теперь придется писать самому, с учетом, что данные — в БД.


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


Одно время я думал использовать альтернативы в виде архивов или монтируемого vhd-образа. Но всё упирается в вопрос, а как это восстанавливать, если что-то сбойнет или на диске появится бэдблок от времени?


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

WinRAR, например, умеет добавлять данные для восстановления в архив

Ну да, только они тоже могут быть повреждены. Пока у вас архив относительно маленький — шансы потерять данные тоже пропорционально маленькие. А если мы начинаем говорить о файле размером в 2-3 тб, то малейшее его повреждение в середине приводит к тому, что еще не поврежденные данные из него вытащить — очень сложно. Причем это вообще глобальная проблема для кучи форматов и в большинстве случаев даже соотв. софта для этого в природе не существует.
Вот в итоге вам что страшнее? Потерять 2-3 тб разом или десяток файлов на 100-200 кб из этого объема?

НЛО прилетело и опубликовало эту надпись здесь
> Копируем на этот винт, ну, например, видео на 1,5-5 гб размером. На выходе получаем файл, побитый на несколько фрагментов и раскиданный по всей поверхности диска. Казалось бы, что мешает его положить в начало/конец диска одним куском? Ан нет…

В книге «Design and implementation of 4.4BSD» в разделе про UFS писали, что специально переходят на следующую полосу (cylinder group) если файл заполняет более 1/2 полосы, и что это эмпирически дало заметное улучшение ситуации с фрагментацией.
Возможно, тут похожие мотивы.

Может имеет смысл не пробегать по всем файлам а следить за их созданием и изменением при помощи ReadDirectoryChangesW?

Это требует чтобы программа была запущена всё время, а при запуске всё равно понадобится часовое сканирование.


Лучше уж вот с этим разобраться: https://docs.microsoft.com/en-us/windows/win32/fileio/change-journals

Вариант - отдельная служба, которая ведет журнал изменений/Консольное приложение запускаемое через nssm как служба.

Лучше уж вот с этим разобраться: docs.microsoft.com/en-us/windows/win32/fileio/change-journals
Тут есть нюанс: этот журнал по размеру ограничен (причем, по умолчнию — ограничен сильно). И, к примеру, Служба репликации файлов, которая была на него завязана (она использовлась в AD для репликации шаблонов групповых политик), стоило ей полежать по недосмотру пару дней, часто нарывалась на переполнение этого журнала даже на контроллерах домена, где активность на системном диске обычно невелика (печально известная ошибка JRNL_WRAP_ERROR).
Так что программу отслеживания лучше держать запущенной постоянно.
Ну и при высокой интенсивности дисковых операций та же служба репликации файлов, но используемая для репликации DFS (т.е., фактически — содержимого общих папок на сервере) тоже могла не успеть обработать поток изменений и вываливалась с аналогияной ошибкой.
Ну, и при некорректном завершении работы этот журнал не так уж редко портился.
влияние антивируса или "индексатора" файлов на скорость закрытия

А где этому учат? И где во всех насквозь асинхронных фреймворках API для асинхронного закрытия файлов?

А как их закрыть асинхронно? Системный вызов CloseHandle — он существенно синхронный, асинхронность к нему не приделаешь: она в системе на этот самый Handle завязана.
Разве что как в статье — раскидать по пулу потоков.
PS Интересно, если папку исключить из real-time сканирования — будет ли драйвер фильтра антивируса обрабывать закрытие файла существенно быстрее?

Если я правильно понимаю, то антивирус влияет не только на скорость закрытия файлов. VSCode и IDE от JetBrains при первом запуске на винде спрашивают о том, стоит ли добавлять папку с проектами в исключения Windows Defender. Методом научного тыка выяснено, что если исключение не создавать - скорость работы падает. При чем вся, начиная от банального pip install, заканчивая pytest

С влиянием антивируса сталкивается каждый второй. Когда я работал админом, каждый второй программист приходил и требовал добавить его каталог в исключения антивируса "а то тормозит". Да даже на примере элементарного копирования каталога с большим количеством файлов видно, если работаешь с разными OS. А может не стоит хранить столько файлов в NTFS? VFS, как microsoft поступила с gitfs? Или упаковать их в базу данных, если это документы? Или выбрать FS которая более приспособлена для хранения? их десятки готовых.

Вот подобные тормоза и тугой терминал, причём pwsh вообще прямо неприлично долго стартует, и мешают мне с комфортом разрабатывать на Windows машине. Причём ситуация вообще не меняется, даже Windows Terminal её не спас, хотя жизнь и стала лучше.

Думаю это немаловажный фактор, что в среде разработчиков так популярны Linux и Mac OS. И никакой WSL этого не исправит ведь.

WSL нет. Но WSL2 виртуальная машина и антивирус в нее лезть не должен. Хотя там тоже будет просадка из-за слоя виртуализации.

С точки зрения ОС и оборудования все ваши действия не асинхронны. Это нужно понимать.

Неверно. С точки зрения оборудования HDD (и его совремнные эквиваленты), да и некоторые другие устройства — тоже, находят и передают в память запрошенные данные асинхронно, пользуясь прямым доступом к памяти, а процессор в это время выполняет что-то другое.
С точки зрения ОС действия тоже могут быть асинхронными: в Windows асинхронные (без блокировки потока выполнения) операции ввода/вывода был изначально, начиная с самой первой версии NT, и в Linux они AFAIK тоже теперь реализованы.
> С точки зрения ОС и оборудования все ваши действия не асинхронны.

Вот я, например, выдал aio_read() на блок данных и попросил нотификацию через kqueue. Запрос встал где-то в очередь.
Запрос преобразовался в запрос к диску. У диска поддерживаются тегированные команды. Ушла команда «тег=4 прочитать блоки 35410800-35410815» по интерфейсу к диску. У диска в это время ещё ждут команды с тегами 0,1,6,7,19.
Пусть диск — магнитный (HDD). Он заглянул в таблицу ремапа, проверил все полученные физические LBA и нашёл, что сейчас он двигается по возрастанию блоков, и команды упорядочённые в порядке возрастания физических LBA — 1,19,7,4,6,0.
Ближайшим он отдаст содержимое блока 17254987 для команды с тегом 1. Потом блока 19443009 для команды с тегом 19. И так далее.
На каждый прочитанный блок ОС будет размораживать какое-то ожидание и отдавать данные заказчику (юзерленд или ядерный драйвер, если это, например, структуры FS).
Когда-нибудь дойдёт запрос и до нашей aio_read(). А тем временем процесс успел поставить в очередь ещё десяток aio_write(), которые дойдут до того же диска и будут отработаны в свою очередь.

> Это нужно понимать.

Что мы должны «понимать» в описанном и почему я не должен считать все эти операции асинхронными?

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

Заполнив очередь запросами вы получите торможение

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

> Заполнив очередь запросами вы получите торможение.

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

Разумеется, можно придумать любую самую кривую стратегию шедулинга запросов на любом уровне (очередь в приложении, в ОС, в контроллере диска, в самом диске и т.п.), что какие-то операции вообще остановятся. Есть типовые ошибки подобного рода. Но промышленно ценные реализации такого не допускают — они дают гарантии с чётко определёнными пределами времён отработки каждого запроса.

В том же примере с HDD, если вы берёте следующий блок на выполнение как ближайший к текущему, это может заклинить его в узком участке, а если вы реализуете «лифтовый» оптимизатор, который сначала отрабатывает все поступившие запросы при движении вперёд, пока есть из-за чего двигаться, а потом — при движении назад, и так по кругу — будет и достаточно быстрое, и эффективное выполнение.

Ну а для случаев, когда нужны приоритеты — явно задаются приоритеты вместе с полиси, которую они определяют (гарантированные доли времени, жёсткие предпочтения, или что там будет).

Вы, наверно, имеете в виду вариант, когда из-за отсутствия приоритизации на нижнем уровне приходится реализовывать её на более высоком. Да, есть и такие варианты. Например, может быть полиси типа «на каждый запрос приоритета 2 отдавать не более 10 запросов приоритета 1 (ниже) в ОС; следующие тормозить у себя до получения ответа». (Я бы применил что-то в духе hierarchical token bucket с лимитированием неотвеченной полосы… но это всё тестить в реале надо.) Но доля случаев необходимости такой приоритизации, насколько я вижу, мала, и с ходу даже не вспомню приложение, которое бы делало что-то подобное. У Oracle DB такого нет?

А если у вас есть логическая зависимость между запросами (как у СУБД, сначала пишется запись об изменении в журнал, потом в основное хранилище базы, и потом запись о завершении транзакции в журнал), то её и надо реализовывать напрямую через постановку следующей операции в коллбэке от предыдущей, а не просто через последовательность в очереди — которая в таких условиях и не должна работать.

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

> Мой изначальный посыл был о том, что об этом стоит задумываться.

Да. Но — в определённых типах задач — где подобные вопросы могут встать изначально.

> выяснится что где-то на нижнем уровне какая-то его часть упирается во вполне синхронный процесс.

Верно. Процессор выполняет команды вполне синхронно (не учитываем OoO, у него протяжённость невелика). Любой асинхронный процесс состоит из множества мелких синхронных кусков. Важно не тормозиться на них там, где этого не ожидаешь.
Пусть диск — магнитный (HDD). Он заглянул в таблицу ремапа, проверил все полученные физические LBA и нашёл, что сейчас он двигается по возрастанию блоков, и команды упорядочённые в порядке возрастания физических LBA — 1,19,7,4,6,0.
Ближайшим он отдаст содержимое блока 17254987 для команды с тегом 1. Потом блока 19443009 для команды с тегом 19. И так далее.

(реплика, не имеющая прямого отношения к беседе)
у меня есть впечатление, что современные hdd устроены хитрее, они учитывают не только и не столько близость дорожек, сколько то, над каким сектором окажется головка после перемещения.
именно это отправило в отставку старое эмпирическое правило iops≈rpm/60, сейчас диски при достаточной глубине очереди выдают в разы больше.

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

Это тоже достаточно старо — драйвер UFS на технике уровня PDP-11, VAX учитывал такое. В заголовке суперблока UFS поэтому были всякие rotational delay конкретной техники.
Но на HDD с начала 90-х, с тем, как в диске разные области стали иметь разные временны́е плотности данных для уравнивания пространственных плотностей — эти данные драйверу уровня ОС уже недоступны. Уровень ремапа номера сектора не только производит подстановки в таблице замен, но и преобразует linear block address в комбинацию зона — цилиндр — головка — сектор, с тем, что разные зоны имеют разное количество секторов на дорожку. (Тогда же геометрическая адресация, видимая на шине, перестала иметь реальный смысл и осталась только как легаси.)

> именно это отправило в отставку старое эмпирическое правило iops≈rpm/60, сейчас диски при достаточной глубине очереди выдают в разы больше.

Да.

По поводу оптимизации fsync для stateless-серверов. Разговаривал как-то с программистом, который допиливал файловую систему как раз для таких виртуализованных систем облачного провайдера. Говорит, пришел в проект, а там все настолько было выкинуто из ядра, что даже порушили консистентность журналирования по метаданным: сервер уходит в перезагрузку, потом в него, если надо, заливаются новые версии снапшотов виртуалок, и сервер снова в строю, но базовая fs уже находится в неконсистентном состоянии. Сыпались сервисы из-за потери данных, со временем корраптилась файловая система, и даже понять не могли, почему. В итоге часть оптимизаций пришлось откатывать назад, потому что альтернативный workaround выходил слишком дорогим по времени - после любой случайной перезагрузки полная переналивка диска с нуля.

Да ща все дистрибы на него потихоньку съедут, zstd в виде консольной команды уже везде доступен, и tar уже ключ --ztsd более 3 лет понимает.

Собственно багу в Debian от Canonical по поводу поддержки 3 года (и мимо 11 релиза).

Возможно, в том числе из-за этого кажется, что Windows медленнее Linux.

Слово "кажется" здесь лишнее, выше по тексту же чётко сказано что на Windows процессы создаются медленнее.

в последнее десятилетие производительность одного ядра ЦП примерно находилась на плато.

Ну да, конечно. Сравните показатели десятилетних Intel 2xxx с современными Intel 11xxx.

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

Зато Windows потоки намного дешевле линуксовых

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

Скорость создания потоков, которую измерили по вашей ссылке, не имеет значения: потоки в подавляющем большинстве приложений берутся из пула.

Значение имеет скорость переключения между потоками и поддержка множества потоков операционными системами.

Ну вы ссылку на бенчмарки-то давать будете?

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

Однажды столкнулся с дикими тормозами на ноуте (кажется это был Делл) с i5 на свежей Win10. В какой-то момент что-то пошло не так и ядра не поднимали частоту с 500 МГц в простое до положенных 2,2 ГГц в рабочем режиме. В какой момент это произошло - непонятно, пользователь грамотный, дичь не творил.
Был перечитан весь интернет, испробованы все способы, ничего не помогло.
Переустановка Win с нуля решила вопрос. Такая история.

НЛО прилетело и опубликовало эту надпись здесь

Спасибо, такой гипотезы я ни на одном форуме не встречал. Кстати, странно, что при работе от батареи (полностью заряженной) частота так же не повышалась.
Проверить сейчас не могу, но при случае буду готов, спасибо.

Насчет «перекомпиляции» на amd_x64-v4 — это, конечно, в теории классно. Но на практике учитывая количество пакетов — практически всегда требует менять что-то в пакетах для совместимости, и не всегда это видно при компиляции. Разработчиков много, причем подавляющее большинство, включая пишущих ассемблерные вставки, не знает даже о самом факте возможности таких проблем.

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

Добавьте также пакеты в генту, в которых есть патчи генту.
Я лично делал такое только с rhel пакетами, и там постоянно какие-то бяки вылазят.
В общем такая операция требует до недели работы вполне себе дорогого специалиста с специфичными скилами.
А разница в производительности — ну процентов 5-10 обычно.
Плюс учтите, что с появлением апдейтов надо повторять.
А если не повторять, то, к примеру, для mysql от апдейта может быть ускорение больше, чем от перекомпиляции с расширенными инструкциями.

Последние оськи Windows живут своей сказочной жизнью отдельно от установленного ПО. Microsoft программеры наплодили столько сервисов, что процессор просто захлёбывается на стартапе. Использование affinity для svchost, выделяя им для работы пару процессорных ядер, иногда помогает. Компьютер оживает. GUI программ начинают работать с меньшими лагами. Однако, проги опирающиеся на backend начинают тормозить. Поэтому приходится периодически профилировать машину под определённые задачи.

не понятно, почему наплодив столько сервисов, они забили сделать хотя бы сниженный приоритет для них. Из всех системных, что видел, только cервис оптимизации томов работает со сниженным приоритетом, остальное - везде обычный.

cами сделали уровни приоритета для цп, io и памяти - но ими не пользуются.

Это какие приложения backend из-за svchost начинают тормозить?
или вы про торможение всех экщемпляров процесса а не только те что грузят машину обновлениями/телеметрией и службами, которые нужны только майкрософту.

Если бы я управлял набором серверов, состоящим из тысяч машин, у меня было бы сильное искушение скомпилировать все пакеты из исходников, выбрав в качестве целевого уровень современной микроархитектуры.

Интересно, по какой причине облачные провайдеры используют что-то подобное RedHat/Debian, но почти никогда Gentoo или Calculate? Понятно, что все эти флаги - штука специфичная (и не серебряная пуля, как написано выше), но там много и других преимуществ...

Корпоративные макаки не осиливают USE флаги?

по очень простой причине.
вот у меня сейчас есть система, в которой мне нужно поддерживать up-to-date состояние всех пакетов.
в среднем меняется три пакета каждую неделю.
а в случае генту это перекомпиляция трех пакетов каждую неделю. на каждой из десятка-сотни машин(ну или перенос организовывать).
этим заниматься, конечно, можно. Но по факту это +один выделенный на задачу человек.

Время своё не жалко? Гуглите ansible, пора уже выходить из пещеры.

Время не жалко, эта операция в контракте прописана как ручная и оплачивается по рейту 120 в час. К тому же за ней стоит ручная проверка состояния(автоматическая выполняется независимо, естественно, не руками), в которую входит sql profiling(вроде как не автоматизируется никак, не знаю как можно обьяснить системе какией из sql критично замедлилися, какие нет. Типа замедлилися, но система переживет).
А вот времени делать компиляцию жалко, да.
на каждой из десятка-сотни машин

Свое зеркало с бинарями на выделенном билд-сервере не поможет?
Такие и для дебианов делают, потому что сотни серверов, обновляющих один и тот же пакет — это чересчур. Для генту это делали еще лет 15 назад, когда этот компы крутил на полставки.

Поможет, если одинаковые окружения везде.
В любом случае это не то, что делается пару часов в месяц.
Не всегда стоят эти проценты мороки.

Решил более детально замерить различные аспекты производительности создания процессов в Windows, вот мои результаты для Windows 10 20H2.

Обычный запуск, система без антивируса:

  • ~7 мс на ShellExecuteEx - высокоуровневый API оболочки предназначенный скорее для открытия файлов в приложении по умолчанию, чем создания процессов.

  • ~1.1 мс на CreateProcess - основной документированный метод.

  • ~0.8 мс на NtCreateUserProcess - нижележащий системный вызов.

  • ~0.9 мс на NtCreateProcessEx + NtCreateThreadEx - альтернативный (более старый) способ, требующий больше действию вручную.

Создание клона процесса (местный аналог fork), система без антивируса:

  • ~1.5 мс используя NtCreateUserProcess.

  • ~0.9 мс используя NtCreateProcessEx + NtCreateThreadEx.

В то же время, создание нового потока занимает всего ~0.03 мс, что намного быстрее.

Упомянутый выше NtCreateProcessEx имеет интересную особенность - он создаёт только объект процесса, без изначального потока. Поскольку драйвера, которые подписываются на синхронные уведомления о создании процессов (посредством PsSetCreateProcessNotifyRoutine) получают их только при создании первичного потока в целевом процессе, мы получаем возможность измерить "чистое" время создания процессов, без вклада от сторонних драйверов. В этом случае NtCreateProcessEx тратит ~0.6 мс.

Наличие в системе дополнительных драйверов (вроде антивируса или EDR), может существенно повлиять на результаты. Так, включённый Windows Defender даёт примерно +90% замедления, заставляя CreateProcess тратить по ~2.1 мс. Но это ещё ничего, если взять какой-нибудь откровенно плохой антивирус, например, китайский 360 Security, то можно замедлить CreateProcess в 200 (!) раз, до ~240 мс.

Я еще в 2005 году читал, что программы с нативным AOT-кодом скоро отомрут, из-за того что JIT может компилировать под текущий процессор, и будет работать быстрее, а все будут писать на Nemerle... Эх, ну и где оно? А MS добавила AOT-компиляцию в сишарп.

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

Помню юзал Gentoo и все собирал с march=native. Не особо это и помогало

Вы пишите, что вы мейнтейнер Mozilla или как-то связаны с ними.


Может тогда пните их под зад, чтобы они начали поддерживать zstandart. A то как другое ПО будет использовать новомодные библиотеки, если Accept-Encoding: gzip, deflate?


Спасибо ;-)

И в firefox и в chromium вижу одинаковое
Accept-Encoding: gzip, deflate, br

Вроде как brotli (которое тут под псевдонимом br) достаточно прогрессивное сжатие.

да, brotli жмёт на 20-30% лучше, но на максимальном сжатии httpd буквально вис и сайт раз в 5 медленнее открывался, как раз то о чём писал автор. по-моему проблемы были с большими js файлами. по факту нужно проводить очень скрупулёзное тестирование на предмет реальной выгоды и параметров. и интересно как модуль апача скомпилирован, с каким набором инструкций :)

ну а сам brotli кстати все современные браузеры поддерживают, но им никто не пользуется (я вот о нём случайно узнал), собственно и об этом автор писал :)

это же перевод

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории