0

В общем, другу понадобился софт, который удаленно, на его сервере ищет из всех содержащихся там текстовых файлов совпадение по заданному слову. Я написал класс отвечающий за TCP соединение и вот тут вопрос по классу, который будет искать. В принципе все работает, даже системные ресурсы не так сильно жрет (за исключением процессора до 20%), но все же скорость поиска меня не устаревает, хочется побыстрее. Вот код и вопрос, как можно улучшить, ускорить его работу

foreach (string item in dirWork)
{
    foreach (string item2 in SafeEnumerateFiles(item))
    {
        using (StreamReader _reader = new StreamReader(new BufferedStream(File.OpenRead(item2), 1024 * 1024)))
        {
            string line;
            while ((line = await _reader.ReadLineAsync()) != null)
            {
                if (line.Contains(ItemSearch))
                {
                    name.Add($"{Path.GetFileName(item2)}");
                    result.Add($"{line}");
                }
            }
        }
    }
}

dirWork содержит коллекцию папок лежащие на разных дисках. SafeEnumerateFiles(item) метод возвращает полную коллекцию всех файлов в папке и подпапках.

Podreju
  • 169
  • Если есть сеть, значит узкое место там. Показанный код выглядит нормально. – aepot May 24 '21 at 23:32
  • @aepot поиск производится на TCP сервере, т.е в самом приложении, найденные строки отправляются клиенту, а так же имя файла – Podreju May 24 '21 at 23:34
  • 1
    Интерполяции строк лишние, привОдите строки к строкам. – aepot May 24 '21 at 23:35
  • @aepot да, спасибо, забыл убрать, т.е скорость тут более не выжать? – Podreju May 24 '21 at 23:37
  • StringComparison.Ordinal в помощь. – Alexander Petrov May 24 '21 at 23:54
  • Возможно, следует убрать асинхронность. Или же вместо File.OpenRead использовать new FileStream с параметром bool async = true. – Alexander Petrov May 25 '21 at 00:00
  • 1
    Если искать нужно часто и много, то стоит посмотреть на https://ru.stackoverflow.com/a/532675/184217 – Alexander Petrov May 25 '21 at 00:03
  • 1

1 Answers1

3

Если вам нужна скорость, то я бы вот что попробовал

  1. Убрал бы асинхронность. Асинхронность делает приложение более отзывачивым, но конкретная операция может стать медленней из за построения конечногго автомата и переключений контекста. Но это чисто догадка, я не проверял её правдивость.

  2. Увеличил бы буфер. Один мегабайт таки это маловато. Я бы сделал магабайт 10-100 на поток

  3. Обрабатывал бы файлы параллельно. При большои буфере в одном потоке когда файл считыватся, проц простаивает и когда данные обрабатываются - простаивает жесткий диск.

Вот пример

private IEnumerable<(string filename, string line)> GetFilieredLines(string file, string pattern)
{
    using(var reader = new StreamReader( new BufferedStream( File.OpenRead(file), 10*1024*1024) ))
    {
        while(!reader.EndOfStream)
        {
            var line = reader.ReadLine();
            if(line.Contains(pattern)) yield return (file, line);
        }
    }
}

Как вызывать

var folder = @"D:\.....";
var results = Directory.GetFiles(folder, "*.cs", SearchOption.AllDirectories)
    .AsParallel()
    .SelectMany(x=>GetFilieredLines(x, "public class"))
    .Dump();

Вывод

...

tym32167
  • 32,857
  • Ругается на это, инфы про дамп не могу найти .Dump(); – Podreju May 25 '21 at 02:09
  • так уберите .Dump() из кода, это метод LinqPad - проги, которую я использовал, чтобы запустить скритп на C# – tym32167 May 25 '21 at 02:13
  • @tym32167 Если вы сравните размер строк до вызова Contains: if (line.Length >= pattern.Length && line.Contains(pattern)) - это может ускорить сравнение в 5-10 раз для тех случаев, когда искомая строка длиннее итерируемой. В противном случае замедление будет ~10-15% (больше похоже на погрешность, т.к. разница в миллисекунды на 10_000_000 строк). Ускорение связано с тем, что внутри метода Contains выбрасывается перехватываемое исключение ArgumentOutOfRangeException ровно на той-же самой проверке. – Blackmeser May 25 '21 at 03:59
  • @Blackmeser там проверка на StartIndex, который всегда 0. К тому же подобные оптимизации сильно зависят от характера данных в файлах и паттерна. – tym32167 May 25 '21 at 04:14
  • Там проверка: if (count < 0 || startIndex > this.Length - count) throw new ArgumentOutOfRangeException которая true, когда длина входящей строки больше внутренней, т.к. выполняется условие 0 > line.length - pattern.length – Blackmeser May 25 '21 at 04:30
  • @Blackmeser не совсем, ведь в качестве count там передается this.Length, то есть выражение startIndex > this.Length - count становится startIndex > this.Length - this.Length, то есть в случае, что я выше описал, это будет 0>0, что никогда не является верным. То есть вы можете смело писать if (string.Empty.Contains("long string")) {} и никаких исключений выкинуто не будет. – tym32167 May 25 '21 at 04:47
  • @tym32167 точно, перепутал строку, но почему тогда такое падение производительности если всё время вызывать Contains?... – Blackmeser May 25 '21 at 04:59
  • @Blackmeser это надо замерять. Просто предположу, что дело доходит до неуправляемого вызова, что само по себе требует некоторого времени. – tym32167 May 25 '21 at 13:16
  • @tym32167 может быть один раз, для инициализации, но не для каждой же строки... Хотя тема довольно интересная, узнать сколько времени занимает такой вызов. – Blackmeser May 25 '21 at 17:01