Как сделать так, чтобы эти операции ввода-вывода читались параллельно и производительно

1

У меня есть список файлов: 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 или что-то в этом роде? Память не является проблемой; Я смотрю диспетчер задач, и есть много.

  • 0
    Определите «чрезвычайно» медленно. Вы знаете, что чтение с диска примерно в 100 000 раз медленнее, чем чтение из ОЗУ, верно?
  • 0
    Вы только проверяете, существуют ли файлы? Если бы вы были, я бы написал свой собственный поиск. Поместите все имена файлов в список. Затем начните с базового каталога и выполните рекурсивный поиск по всем каталогам. Когда файл найден, удалите его из списка. Затем вы можете вернуть список необнаруженных файлов с файлами, которые не были удалены из списка. Если вы не пытаетесь это сделать, вам следует больше объяснить, чего вы пытаетесь достичь.
Показать ещё 14 комментариев
Теги:
multithreading
wpf
parallel-processing

4 ответа

0

Я не согласен с вашим утверждением, что вы не можете использовать 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();
    }
}
  • 0
    Очень хороший момент. Производительность ReadAsync еще предстоит проверить на жестких дисках в моем приложении. Для всех остальных: он говорит, что, в основном, на RPM-дисках штраф за переключение контекста запланированных потоков с ReadAsync был бы вредным, потому что эти диски требуют механических движений головок для чтения секторов. Однако обратите внимание на то, насколько пагубно должно зависеть количество ядер на этом ПК из-за функциональности рабочих в AsParallel, поэтому, возможно, это не так плохо, как мы думаем, поскольку степень параллелизма ограничена.
  • 0
    @Alexandru Речь идет не о ReadAsync. Это параллельный период.
Показать ещё 1 комментарий
0

Я получил его работу; проблема заключалась в том, что я пытался использовать ExtendedObservableCollection с AddRange вместо вызова "Добавить несколько раз в каждой диспетчеризации UI"... по какой-то причине производительность методов, список пользователей которых здесь, на самом деле медленнее в моей ситуации: ObservableCollection не поддерживает AddRange метод, поэтому я получаю уведомление для каждого добавленного элемента, кроме того, что касается INotifyCollectionChanging?

Я думаю, потому что он заставляет вас вызывать уведомления об изменении с помощью.Reset(reload) вместо.Add(diff), существует некоторая логика, которая вызывает узкие места.

Прошу прощения за то, что вы не опубликовали остальную часть кода; Я был действительно отброшен этим, и я объясню, почему в одно мгновение. Кроме того, примечание для других, которые сталкиваются с одной и той же проблемой, это может помочь. Основная проблема с инструментами профилирования в этом сценарии заключается в том, что они здесь не помогают. Большая часть вашего времени приложения будет потрачена на чтение файлов независимо. Поэтому вам нужно отдельно тестировать всех диспетчеров.

  • 0
    Асинхронный ввод-вывод дает вам нулевое преимущество в этом сценарии (с чего бы это?). Если ваш код стал быстрее, вы либо изменили что-то другое, либо ваше измерение неверно.
  • 0
    @usr На самом деле ты прав. Производительность не имеет значения, использую ли я ReadAsync или нет. Только что попробовал. Я думал, что по какой-то причине ReadAsync использует какое-то «умное планирование» для такого рода операций ввода-вывода, но я думаю, что нет. Отредактирую ответ, спасибо! Все было так, как я отправлял.
0

Доступ к файлам по своей сути не параллелен. Вы можете использовать только параллелизм, если вы обрабатываете некоторые файлы при чтении других. Нет смысла ждать диск параллельно.

Вместо того, чтобы ждать 100 000 раз 1 мс для доступа к диску, вы программируете ждать один раз 100 000 мс = 100 с.

  • 0
    Но нет ли метода планирования, который оптимизирует это время чтения? Я собираюсь попробовать некоторые хитрые вещи с асинхронными методами. Я имею в виду, в идеале, должен быть какой-то способ оптимизировать время чтения. Должно быть, оно должно быть встроено.
  • 0
    @Alexandru: теоретически возможны преимущества одновременного выполнения нескольких операций чтения, поскольку это позволяет Windows и контроллеру диска планировать чтение более эффективно. Но а) улучшение не будет ужасно драматичным, и б) если вы зайдете слишком далеко, вы столкнетесь с слишком большим количеством споров и меньшей производительностью. Вы должны начать с базового измерения, которое вы можете сравнить с теоретической максимальной производительностью с учетом аппаратного обеспечения. В противном случае, нет никакого способа ответить, является ли то, что вы видите, «слишком медленным».
Показать ещё 2 комментария
0

К сожалению, это неопределенный вопрос без примера воспроизводимого кода. Поэтому невозможно предложить конкретные советы. Но мои две рекомендации:

  • Пропустите экземпляр ParallelOptions, в котором свойство MaxDegreeOfParallelism задано достаточно низким. Что-то вроде количества ядер в вашей системе или даже этого числа минус один.

  • Убедитесь, что вы не слишком много ожидаете от диска. Вы должны начать с известной скорости диска и контроллера и сравнить это с пропускной способностью данных, которую вы получаете. Отрегулируйте степень параллелизма еще ниже, если это похоже на то, что вы уже достигли максимальной теоретической пропускной способности или близки к ней.

Оптимизация производительности - это постановка реалистичных целей на основе известных ограничений аппаратного обеспечения, измерение вашей фактической производительности, а затем просмотр того, как вы можете улучшить самые дорогостоящие элементы вашего алгоритма. Если вы еще не сделали первые два шага, вы действительно должны начать там. :)

  • 0
    Что-то вроде количества ядер в вашей системе, или даже это число минус один. Процессор не будет узким местом. Установка MaxDOP на основе этого - плохая идея.
  • 0
    ОП жалуется, что его пользовательский интерфейс блокируется. Так что что-то потребляет доступные ресурсы процессора. Предполагая, что он правильно реализовал параллельный код (и я допускаю, что без хорошего примера кода это не гарантируется), это говорит о том, что, хотя ввод / вывод должен быть узким местом, ему каким-то образом удалось сохранить занятость ЦП в любом случае. И способ обойти это специально сократить количество параллельных потоков.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню