Есть регулярная задачка "обойти структуру папок и сгенерить аналогичную структру, но с другими данными".
При этом
а) некоторые папки надо пропустить.
б) имена папок/файлов в папке назначения могут различаться и об этом надо знать вызывающему коду.
Как оно выглядит сейчас — см код и пример ниже (убраны проверки, на практике лучше не использовать)
Реальный код — UniversIS.IO.FileSystemHelper.VisitAll() — брать здесь
Страшно? Так вот, этот код с марта
1) существовал как один метод
2) был вынесен в библиотеку компонентов
3) параметры были объединены в структуру
4) был вынесен в отдельный класс, унаследованный от Component (раздулся втрое и стал вообще неподдерживаемым)
5) приведён к текущему виду и отмечен как // DONTTOUCH. Кстати, такие комментарии в Task List window — великое дело.
Придумаете как это написать красивее?
Злостный оффтоп
Сравнил время копирования (см код ещё ниже)
-кодом со всеми проверками,
-Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(src, dest2),
-Far'a (1,71a3, системные функции копирования)
-проводника win7.
Внимание вопрос:
Из какого места у товарищей растут руки, если мой абсолютно не оптимизированный код на паре тысяч мелких файлов выигрывает от %10 у кода из библиотеки VB и до 150% (30 сек vs 2 мин) у фара???
Не, понятно что без полноценной проверки на разных машинах/с разными наборами никаких выводов быть не должно. Но вопрос "чем оно там занимается" по-прежнему в силе.
Здравствуйте, Sinix, Вы писали:
S>Внимание вопрос: S>Из какого места у товарищей растут руки, если мой абсолютно не оптимизированный код на паре тысяч мелких файлов выигрывает от %10 у кода из библиотеки VB и до 150% (30 сек vs 2 мин) у фара???
Боюсь, Ваш тест не вполне корректен. После каждого теста следовало бы перезагружать машину, дабы гарантированно сбросить файловый кэш системы.
S>А как бы вы написали?
Что-нибудь вроде этого
public interface ICopyControl
{
bool CanCopyFile(string source, string dest);
bool CanCopyDir(string source, string dest);
}
class Program
{
public static EventWaitHandle completeEvent =
new EventWaitHandle(false, EventResetMode.AutoReset);
public static int ItemsCount;
public static void Copy(
string sourceDir, string destDir, ICopyControl copyControl)
{
try
{
if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
var dirs = Directory.GetDirectories(sourceDir);
var dest = destDir + "\\";
for (int i = 0; i < dirs.Length; )
{
var src = dirs[i++];
var dst = dest + src.Substring(sourceDir.Length);
if (copyControl == null || copyControl.CanCopyDir(src, dst))
{
Interlocked.Increment(ref ItemsCount);
ThreadPool.QueueUserWorkItem((o) =>
{
Copy(src, dst, copyControl);
});
}
}
var files = Directory.GetFiles(sourceDir);
for (int i = 0; i < files.Length; )
{
var srcF = files[i++];
var dstF = dest + srcF.Substring(sourceDir.Length);
if (copyControl == null || copyControl.CanCopyFile(srcF, dstF))
{
File.Copy(srcF, dstF);
}
}
}
finally
{
if (Interlocked.Decrement(ref ItemsCount) == 0) completeEvent.Set();
}
}
public static void TestCopy(string sourceDir, string destDir)
{
if (!Directory.Exists(sourceDir)) return;
completeEvent.Reset();
Thread.VolatileWrite(ref ItemsCount, 1);
Copy(sourceDir, destDir, null);
completeEvent.WaitOne();
}
static void Main(string[] args)
{
TestCopy(@"K:\A1", @"K:\B1");
Console.WriteLine("Complete");
Console.ReadKey();
}
}
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, Sinix, Вы писали:
S>>Внимание вопрос: S>>Из какого места у товарищей растут руки, если мой абсолютно не оптимизированный код на паре тысяч мелких файлов выигрывает от %10 у кода из библиотеки VB и до 150% (30 сек vs 2 мин) у фара???
JR>Боюсь, Ваш тест не вполне корректен. После каждого теста следовало бы перезагружать машину, дабы гарантированно сбросить файловый кэш системы.
S>>А как бы вы написали?
JR>Что-нибудь вроде этого
JR>
JR> public interface ICopyControl
JR> {
JR> bool CanCopyFile(string source, string dest);
JR> bool CanCopyDir(string source, string dest);
JR> }
JR> class Program
JR> {
JR> public static EventWaitHandle completeEvent =
JR> new EventWaitHandle(false, EventResetMode.AutoReset);
JR> public static int ItemsCount;
JR> public static void Copy(
JR> string sourceDir, string destDir, ICopyControl copyControl)
JR> {
JR> try
JR> {
JR> if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
JR> var dirs = Directory.GetDirectories(sourceDir);
JR> var dest = destDir + "\\";
JR> for (int i = 0; i < dirs.Length; )
JR> {
JR> var src = dirs[i++];
JR> var dst = dest + src.Substring(sourceDir.Length);
JR> if (copyControl == null || copyControl.CanCopyDir(src, dst))
JR> {
JR> Interlocked.Increment(ref ItemsCount);
JR> ThreadPool.QueueUserWorkItem((o) =>
JR> {
JR> Copy(src, dst, copyControl);
JR> });
JR> }
JR> }
JR> var files = Directory.GetFiles(sourceDir);
JR> for (int i = 0; i < files.Length; )
JR> {
JR> var srcF = files[i++];
JR> var dstF = dest + srcF.Substring(sourceDir.Length);
JR> if (copyControl == null || copyControl.CanCopyFile(srcF, dstF))
JR> {
JR> File.Copy(srcF, dstF);
JR> }
JR> }
JR> }
JR> finally
JR> {
JR> if (Interlocked.Decrement(ref ItemsCount) == 0) completeEvent.Set();
JR> }
JR> }
JR> public static void TestCopy(string sourceDir, string destDir)
JR> {
JR> if (!Directory.Exists(sourceDir)) return;
JR> completeEvent.Reset();
JR> Thread.VolatileWrite(ref ItemsCount, 1);
JR> Copy(sourceDir, destDir, null);
JR> completeEvent.WaitOne();
JR> }
JR> static void Main(string[] args)
JR> {
JR> TestCopy(@"K:\A1", @"K:\B1");
JR> Console.WriteLine("Complete");
JR> Console.ReadKey();
JR> }
JR> }
JR>
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, Jolly Roger, Вы писали:
Это из какого смысла Я имею в виду, что уже не в первый раз вижу копирование чьего-то поста без комментариев, может за этим скрывается какой-то особый посыл, а я просто не в курсе этой здешней традиции? А хотелось бы
Коротко:
1) Спасибо
2) Согласен, обобщать неправомерно. Тем не менее, запускал несколько раз в различном порядке
3) Многопоточность — не самая лучшая идея для копирования, особенно для больших файлов.
Специальный дохлокомп для тестирования (cel 2.4/512 mb/win7 забитый чем только можно):
4) Async file IO может дать ещё чуть чуть выигрыша, а может и не дать.
5) Пример с копированием был, как ни странно, примером. Речь шла об обходе папок (с возможностью пропуска отдельных веток), формированием destination path и скармливании обработчикам для директорий/файлов.
Здравствуйте, Sinix, Вы писали:
S>3) Многопоточность — не самая лучшая идея для копирования, особенно для больших файлов.
Ну так Вы же на целероне запускали Согласитесь, сейчас имеет прямой смысл ориентироваться на более продвинутые архитектуры. Кроме многоядерности сейчас уже не редкость и RAID-ы даже на десктопах.
Асинхронность может дать прирост, но с ней возни много для получения реального выигрыша, а я этот пример прямо сейчас набросал и не тестировал, только один раз запустил, чтобы убедиться в работоспособности Поначалу и хотел организовать копирование асинхронно, но потом поленился
Всё равно это мелочи, за реальным ускорением надо в натив идти, в недокументированное API, некоторые даже в ядро лезут, и надо признать — там реальные приросты получают, но я не сторонник столь глубокого бурения для прикладных задач
Здравствуйте, Sinix, Вы писали:
S>Придумаете как это написать красивее?
"Красиво" — понятие растяжимое. Лично я постарался бы вынести функциоал обхода дерева:
public static class TreeTraverser
{
public static void Traverse<T>(T root, Func<T, IEnumerable<T>> getChildren, Action<T> visitor)
{
visitor(root);
foreach (T child in getChildren(root) ?? Enumerable.Empty<T>())
{
Traverse(child, getChildren, visitor);
}
}
}
Тогда функция копирования структуры папок выглядела бы вот так:
var intialState = new
{
dir = true,
src = "c:\\Download",
dst = "c:\\Tmp\\Test"
};
TreeTraverser.Traverse(
intialState,
state =>
{
if (!state.dir)
return null;
var dirs = from d in Directory.GetDirectories(state.src)
let folderName = Path.GetFileName(d)
select new
{
dir = true,
src = d,
dst = Path.Combine(state.dst, folderName)
};
var files = from d in Directory.GetFiles(state.src)
let folderName = Path.GetFileName(d)
select new
{
dir = false,
src = d,
dst = Path.Combine(state.dst, folderName)
};
return dirs.Concat(files);
},
state =>
{
if (state.dir && !Directory.Exists(state.dst))
Directory.CreateDirectory(state.dst);
}
);
Здравствуйте, Jolly Roger, Вы писали:
S>>3) Многопоточность — не самая лучшая идея для копирования, особенно для больших файлов.
JR>Ну так Вы же на целероне запускали Согласитесь, сейчас имеет прямой смысл ориентироваться на более продвинутые архитектуры. Кроме многоядерности сейчас уже не редкость и RAID-ы даже на десктопах.
Мне кажется, вы не до конца поняли суть комментария. Наличие хоть сотни ядер у процессора не сильно поможет в задаче ускорения копирования файлов. Более того, использование многопоточности в данном случае может привести к багам. ThreadPool по-моему никак не гарантирует упорядоченности выполнения задач, и ледовательно вполне можно столкнуться с ситуацией, когда код копирования файла запускается раньше кода создания каталога для этого файла.
JR>Асинхронность может дать прирост, но с ней возни много для получения реального выигрыша, а я этот пример прямо сейчас набросал и не тестировал, только один раз запустил, чтобы убедиться в работоспособности Поначалу и хотел организовать копирование асинхронно, но потом поленился
Асинхронность может дать заметный выигрыш если у вас код обращается к разным независимым ресурсам. В этом примере ресурс один — контроллер диска. Приросту взяться особо не откуда.
Здравствуйте, Lloyd, Вы писали:
L>Мне кажется, вы не до конца поняли суть комментария. Наличие хоть сотни ядер у процессора не сильно поможет в задаче ускорения копирования файлов. Более того, использование многопоточности в данном случае может привести к багам. ThreadPool по-моему никак не гарантирует упорядоченности выполнения задач, и ледовательно вполне можно столкнуться с ситуацией, когда код копирования файла запускается раньше кода создания каталога для этого файла.
JR>>Асинхронность может дать прирост, но с ней возни много для получения реального выигрыша, а я этот пример прямо сейчас набросал и не тестировал, только один раз запустил, чтобы убедиться в работоспособности Поначалу и хотел организовать копирование асинхронно, но потом поленился
L>Асинхронность может дать заметный выигрыш если у вас код обращается к разным независимым ресурсам. В этом примере ресурс один — контроллер диска. Приросту взяться особо не откуда.
ThreadPool не гарантирует, но здесь директория создаётся до того, как в неё начинают копироваться файлы, поэтому переупорядочивание запросов пулом повлиять никак не может. По поводу одного ресурса и влияния асинхронности, то вопрос не столь прост. Контроллер может быть и не один, если копируем с одного физического диска на другой. Это может быть контроллер RAID-массива, а они бывают разные. Современные SATA контроллеры способны перупорядочивать выданные им запросы с целью оптимизации обращений к дискам, но для этого они должны иметь сразу несколько запросов, чего не добиться в однопоточной работающей синхронно программе. Во всяком случае, в стандарт SATA такая возможность заложена. Копирование — по большей части физическая работа с диском, но не только, кое-что приходится делать и процессору. Но при синхронной работе процессор будет просто стоять, пока выполняется обращение к диску. Тем более это актуально, когда идёт не только копирование, но и промежуточная переработка данных. Имея два потока на ядро мы уже в определённой степени можем совместить эти операции, асинхрон будет ещё более эффективен.
То есть если стоит задача получить максимум скорости, то надо собирать информацию о системе и выбирать алгоритм, исходя из имеющихся ресурсов, а это геморрно, и результат не столь впечатляющ, чтобы стоило на это тратиться.
Здравствуйте, Jolly Roger, Вы писали:
L>>Асинхронность может дать заметный выигрыш если у вас код обращается к разным независимым ресурсам. В этом примере ресурс один — контроллер диска. Приросту взяться особо не откуда.
JR>ThreadPool не гарантирует, но здесь директория создаётся до того, как в неё начинают копироваться файлы, поэтому переупорядочивание запросов пулом повлиять никак не может.
+1. Это я проглядел.
JR>По поводу одного ресурса и влияния асинхронности, то вопрос не столь прост. Контроллер может быть и не один, если копируем с одного физического диска на другой. Это может быть контроллер RAID-массива, а они бывают разные. Современные SATA контроллеры способны перупорядочивать выданные им запросы с целью оптимизации обращений к дискам, но для этого они должны иметь сразу несколько запросов, чего не добиться в однопоточной работающей синхронно программе. Во всяком случае, в стандарт SATA такая возможность заложена.
Для нового файла? Если честно, не могу представить зачем может понадобиться такое переупорядочивание для "нового" файла.
JR>Копирование — по большей части физическая работа с диском, но не только, кое-что приходится делать и процессору. Но при синхронной работе процессор будет просто стоять, пока выполняется обращение к диску. Тем более это актуально, когда идёт не только копирование, но и промежуточная переработка данных. Имея два потока на ядро мы уже в определённой степени можем совместить эти операции, асинхрон будет ещё более эффективен.
Здравствуйте, Jolly Roger, Вы писали:
JR>Ну так Вы же на целероне запускали Согласитесь, сейчас имеет прямой смысл ориентироваться на более продвинутые архитектуры. Кроме многоядерности сейчас уже не редкость и RAID-ы даже на десктопах.
1) вам уже ответил Lloyd.
JR>Всё равно это мелочи, за реальным ускорением надо в натив идти, в недокументированное API, некоторые даже в ядро лезут, и надо признать — там реальные приросты получают, но я не сторонник столь глубокого бурения для прикладных задач
Речь шла совсем не об этом — вопрос был в том, почему код, для производительности и не заточенный, работает слегка шустрее специализированных программ?
Здравствуйте, Lloyd, Вы писали:
L>"Красиво" — понятие растяжимое. Лично я постарался бы вынести функциоал обхода дерева:
Мдя... получается то же самое — callbacks в VisitAll не должны заниматься получением папок/файлов — это должно быть сделано за них. Ваш вариант упрощает сам метод VisitAll — грабли с передачей параметров в делегаты остаётся. Либо оставить как есть, либо всё-таки сгруппировать параметры в что-то типа VisitState. По второму кругу?
P.S. А не намекают ли такие грабли на необходимость пересмотреть подход? Не хотелось бы — в остальном всё прямо замечательно.
Здравствуйте, Sinix, Вы писали:
S>Сравнил время копирования (см код ещё ниже) S>-кодом со всеми проверками, S>-Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(src, dest2), S>-Far'a (1,71a3, системные функции копирования) S>-проводника win7.
S>Внимание вопрос: S>Из какого места у товарищей растут руки, если мой абсолютно не оптимизированный код на паре тысяч мелких файлов выигрывает от %10 у кода из библиотеки VB и до 150% (30 сек vs 2 мин) у фара???
Здравствуйте, Uzzy, Вы писали:
U>Ваш вариант включает обновление ГУИ?
Похоже, проблема была не в нём — даже на полудохлой машинке проц не доходил до 100, узкое место — очередь диска (0,7..5,0). Уже предположили что проблема была в неэффективном коде фара'а — тот использует net file api.
Не спорю, может консольный фар интенсивно свопится, но при более-менее прямых руках влияние UI на собственно копирование не даёт четырёхкратного замедления.
Здравствуйте, Sinix, Вы писали:
S>Ваш вариант упрощает сам метод VisitAll — грабли с передачей параметров в делегаты остаётся.
А зачем в них передавать параметры? Задача какая?
S>Либо оставить как есть, либо всё-таки сгруппировать параметры в что-то типа VisitState. По второму кругу?
Здравствуйте, Lloyd, Вы писали:
L>А зачем в них передавать параметры? Задача какая?
Попробую описать
1) на диске валяется директория с кучей поддиректорий и папок, организованным хитрым образом.
2) Из исходного набора данных надо сделать похожую, при этом:
2.1) Примитивную логику типа обхода директорий/генерации конечных имён должен выполнять helper.
2.2) логика обработки может изменять имена части конечных папок (допустим было folder стало folder [2009-12-21])
2.2) логика обработки может пропускать некоторые папки (вместе с подпапками), при этом логика пропуска тесно связана с логикой генерации имён — разносить её нежелательно.
3) если изменился корневой путь к директории назначения, то об этом должен узнавать вызывающий код.
4) на практике оказалось, что вызывающий код не унаследован от общего класса — либо инкапсулиуем хелпер, либо используем static-методы (последнее желательней, т.к. меньше проблем с состоянием).
Текущий вариант устраивает всем кроме избыточности параметров и наличия ref-параметров.
Поскольку код инфраструктурный — некритично. Но раздражает