Проблема с производительностью IdFTP при анализе всех файлов в каталоге и подкаталогах

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

class function TATDFTPUtility.findAllDirectoryFiles(var ftpClient: TIdFTP; directory: String; deepness: Integer = 0): TidFTPListItems;
var
  I: Integer;
  localDirectoryListing: TIdFTPListItems;
  baseDirectory: string;
begin
  Result := TIdFTPListItems.Create;
  *// this function uses ftpClient.ChangeDirUp until it reaches the '' directory*
  changeUpToDirectory(ftpClient, '');
  try
    ftpClient.ChangeDir(directory);
    ftpClient.List;
    Result.Assign(ftpClient.DirectoryListing);
    localDirectoryListing := Result;
    baseDirectory := ftpClient.RetrieveCurrentDir;
    for I := 0 to localDirectoryListing.Count - 1 do
    begin
      if (localDirectoryListing.Items[i].ItemType = ditDirectory) then
      begin
        result := addTwoFTPListItems(result, findAllDirectoryFiles(ftpClient, baseDirectory + '/' + localDirectoryListing.Items[i].FileName));
      end;
    end;
  except
  end;
end;

class function TATDFTPUtility.addTwoFTPListItems(listA: TIdFTPListItems; listB: TIdFTPListItems): TidFTPListItems;
var
  i: integer;
begin
  Result := listA;
  for I := 0 to listB.Count - 1 do
  begin
    with Result.Add do
    begin
      Data := listB.Items[i].data;
      Size := listB.Items[i].Size;
      ModifiedDate := listB.Items[i].ModifiedDate;
      LocalFileName := listB.Items[i].LocalFileName;
      FileName := listB.Items[i].FileName;
      ItemType := listB.Items[i].ItemType;
      SizeAvail := listB.Items[i].SizeAvail;
      ModifiedAvail := listB.Items[i].ModifiedAvail;
      PermissionDisplay := listB.Items[i].PermissionDisplay;
    end;
  end;
end;

Теперь проблема в том, что это занимает около 15-20 минут!!! Есть ли более эффективный способ?
Вот несколько фактов об этом конкретном случай:
1- После того, как я запустил программу, она обнаружила около 12000 файлов с почти 100-200 каталогами, но максимальная глубина была около 7
2- Мне нужно только разобрать, и мне не нужно загружать или загружать что-либо
3. Причина, по которой я использовал исключение, заключается в том, что внутри FTP есть несколько папок, к которым у меня нет доступа, и это вызывает ошибку access violation в IdFTP, и я использовал try...except, чтобы игнорировать любой каталог, который может не получить доступ.


person ali ahmadi    schedule 16.11.2016    source источник


Ответы (1)


Вы звоните ChangeDirUp() (возможно много раз?), а затем звоните ChangeDir(). Если directory является абсолютным путем, вы можете просто вызвать ChangeDir() один раз, чтобы сразу перейти к целевой папке и полностью избежать ChangeDirUp(). Рекурсивный цикл внутри findAllDirectoryFiles() использует абсолютные пути из RetrieveCurrentDir(), поэтому повторные вызовы ChangeDirUp() и ChangeDir() напрасны. Вы можете значительно сократить накладные расходы, не перемещаясь вверх и вниз по дереву папок без необходимости.

findAllDirectoryFiles() возвращает только что выделенный TIdFTPListItems, который вызывающая сторона должна освободить. Это само по себе, как правило, плохой выбор дизайна, но особенно в этом случае, потому что рекурсивный цикл вообще не освобождает эти вторичные объекты TIdFTPListItems, поэтому они утекают.

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

Вы полностью игнорируете параметр deepness.

С учетом сказанного попробуйте что-то более похожее на это:

class procedure TATDFTPUtility.findAllDirectoryFiles(ftpClient: TIdFTP; const directory: String;var files: TIdFTPListItems; deepness: Integer = -1);
var
  I: Integer;
  baseDirectory: string;
  subDirectories: TStringList;
  item: TIdFTPListItem;
  localDirectoryListing: TIdFTPListItems;
begin
  try
    if directory <> '' then
      ftpClient.ChangeDir(directory);
    ftpClient.List;
  except
    Exit;
  end;
  baseDirectory := ftpClient.RetrieveCurrentDir;
  localDirectoryListing := ftpClient.DirectoryListing;
  subDirectories := nil;
  try
    for I := 0 to localDirectoryListing.Count - 1 do
    begin
      case localDirectoryListing[i].ItemType of
        ditFile: begin
          item := files.Add;
          item.Assign(localDirectoryListing[i]);
          // if you need the full path of each file...
          item.FileName := baseDirectory + '/' + item.FileName;
        end;
        ditDirectory: begin
          item := localDirectoryListing[i];
          if ((item.FileName <> '.') and (item.FileName <> '..')) and
             ((deepness = -1) or (deepness > 0)) then
          begin
            if subDirectories = nil then
              subDirectories := TStringList.Create;
            subDirectories.Add(baseDirectory + '/' + item.FileName);
          end;
        end;
      end;
    end;
    if subDirectories <> nil then
    begin
      if (deepness > 0) then Dec(deepness);
      for I := 0 to subDirectories.Count - 1 do begin
        findAllDirectoryFiles(ftpClient, subDirectories[I], files, deepness);
      end;
    end;
  finally
    subDirectories.Free;
  end;
end;

При вызове findAllDirectoryFiles() в первый раз вы можете установить directory в одно из следующих значений:

  • пустая строка, чтобы начать поиск в текущем каталоге.

  • подпапка, относящаяся к текущему каталогу.

  • абсолютная папка, которая относится к корню сервера.

И установите deepness либо

  • -1 для бесконечной рекурсии

  • ›= 0, чтобы указать глубину рекурсии.

files := TIdFTPListItems.Create;
try
  TATDFTPUtility.findAllDirectoryFiles(ftpClient, 'desired directory', files, desired deepness);
  // use files as needed...
finally
  files.Free;
end;
person Remy Lebeau    schedule 16.11.2016
comment
спасибо за ответ! я не знал, что могу использовать абсолютный путь и что мне не нужно использовать changeDirUp! и мне еще предстояло внедрить глубину, поэтому она ничего не делала, теперь вернемся к вам, я попробовал ваш код, есть несколько проблем, когда вы вызываете findAllDirectoryFiles(), вы делаете недействительным directoryListing, поэтому я использовал дополнительный FTPListItems и присвоил значение, и я повторил это, второе для должно быть J и, самое главное, тоже есть логическая ошибка! программа работает вечно!!, я зарегистрировался и заметил (читайте следующий комментарий) - person ali ahmadi; 17.11.2016
comment
Ваш дополнительный объект TIdFTPListItems не нужен. Кроме того, предложенное вами изменение привело к новой утечке памяти, поэтому я удалил это изменение. Что касается оригинального DirectoryListing, который становится недействительным во время цикла subdirectories, то это потому, что я поставил этот цикл не в том месте. Я исправил это сейчас (сделав лишнюю копию объекта TIdFTPListItems ненужной). - person Remy Lebeau; 17.11.2016
comment
Я заметил, что программа работает вечно! я зарегистрировал его и увидел, что он зацикливается после входа в папки и после нахождения файлов, он выходит на один слой, входит в те же папки и снова просматривает их! и я думаю, что он просто бесконечно зацикливается на 2 последних слоях, я попытаюсь отладить функцию, если смогу. - person ali ahmadi; 17.11.2016
comment
Попробуйте последний код. Это не должно быть повторное сканирование ранее проверенных папок (если только сервер не использует виртуальные папки, которые перенаправляют в папки выше в иерархии папок. Но это неслыханно, но и не часто). - person Remy Lebeau; 17.11.2016
comment
я только что запустил ваш код, предыдущие проблемы решены, мой logs показывает `Число проанализированных файлов = 11587 Количество проанализированных папок = 689`, что заняло 16 минут! кажется, что каждые 20 файлов занимают около 1 секунды, есть ли более быстрый подход? - person ali ahmadi; 17.11.2016
comment
вот почему мне нужно в первую очередь разобрать все эти файлы!, может быть, вы можете придумать лучший способ, люди на этом конкретном FTP-сервере ежедневно загружают файлы примерно для 30 клиентов, каждый клиент имеет другой файл! все файлы имеют дату в имени файла плюс определенное ключевое слово, которое соответствует их клиенту, вот пример файла на ftp-сервере, ftp-адрес /Remy/RemyLebeau/2016/11/17/RL20161117.eau проблема что формат пути не всегда такой! иногда между ними добавляются дополнительные папки, например, 2016/11/hi!/17 !!(продолжить) - person ali ahmadi; 17.11.2016
comment
(это из предыдущего комментария), и что еще хуже, даже дата не всегда такая! иногда это 016/11/17! В любом случае, дело в том, что мне нужно загрузить файлы в определенные даты, и я попытался найти папку с этой датой, но из-за того, что шаблон настолько произвольный, я просто не могу найти единый шаблон! и я должен написать другой шаблон для каждого пользователя !! единственная стабильная вещь - это формат имени файла! вот почему я решил проанализировать все файлы и отфильтровать их по дате внутри них и их расширению (которое показывает, какому пользователю он принадлежит) - person ali ahmadi; 17.11.2016
comment
20 файлов за 1 секунду = 50 мс на файл. Это не кажется неразумным для парсера. Если вы хотите ускорить код, найдите способ анализировать каждый файл быстрее, чем 50 мс, или параллельно анализировать несколько файлов (используйте несколько FTP-соединений, обрабатывающих разные ветки папок в отдельных потоках), или анализируйте меньше файлов в целом. Возможно, не анализируйте файлы, которые вы уже проанализировали, или, возможно, клиент публикует где-то, где вы можете отслеживать, когда загружается новый файл, а затем анализировать файл в это время. Анализ только 30 файлов в день требует меньше затрат, чем анализ 11587 файлов за один раз. - person Remy Lebeau; 17.11.2016
comment
тогда 50 мс на файл - это нормально, я подумал, может быть, я делаю что-то не так! и последнее, что касается той части, где вы сказали найти способ быстрее анализировать каждый файл, не могли бы вы дать мне несколько идей? Если бы я мог удвоить скорость, этого было бы достаточно для моей работы! - person ali ahmadi; 17.11.2016
comment
@aliahmadi, прежде чем вы сможете оптимизировать скорость, вы должны профилировать свой код, чтобы увидеть, где он на самом деле тратит большую часть своего времени, а затем искать способы уменьшить/устранить любые узкие места. Поскольку вы не показали свой код синтаксического анализа, никто здесь не может вам в этом помочь. Попробуйте опубликовать его в CodeReview. - person Remy Lebeau; 17.11.2016