last day (15 days later) » 

6:59 AM
3
A: Как ускорить чтение директорий файловой системы?

StackМожно ускорить почти в два раза, если использовать WinAPI. // Microsoft (R) Roslyn C# Compiler version 1.1.0.51204 using System.Runtime.InteropServices; [DllImport("kernel32.dll")] static extern int GetLastError(); [DllImport("kernel32.dll")] static extern bool FindClose(IntPtr handle); class S...

 
А за счёт чего происходит ускорение?
[DllImport("kernel32.dll")] static extern int GetLastError(); - вместо этого надо использовать Marshal.GetLastWin32Error.
 
"А за счёт чего происходит ускорение?" -- тут прямые вызовы WinAPI. а в Directory.EnumerateFileSystemEntries очень много всего. посмотрите исходник
@Qwertiy "надо использовать Marshal.GetLastWin32Error" -- выдает неправильный код если Tasks.Task и много подзадач (т.е. TaskCreationOptions.AttachedToParent). примера под рукой нет, и возможно это был какой-то побочный эффект. как-нибудь проверю и напишу.
 
А по ссылке утверждается, что наоборот winapi-вариант может дать неправильный код, если там сборщик мусора запустится.
 
@Qwertiy "winapi-вариант может дать неправильный код," -- от версии CLR зависит, как там сказано: "If you compile this with .NET2, it will produce "2 / 0"; if you switch to .NET 4, it will output "2 / 2"". т.е. надо проверять.
 
Нуууу... Вы заменили высокоуровневый код на низкоуровневый, в котором наверняка не учитываете всяческие тонкости наподобие тех, которые учтены здесь. (Например, вы вроде бы не закрываете хендл, а надеетесь на сборщик мусора.) Вы уверены, что рекомендовать такое — хороший совет?
 
6:59 AM
@VladD "вы вроде бы не закрываете хендл, а надеетесь на сборщик мусора." -- где не закрываю? вы смотрели на SafeHandle и на using?
 
@Stack: А, точно, закрываете. Окей, вы не обрабатываете ошибки кроме no more files и file not found. (referencesource.microsoft.com/#mscorlib/system/io/…) Ну и код в BCL идёт тоже через те же нативные функции, но делает больше — вы и вправду считаете, что всё, что они там делают, лишнее, и это можно безболезненно выкинуть?
 
@VladD "точно, закрываете. Окей, вы не обрабатываете ошибки" -- напомнили "а на цепочку закрыл?)". это пример-основа. кому надо тот может дописать то, что требуется. "вы и вправду считаете, что всё, что они там делают, лишнее" -- про все сказать не могу. не смотрел. главное что код работает быстрее чем .Enumerate* и делает то, что мне надо. если найдете баг, напишите.
 
@VladD, а что не так с ошибками? Там же throw new System.ComponentModel.Win32Exception(e);
 
@Qwertiy: Это для FindNext, а не для FindFirst.
 
@VladD "Это для FindNext, а не для FindFirst" -- а для FindFrst.. тоже есть, см. if (h.IsInvalid) throw ...
 
6:59 AM
@Stack: Возможно, я придираюсь. Но просто если вы уж пишете код в замену стандартному, то он должен бы быть реально лучше. А вы измерили лишь скорость обхода, а не что-то другое. Я не хочу копать ошибки, это было бы не очень этично с моей стороны.
 
@VladD, валидный хендл одновременно с ошибкой? Это возможно?
 
@Qwertiy: Майкрософтовский код в аналогичной ситуации почему-то проверяет: referencesource.microsoft.com/#mscorlib/system/io/…
 
class File { public string Path; public string Name; } - а как изменится скорость, если сделать struct?
 
@Qwertiy: Скоростью работы с памятью по сравнению со скоростью работы с файловой системой можно пренебречь, по идее.
 
@VladD "валидный хендл одновременно с ошибкой?" -- если при вызове FindFirstFileEx возникнет ошибка, то h.IsInvalid = true. "Я не хочу копать ошибки, это было бы не очень этично с моей стороны." -- я не обижусь если найдете. мне самому инетересно в каких ситуациях этот код сломается.
 
6:59 AM
@Stack: Если каталог пустой (или файлов по маске не найдено?), то FindFirstFileEx по идее возвращает INVALID_HANDLE_VALUE, у вас это, кажется, приведёт к исключению.
 
@VladD, в целом да, но мы сборщик мусора загрузим этими классами.
 
@VladD "Если каталог пустой (или файлов по маске не найдено?) ... у вас это, кажется, приведёт к исключению" -- да. потому что если garbage in, то fail-fast.
 
@VladD, кстати, у этого кода ещё полезный момент есть - он должен уметь обрабатывать пути длиннее 256 символов за счёт \\?\.
C чего бы пустой каталог был ошибкой?
 
@Qwertiy: Ну да, а так заставим процессор копировать данные каждый раз? Что так, что так не здорово. Ну и снова-таки не думаю, что собрать 10000 классов медленнее, чем открыть один файл, записать в него данные и закрыть.
@Stack: Э, не, пустой каталог не должен быть ошибкой. Чем количество файлов 0 хуже, чем 5?
 
@VladD, копировать 16 байт? Вместо выделения-очистки памяти и копирования 8 байт?
 
6:59 AM
@VladD "пустой каталог не должен быть ошибкой" -- если нужны данные по папке, то не надо ставить суффикс \* и не будет ошибки. а если ставите \* то значит намеревались получить содержимое, но его нет - значит fail fast.
 
По поводу обработки длинных имён — я читал у разработчиков BCL, почему они не обрабатывают длинные имена. Дело в том, что это по сути бесполезно — вы ведь передадите имя файла на дальнейшую обработку другим системным функциям, а у них снова ограничение в 260 символов. Это как-бы неправильно, ограничение должно быть общесистемным, и исправляться на общесистемном уровне, а не костылём.
@Qwertiy: Ну, завтра этот класс разрастётся, и что тогда? Может вы и правы, но в любом случае мне это кажется копеечной экономией по сравнению с доступом к диску.
 
Нет. Если мне точно нужен непустой результат, коллекцию на пустоту я и сам могу проверить. Должна быть пустая коллекция, а не ошибка.
 
Я здесь согласен с @Qwertiy — если я, например, хочу удалить или там забекапить все файлы в 50 заранее заданных каталогах, то обработать 0 файлов в каком-то из каталогов нормально.
 
@VladD, поэтому я и написал в виде вопроса. Но когда итератор списка делали структурой, определили, что выигрыш существенный.
 
@Qwertiy: Это потому, что он должен использоваться во всех случаях, даже когда нужна экстремальная скорость, а у нас тут как бы конкретный, в котором время обработки подавляет всё остальное. То есть с технической стороны вы, скорее всего, правы, но мне это кажется слишком мелкой монетой, чтобы за ней нагибаться.
 
6:59 AM
Хотя нет, с 260 там всё равно что-то не то будет, наверное: [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
 
@VladD "Должна быть пустая коллекция, а не ошибка" -- нет ошбки. видно, код не запускали. так вот в любой папке есть записи . и ... и если .. отфильтровывается, то . возвращается и для пустой папки при запросе с суффиксом \*
 
@VladD, если есть на чём замерить (а в 2 раза на чём-то ведь замерялось), то я бы хотел и со структорой результат узнать.
 
Хм. А вот это уже серьёзнее. В худшем случае будет затёртая память со всеми своими прелестями.
 
Эм.. А если маской будет не *, а какой-нибудь *.txt?
 
@Stack: Нет, не тестировал, каюсь. А если указать нетривиальную маску? "*.txt"?
@Qwertiy: Опередили :)
 
6:59 AM
@Qwertiy "с 260 там всё равно что-то не то будет, наверное:" -- эти 260 это только на имя файла, без пути. путь передается только на входе в FindFirstFileEx, а на выходе только имена
@Qwertiy "если маской будет не *, а какой-нибудь *.txt" -- в коде в комменте перечислены примеры. один из них как-раз "*.png"
@VladD "В худшем случае будет затёртая память со всеми своими прелестями" -- данные передаются из WinAPI наружу в DATA. так что все ок. а если нет, то значит это баг в WinAPI. но пока никто жаловался.
 
@Stack: Ага, оно так и определено: TCHAR cFileName[MAX_PATH];. Тогда проблема будет только если имя файла длиннее 260 символов (и наверное в виде ошибки, а не затёртой памяти, да).
 
@Qwertiy "а в 2 раза на чём-то ведь замерялось" -- с помощью Stopwatch смотрел сколько ElapsedMilliseconds при сканировании папки с 10 тыс. файлов в EnumerateFileSystemEntries и сколько в ReadPath. запускал несколько раз.
 
@VladD, имя не может быть длиннее - тут уже ограничение файловой системы. Так что всё хорошо.
@Stack, а если на структуре замерить?
 
@VladD "проблема будет только если имя файла длиннее 260 символов" -- такого имени (только имени; без пути) не будет, т.к. его просто не получится создать. а вот длина всего пути до файла может быть равна 32767
 
@Stack: А примонтированный сетевой диск? А если создавать через "\\?\"? Вот свидетельство того, что таки бывают: superuser.com/a/78458/56347
 
6:59 AM
в коде в комменте перечислены примеры. один из них как-раз ".png"* - имелось в виду, что будет в случае пустой папки при такой маске? ведь . и .. ей уже не соответствуют.
 
@Qwertiy "а если на структуре замерить?" -- я что-то пропустил наверное. какой структуре? DATA это и так struct
 
@VladD, там вроде про суммарную длину пути, а не про имя файла. Насколько я помню, в ntfs имя не может быть длиннее. Ну если уж там окажется специфическая файловая система, со специфическими именами, то, думаю, система сама что-то сделает. Там же это как инлайновый массив в структуре идёт.
class File { public string Path; public string Name; } - вот тут вместо class сделать struct.
 
@VladD "А примонтированный сетевой диск?" -- если FindFirstFileEx может с ним работать, то все ок.
 
@Qwertiy: Ну да, "C:\оченьдлиннноеимя..............txt" вроде не противоречит описанию. В документации на CreateFile ничего про конкретно имя не нашёл.
@Stack: Может, конечно. Вопрос в том, что там может быть файловая система с именами длиннее чем 260 символов. И FindFirstFileEx придётся вернуть какую-нибудь ошибку.
 
@VladD, ну вернёт он ошибку, а обрабатывающий код сделает из неё исключение. Вроде нет проблемы?
 
6:59 AM
@Qwertiy " вместо class сделать struct." -- попробую. но получится что в Scan будет создаваться структура, и yield return наружу. при этом будет копирование структуры, а не передача ссылки, не так ли?
 
Да, так. Но в структуре только две ссылки, т. е. копируемый размер увеличивается незначительно, а необходимость выделения памяти и сборки мусора отпадает.
 
@VladD "может быть файловая система с именами длиннее чем 260 символов. И FindFirstFileEx придётся вернуть какую-нибудь ошибку" -- на входе ReadPath может быть строка.length = 32767; вы видели такой путь?
 
Кстати, я тут подумал насчёт ускорения в 2 раза - ты не собираешь конкатенацию строк чтобы получить полный путь, в отличие от стандартной функции - на этом можно сэкономить что-то. Потому что я не думаю, что прямое обращение к api как-то экономит на взаимодействии с файловой системой - скорее окружающий код.
 
@Stack: Вы имеете в виду, видел ли я в реальной жизни такие длинные пути, что имя файла больше 260 символов?
 
@VladD "длинные пути, что имя файла больше 260 символов" -- да, полный путь к файлу (т.е. весь путь и имя) чтобы был под 30 тыс. символов в длину.
@Qwertiy " не собираешь конкатенацию строк чтобы получить полный путь" -- и не вызываю Path.GetFullPath, который вызывает ужасный NormalizePath(path, true)
@Qwertiy "вместо class сделать struct." -- проверил. для 9509 файлов. ElapsedMilliseconds, для struct File 34;30;28;27;28;39;27;28;27;27; для class File 30;27;26;27;28;29;28;32;29;27;
 
6:59 AM
@Stack: Таких длинных нет. Но имя файла (не полный путь, а именно имя) длиннее 260 символов — да.
 
Как-то маловато. Я думал, ты там на несколько минут поиск запустил. А при 10К файлов даже не факт, что сборщик мусора хоть раз сработал.
 
@Vlad @Qwertiy @Stack ping
@Vlad @Qwertiy @Stack если что, у нас есть специальный чат про дотнет: chat.stackexchange.com/rooms/26962/c-net--
 
7:28 AM
нам вроде и так неплохо :)
 
7:38 AM
@Qwertiy "не факт, что сборщик мусора хоть раз сработал." -- как выяснить что было сборка мусора? какое-то событие от GC можно получить
 
не знаю
просто я думал, ты ему что-то крупное скормил, где время работы будет минуты
тогда бы по-любому собирал
кстати, у меня когда-то была папка с кучей файлов непосредственно в ней
GetAllFiles выполнялось значительное время
может секунд 15-30
// кстати, а что там с вопросом про поворот картинки?
 
7:51 AM
@Qwertiy "что там с вопросом про поворот картинки?" -- надо проверить как работает. но руки не доходят. на выходных проверю.
 
 
3 hours later…
10:41 AM
@NickVolynkin, ты что-то лишнее в чат перенёс:
4 hours ago, by Qwertiy
[DllImport("kernel32.dll")] static extern int GetLastError(); - вместо этого надо использовать Marshal.GetLastWin32Error.
4 hours ago, by Stack
@Qwertiy "надо использовать Marshal.GetLastWin32Error" -- выдает неправильный код если Tasks.Task и много подзадач (т.е. TaskCreationOptions.AttachedToParent). примера под рукой нет, и возможно это был какой-то побочный эффект. как-нибудь проверю и напишу.
4 hours ago, by Qwertiy
@VladD, кстати, у этого кода ещё полезный момент есть - он должен уметь обрабатывать пути длиннее 256 символов за счёт \\?\.
пожалуй, это стоило оставить
кстати, а чего при переносе в чат время написания не сохраняется?
 
10:58 AM
@Qwertiy похоже, не сохраняется
@Qwertiy а может это из комментариев перенести в сам ответ?
 
ответ не мой же
к тому же, по второй цитате не ясно, что там на самом деле
 
@Stack предлагаю инфу из комментариев по возможности перенести в ответ
@Qwertiy ок, эти комментарии восстановил
 
 
1 hour later…
12:13 PM
Я, честно говоря, не согласен с тотальной зачисткой комментариев. Особенно если комментарии обсуждают границы применимости ответа.
Это не флуд, это техническое обсуждение.
Без комментариев читатели ответа не увидят возможную проблему. Я лично перед тем, как использовать какой-либо ответ с SO, всегда внимательно читаю обсуждение. Мне кажется, оно очень часто не менее ценно, чем сам ответ.
 
12:52 PM
// CC: @NickVolynkin
 
1:08 PM
@Vlad тут рекордное число комментариев — 69. Из них удалены 65.
@Vlad а можно ли всё-таки дополнить ответ информацией о его применимости?
@Vlad а то получается, что каждый читатель обязан прочитать всё обсуждение.
 
@NickVolynkin Обычно на en.SO автор ответа и комментаторы не всегда во всём согласны, поэтому такое вроде бы не работает. Хотя конечно отделять техническую сторону от флуда нужно.
 
@Vlad да, тут вы правы
 
Отделить интересное для будущих читателей от ушедшей вразнос дискуссии не так уж и просто, конечно. :-(
 
@Vlad @Stack @Qwertiy я полностью восстановил комментарии под ответом. Пожалуйста, просмотрите их и удалите всё, что уже устарело. В идеале хотелось бы видеть ответ и — если что-то противоречит мнению автора — пару информативных комментариев.
Не заставлять же каждого читателя читать все 69 комментариев. Тем более, чаще в них всё-таки флуд и устаревшая информация бывает.
 
@Stack: Предлагаю внести конструктивную часть дискуссии в ответ, а остальные комментарии снова снести.
Читать >50 комментариев за крупицей знаний, конечно, глупо.
@NickVolynkin Если автор ответа сотрудничает, то проблем по идее не должно быть, ну и хорошее обсуждение позволяет улучшить почти любой ответ (даже хороший!).
 
1:38 PM
@NickVolynkin " я полностью восстановил комментарии под ответом." -- лучше все перенести/вернуть в чат. позже я части из комментов добавлю в ответ.
 
@Stack пожалуй, я не буду в третий раз протыкивать все комментарии ) Они видны, авторы могут их поудалять самостоятельно.
 
@Vlad "предлагаю внести конструктивную часть дискуссии в ответ, а остальные комментарии снова снести" -- удалять не надо. можно просто перенести в чат. а я позже дополню ответ.
 
@Stack Или так. То есть по идее в этом чате они уже есть, так что надо будет дать ссылку сюда.
 
@Stack да, чат содержит все комментарии. Только если что-то было удалено до перенесения в чат, тогда оно сюда не попало.
 
2:38 PM
@Stack из чата их никто и не удалял - вот же чат
@NickVolynkin а почему чат больше не привязан к ответу?
@Vlad @Stack @Qwertiy @NickVolynkin я свои комментарии почистил
8 hours ago, by Qwertiy
в коде в комменте перечислены примеры. один из них как-раз ".png"* - имелось в виду, что будет в случае пустой папки при такой маске? ведь . и .. ей уже не соответствуют.
@Stack, кажется, этот вопрос без ответа остался?
 
3:31 PM
@Qwertiy как не привязан?
 
С этой стороны привязан. Просто в комментариях нужно оставить ссылку на чат.
Удалил почти все свои комментарии (кроме тех, которые обсуждают исключение, если ни один файл не подпадает под маску). В удалённом ничего кроме «наводящих вопросов» по сути не было.
 
@Qwertiy @Vlad а вот
Комментарии не предназначены для расширенной дискуссии; разговор перемещен в чат. — Nick Volynkin ♦ 9 hours ago
 
@NickVolynkin сорри, надо было load older messages нажать
я имел в виду, что в начале чата нет ссылки на сообщение
 
плюсаните его, пускай будет виден
 
но просто не промотал)
 
3:40 PM
@Qwertiy аа, ну да. Ещё справа есть, в описании чата.
 
ой))
 
внезапно )
 
а я всё время верхним пользовался
ага)))
 
а я вот недавно обнаружил, что в создаваемом таким образом чате всем участникам выдается право на запись явным образом
и это позволяет чатиться тем, у кого репутация меньше необходимой для чатов
 
а как же на мете была тема, что не выдаётся?
или системный чат отличается от переноса модератором?
тогда чего ж они его не допилят, раз функциональность уже есть?
 
3:42 PM
@Qwertiy да вот неизвестно
@Qwertiy проверьте в следующий раз, когда система предложит перенести комменты в чат
я думаю, это должно для всех одинаково работать
 
а что-то я не уверен, что она предлагала
 
ну она предлагает иногда
 
да и надо где-то добыть пользователя с недостаточной репутацией для экспериментов
или права на чат где-то смотреть можно?
в чём разница вообще?
 
@Qwertiy нее
 
кстати, пока вспомнил - вроде где-то встречал упоминание galery rooms - что это? или на мете спросить?
 
3:44 PM
всем предлагает
@Qwertiy когда не все могут писать
 
@NickVolynkin предлагает всем, но как узнать, выданы права явно или нет?
 
http://chat.stackexchange.com/rooms/info/34277/discussion-on-answer-by-stack------?t‌​ab=access&highlight=152111
пройдите по ссылке
это страница настройки прав
доступна в любом чате, где вы владелец
если создавать чат по предложению системы, когда много комментариев — тот кто нажал, тот и будет владельцем
а у остальных write access
 

  last day (15 days later) »