У меня есть список файлов: List<string> Files
в моем приложении С# -based WPF.
Files
содержат ~ 1,000,000 уникальных путей к файлам.
Я запустил профилировщик в своем приложении. Когда я пытаюсь выполнять параллельные операции, он ДЕЙСТВИТЕЛЬНО отстает, потому что он привязан к IO. Он даже отстает от моих потоков пользовательского интерфейса, несмотря на то, что у них нет диспетчеров (обратите внимание на две строки, отмеченные мной):
Files.AsParallel().ForAll(x =>
{
char[] buffer = new char[0x100000];
using (FileStream stream = new FileStream(x, FileMode.Open, FileAccess.Read)) // EXTREMELY SLOW
using (StreamReader reader = new StreamReader(stream, true))
{
while (true)
{
int bytesRead = reader.Read(buffer, 0, buffer.Length); // EXTREMELY SLOW
if (bytesRead <= 0)
{
break;
}
}
}
}
Эти две строки кода занимают ~ 70% от всех моих тестовых прогонов. Я хочу достичь максимальной распараллеливания для ввода-вывода, сохраняя при этом производительность, чтобы он не исказил пользовательский интерфейс приложения полностью. Нет ничего другого, влияющего на мою работу. Доказательство. Использование Files.ForEach
не Files.ForEach
мой пользовательский интерфейс, а WithDegreeOfParallelism
помогает WithDegreeOfParallelism
(но я пишу приложение, которое предполагается использовать на любом ПК, поэтому я не могу предположить определенную степень параллелизма для этого вычисления); также, на ПК, на котором я установлен, есть твердотельный жесткий диск. Я искал в StackOverflow и нашел ссылки, которые говорят об использовании асинхронных методов чтения IO. Однако я не уверен, как они применяются в этом случае. Может, кто-то может пролить свет? Также; как вы можете настроить время конструктора нового FileStream; что это возможно?
Редактировать: Ну, вот что-то странное, что я заметил... пользовательский интерфейс не так сильно разбит, когда я поменяю Read для ReadAsync, все еще использую AsParallel. Просто ожидая, что задача, созданная ReadAsync для завершения, заставляет мой поток пользовательского интерфейса поддерживать некоторую степень удобства использования. Я думаю, что это делает какое-то асинхронное планирование, которое выполняется в этом методе для поддержания оптимального использования диска, а не для дробления существующих потоков. И на этой ноте, есть ли когда-либо шанс, что операционная система утверждает, что существующие потоки выполняют IO, например, поток пользовательского интерфейса приложения? Я серьезно не понимаю, почему его замедляет мой поток пользовательского интерфейса. Является ли работа по планированию ОС от IO на моем потоке или что-то еще? Сделали ли они что-то в CLR, чтобы есть темы, которые не были явно связаны с помощью Thread.BeginThreadAffinity
или что-то в этом роде? Память не является проблемой; Я смотрю диспетчер задач, и есть много.
Я не согласен с вашим утверждением, что вы не можете использовать WithDegreeOfParallelism, потому что он будет использоваться на любом ПК. Вы можете установить его на количество процессоров. Не используя WithDegreeOfParallelism, вы будете раздавлены на некоторых ПК.
Вы оптимизированы для твердотельного диска, где голова не должна двигаться. Я не думаю, что этот неограниченный параллельный дизайн будет зависеть от обычного диска (любого ПК).
Я бы попробовал BlockingCollection с тремя очередями: FileStream, StreamReader и ObservableCollection. Ограничьте FileStream как 4 - он просто должен опережать StreamReader. И никакого параллелизма.
Одна голова - одна голова. Он не может читать из 5 или 5000 файлов быстрее, чем он может читать с 1. На твердом состоянии нет переключения штрафа из файла в файл - на обычном диске существует значительный штраф. Если ваши файлы фрагментированы, существует значительный штраф (на обычном диске).
Вы не показываете, что записывают данные, но следующим шагом было бы поместить запись в другую очередь с помощью BlockingCollection в BlockingCollection. EG sb.Append (текст); в отдельной очереди. Но это может быть больше накладных расходов, чем того стоит. Держите эту голову как можно ближе к 100% занятости в одном непрерывном файле, это лучшее, что вы собираетесь делать.
private async Task<string> ReadTextAsync(string filePath)
{
using (FileStream sourceStream = new FileStream(filePath,
FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 4096, useAsync: true))
{
StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
string text = Encoding.Unicode.GetString(buffer, 0, numRead);
sb.Append(text);
}
return sb.ToString();
}
}
Я получил его работу; проблема заключалась в том, что я пытался использовать ExtendedObservableCollection с AddRange вместо вызова "Добавить несколько раз в каждой диспетчеризации UI"... по какой-то причине производительность методов, список пользователей которых здесь, на самом деле медленнее в моей ситуации: ObservableCollection не поддерживает AddRange метод, поэтому я получаю уведомление для каждого добавленного элемента, кроме того, что касается INotifyCollectionChanging?
Я думаю, потому что он заставляет вас вызывать уведомления об изменении с помощью.Reset(reload) вместо.Add(diff), существует некоторая логика, которая вызывает узкие места.
Прошу прощения за то, что вы не опубликовали остальную часть кода; Я был действительно отброшен этим, и я объясню, почему в одно мгновение. Кроме того, примечание для других, которые сталкиваются с одной и той же проблемой, это может помочь. Основная проблема с инструментами профилирования в этом сценарии заключается в том, что они здесь не помогают. Большая часть вашего времени приложения будет потрачена на чтение файлов независимо. Поэтому вам нужно отдельно тестировать всех диспетчеров.
Доступ к файлам по своей сути не параллелен. Вы можете использовать только параллелизм, если вы обрабатываете некоторые файлы при чтении других. Нет смысла ждать диск параллельно.
Вместо того, чтобы ждать 100 000 раз 1 мс для доступа к диску, вы программируете ждать один раз 100 000 мс = 100 с.
К сожалению, это неопределенный вопрос без примера воспроизводимого кода. Поэтому невозможно предложить конкретные советы. Но мои две рекомендации:
Пропустите экземпляр ParallelOptions, в котором свойство MaxDegreeOfParallelism задано достаточно низким. Что-то вроде количества ядер в вашей системе или даже этого числа минус один.
Убедитесь, что вы не слишком много ожидаете от диска. Вы должны начать с известной скорости диска и контроллера и сравнить это с пропускной способностью данных, которую вы получаете. Отрегулируйте степень параллелизма еще ниже, если это похоже на то, что вы уже достигли максимальной теоретической пропускной способности или близки к ней.
Оптимизация производительности - это постановка реалистичных целей на основе известных ограничений аппаратного обеспечения, измерение вашей фактической производительности, а затем просмотр того, как вы можете улучшить самые дорогостоящие элементы вашего алгоритма. Если вы еще не сделали первые два шага, вы действительно должны начать там. :)