2009-11-04 2 views
7

У меня есть огромный каталог около 500 тыс. Jpg-файлов, и я хотел бы архивировать все файлы, которые старше определенной даты. В настоящее время для выполнения сценария требуется несколько часов.Выполняется копирование файла в C#?

Это связано с очень плохой работой серверов хранения GoGrid, но в то же время я уверен, что есть способ более эффективно использовать Ram/Cpu для выполнения того, что я делаю.

Вот код, у меня есть:.

var dirInfo = new DirectoryInfo(PathToSource); 
var fileInfo = dirInfo.GetFiles("*.*"); 
var filesToArchive = fileInfo.Where(f => 
    f.LastWriteTime.Date < StartThresholdInDays.Days().Ago().Date 
     && f.LastWriteTime.Date >= StopThresholdInDays.Days().Ago().Date 
); 

foreach (var file in filesToArchive) 
{ 
    file.CopyTo(PathToTarget+file.Name); 
} 

Дни() s назад() материал просто синтаксический сахар.

+0

Это зависит на операционной системе хоста, которая должна быть первоклассной. –

+0

Ya, правда, там могут быть миллионы файлов, я не могу даже получить счет каталога через Windows Explorer из-за подобных проблем с производительностью. – Scott

+2

Грамматика Nazi говорит: «Performant» - это не слово :) –

ответ

3
+0

Спасибо Mauricio ... это работает на проблему с RAM, но не на CPU. По-прежнему требуется несколько часов, но, по крайней мере, RAM не налетает на меня. – Scott

+0

Это работает достаточно хорошо, чтобы решить мою проблему. Занимает около 2 часов, но теперь он может работать в фоновом режиме с максимальным объемом 4 мегабайта ОЗУ, тогда как раньше он использовал бы сотни мегабайт. – Scott

1

Вы можете поэкспериментировать с использованием (ограниченного числа) потоков для выполнения CopyTo(). Сейчас вся операция ограничена 1 ядром.

Это улучшит производительность только в том случае, если он теперь связан с ЦП. Но если это работает на RAID, это может сработать.

+0

Я верю, что GoGrid - это «в облаке». Могут быть ограничения на активные соединения. Независимо, хороший совет. – user7116

2

Я бы хотел иметь в виду правило 80/20 и отметить, что если основная часть замедления составляет file.CopyTo, и это замедление значительно перевешивает производительность запроса LINQ, тогда я бы не стал беспокоиться. Вы можете проверить это, удалив линию file.CopyTo и заменив ее операцией Console.WriteLine. Время, которое по сравнению с реальной копией. Вы найдете накладные расходы GoGrid по сравнению с остальной частью операции. Моя догадка не будет никаких реалистичных больших выигрышей на вашем конце .

EDIT: Хорошо, поэтому 80% является операцией GetFiles, что неудивительно, если на самом деле в каталоге имеется миллион файлов. Лучше может быть, чтобы начать использовать API Win32 непосредственно (как FindFirstFile и family) и P/Invoke:

[DllImport("kernel32.dll", CharSet=CharSet.Auto)] 
static extern IntPtr FindFirstFile(string lpFileName, 
    out WIN32_FIND_DATA lpFindFileData); 

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

EDIT2: Я бы также подумал об изменении с GetFiles("*.*") до GetFiles(). Поскольку вы просите все, нет смысла в том, чтобы он применял правила глобуса на каждом шагу.

+0

Основная часть операции - это оператор dirInfo.GetFiles ("*. *"). Я делаю тест всего за 5 дней, и у меня заканчивается RAM/Patience, прежде чем я могу даже получить количество файлов в каталоге, из которого можно выполнить запрос linq. Есть ли лучший способ GetFiles [], как только у GetFiles [] вернуть файлы, находящиеся в пределах диапазона, вместо того, чтобы возвращать их все? По крайней мере, в первый раз я могу разбить эту операцию на куски 10%, а затем архивировать каждую ночь. Как сейчас, я не могу никуда уйти. – Scott

+0

Да, изменение структуры каталогов - это то, что я пытаюсь сделать, но сначала мне нужно получить доступ к файлам, не дожидаясь всего дня и вычеркивая время на сервере :) – Scott

10

Единственная часть, которую я думаю, что вы можете улучшить, это dirInfo.GetFiles("*.*"). В .NET 3.5 и ранее он возвращает массив со всеми именами файлов, что требует времени для создания и использования большого количества ОЗУ. В .NET 4.0 существует новый метод Directory.EnumerateFiles, который вместо этого возвращает IEnumerable<string> и извлекает результаты сразу же после их чтения с диска. Это может немного улучшить производительность, но не ожидайте чудес ...

+0

На самом деле, это то, что нужно сделать, EnumerateFiles возвращает Enumerator, а не весь список. Вы сохраняете всю память, необходимую для массива. Предположим, что его 500-килобайтные файлы * 100bytes = 50MBs ОЗУ. Используя Enumerate, вы будете использовать только 100bytes, потому что вы получаете 1 файл за раз. – Kugel

+0

+1, .Net 4.0 имеет множество действительно приятных функций в System.IO. Не уверен, что это улучшит ситуацию с миллионом файлов в каталоге: -D – user7116

2

Вам необходимо использовать стороннюю утилиту для выполнения копирования для вас. Что-то вроде robocopy может значительно ускорить вашу обработку. См. Также https://serverfault.com/questions/54881/quickest-way-of-moving-a-large-number-of-files

+0

+1, robocopy/minage = X/maxage = Y – user7116

+2

И robocopy включен в Win7 и Server 2008 по умолчанию! – joshperry

+0

да, не совсем то, что я назвал бы «третьей стороной»;) –

0

Прислушайтесь к этому Hanselminutes podcast. Скотт беседует с Аароном Боковером, автором медиа-плеера Банши, они столкнулись с этой точной проблемой и поговорили об этом в 8:20 в подкасте.

Если вы можете использовать .Net 4.0, используйте их Directory.EnumerateFiles, как упомянуто Thomas Levesque. Если нет, тогда вам может понадобиться написать свой собственный код хостинга, как в Mono.Posix, используя собственные API Win32.

Смежные вопросы