Самый быстрый способ скопировать файлы из одного каталога в другой

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

Для этого я использую следующий код:

using(SqlConnection connection = new SqlConnection("datasource or route"))
{

  connection.Open();

  using(SqlCommand cmd = new SqlCommand("SELECT idPic, namePicFile FROM DocPicFiles", connection))
  using (SqlDataReader reader = cmd.ExecuteReader())
  {

    if (reader != null)
    {
      while (reader.Read())
      {
        //picList IS AN ARRAY THAT Contains All the files names in a directory
        if (picList.Any(s => s.Contains(reader["namePicFile"].ToString())))
        {
          File.Copy("theFile  in the Directory or array picList",  "the destiny directory"+ ".jpg", false)
        }
      }
    }
  }
}

Есть ли способ сделать это за меньшее время? Для этого требуется 1 час для 20 876 записей.


person ger    schedule 26.08.2015    source источник
comment
Сколько из этого времени используется File.Copy и сколько используется для цикла и поиска имен? Если вы не измеряете, вы не можете понять, в чем проблема, и искать жизнеспособные (и существующие) решения.   -  person Steve    schedule 27.08.2015
comment
Разделите свой метод на один, который возвращает строку списка, содержащую все файлы, которые вы хотите скопировать, а затем отправьте этот список в другой метод, который выполняет копирование. Затем вы можете измерить оба метода и выяснить, где находится узкое место.   -  person Kvam    schedule 27.08.2015
comment
если вы можете ром cmd здесь, чем мое решение поможет. Заголовок stackoverflow.com/questions/4743094/   -  person pankaj kumar    schedule 13.08.2020


Ответы (4)


Поскольку ваша подсистема ввода-вывода почти наверняка является здесь узким местом, использование библиотеки параллельных задач, вероятно, настолько хорошо, насколько это возможно:

static void Main(string[] args)
{
  DirectoryInfo source      = new DirectoryInfo( args[0] ) ;
  DirectoryInfo destination = new DirectoryInfo( args[1] ) ;

  HashSet<string> filesToBeCopied = new HashSet<string>( ReadFileNamesFromDatabase() , StringComparer.OrdinalIgnoreCase ) ;

  // you'll probably have to play with MaxDegreeOfParallellism so as to avoid swamping the i/o system
  ParallelOptions options= new ParallelOptions { MaxDegreeOfParallelism = 4 } ;

  Parallel.ForEach( filesToBeCopied.SelectMany( fn => source.EnumerateFiles( fn ) ) , options , fi => {
      string destinationPath = Path.Combine( destination.FullName , Path.ChangeExtension( fi.Name , ".jpg") ) ;
      fi.CopyTo( destinationPath , false ) ;
  }) ;

}

public static IEnumerable<string> ReadFileNamesFromDatabase()
{
  using ( SqlConnection connection = new SqlConnection( "connection-string" ) )
  using ( SqlCommand cmd = connection.CreateCommand() )
  {
    cmd.CommandType = CommandType.Text ;
    cmd.CommandText = @"
      select idPic ,
             namePicFile
      from DocPicFiles
      " ;

    connection.Open() ;
    using ( SqlDataReader reader = cmd.ExecuteReader() )
    {
      while ( reader.Read() )
      {
        yield return reader.GetString(1) ;
      }
    }
    connection.Close() ;

  }
}
person Nicholas Carey    schedule 26.08.2015
comment
Есть ли у вас какие-либо измерения времени, подтверждающие, что TPL будет быстрее, чем текущий подход OP? Я видел, что многопоточный способ работает медленнее, чем однопоточный. - person displayName; 27.08.2015
comment
@nicholas-carey У меня проблемы с этой строкой: Parallel.ForEach( filesToBeCopied.SelectMany( fn => source.EnumerateFiles( fn )) , options , fi =› { string destinationPath = Path.Combine( destination.FullName , Path.ChangeExtension(fi.Name, .jpg)) fi.CopyTo(destinationPath, false); - person ger; 27.08.2015

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

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

РЕДАКТИРОВАТЬ: я считаю плохой практикой держать соединение с базой данных открытым в течение такого длительного времени. Я предлагаю вам извлечь все необходимые данные из некоторого кеша в памяти (массив, список и т. д.), а затем перебирать их по мере копирования файлов. Соединение с базой данных является ценным ресурсом, и в приложениях, которые должны обрабатывать высокий параллелизм (но не только), быстрое освобождение соединения является обязательным.

person Mihai Caracostea    schedule 26.08.2015
comment
Ага! при подключении ресурс увеличивается, а я поменял список на объект DocPicFiles, этот сдвиг значительно уменьшается, действие копирования 35 минут!! - person ger; 27.08.2015
comment
@ger: Я был удивлен, увидев такую ​​большую разницу во времени с этим небольшим изменением. Если это возможно для вас, можете ли вы снова запустить свой код с открытым соединением и без него. Таким образом, мы будем знать, что нет никакого другого фактора в игре. - person displayName; 31.08.2015

Позвольте мне сделать предположение - Ммммм... Нет. Нет способа сделать это быстрее.

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

См. ответы на этот вопрос, который я задавал ранее.

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

Ниже приведено кое-что, чтобы мы могли понять, что означает медленно при записи на диск по сравнению с кэшем. Если доступ к кэшу занимает 10 минут, это означает, что чтение с диска занимает 2 года. Все обращения показаны на изображении ниже. Очевидно, что когда ваш код будет выполняться, узким местом будет запись на диск. Лучшее, что вы можете сделать, чтобы запись на диск оставалась последовательной.

введите здесь описание изображения

person displayName    schedule 26.08.2015
comment
Заголовок stackoverflow.com/questions/4743094/ - person pankaj kumar; 13.08.2020

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

person Keith Gresham    schedule 19.03.2018
comment
Привет, @keith gresham, но мне нужен один к одному, а не весь пакет файлов! Спасибо! - person ger; 20.03.2018
comment
@ger Я полагаю, что решение Кейта относится к проблеме перемещения большого количества файлов, когда источник и место назначения находятся на разных физических дисках. В этом конкретном случае метод, который он здесь упоминает, может ускорить работу за счет консолидации целевых записей MFT, что, в свою очередь, может уменьшить количество промахов кэша во время этой сложной операции. - person Glenn Slayden; 24.07.2019