Чтение файлов
Ранее мы рассмотрели две рабочие нагрузки, связанные с процессором. Но как насчет рабочей нагрузки, связанной с вводом-выводом? Отличается ли семантика, когда горутины естественным образом входят в состояние ожидания и выходят из него? Посмотри на рабочую нагрузку IO-Bound, которая читает файлы и выполняет текстовый поиск.
Эта первая версия является последовательной версией функции с именем find.
В листинге выше мы видим последовательную версию функции find. В строке 43 объявляется переменная с именем found для ведения подсчета количества раз, когда указанное имя topic встречается в заданном документе. Затем в строке 44 документы перебираются, и каждый документ считывается в строке 45 с помощью функции read. Наконец, в строке 49-53 функция Contains из пакета strings используется для проверки того, можно ли найти topic в наборе элементов, считанных из документа. Если topic найден, found переменная увеличивается на единицу.
Вот реализация функции read, которую вызывает find.
Пришло время поработать с кодом!
Функция read в листинге выше начинается с time.Sleep вызова на одну миллисекунду. Этот вызов используется для имитации задержки, которая могла бы возникнуть, если бы мы выполнили реальный системный вызов для чтения документа с диска. Постоянство этой задержки важно для точного измерения производительности последовательной версии по find сравнению с конкурентной версией. Затем в строках 35–39 фиктивный XML-документ, хранящийся в глобальной переменной file, преобразуется в значение структуры для обработки. Наконец, набор элементов возвращается вызывающей стороне в строке 39.
Примечание. Есть несколько способов и вариантов, которые можно использовать при написании конкурентной версии find.
Пришло время поработать с кодом!
В листинге выше представлена функция findConcurrent, которая является конкурентной версией функции find. Конкурентная версия использует 30 строк кода, в отличие от 13 строк кода для не конкурентной версии. Наша цель при реализации конкурентной версии состояла в том, чтобы контролировать количество горутин, которые используются для обработки неизвестного количества документов. Мы используем шаблон объединения, в котором канал используется для подачи пула горутин.
Кода много, поэтому мы выделили только важные для понимания строки:
-
Строки 61-64: канал создан и заполнен всеми документами для обработки.
-
Строка 65: канал закрыт, поэтому пул горутин естественным образом завершается, когда все документы обработаны.
-
Строка 70: создается пул горутин.
-
Строка 73-83: каждая горутина в пуле получает документ из канала, считывает документ в память и проверяет содержимое для темы. При совпадении локальная найденная переменная увеличивается.
-
Строка 84: сумма отдельных подсчетов Горутины суммируется в окончательный подсчет.
Конкурентная версия определенно сложнее, чем последовательная, но стоит ли она того? Лучший способ снова ответить на этот вопрос — создать бенчмарк. Для этих тестов была использована коллекция из 1000 документов с отключенным сборщиком мусора. Существует последовательная версия, использующая функцию find, и конкурентная версия, использующая функцию findConcurrent.
В листинге выше показаны эталонные функции. Вот результаты, когда для всех горутин доступен только один поток ОС/аппаратного обеспечения. Последовательная версия использует одну горутину, а конкурентная версия использует runtime.NumCPU или восемь горутин на своей машине. В этом случае конкурентная версия использует конкурентность без параллелизма.
Бенчмарк в листинге выше показывает, что конкурентная версия примерно на 87–88% быстрее, чем последовательная версия, когда для всех горутин доступен только один поток ОС/аппаратного обеспечения. Это то, чего мы ожидали, поскольку все горутины эффективно используют один поток ОС/аппаратного обеспечения. Естественное переключение контекста, происходящее для каждой Goroutine в read вызове, позволяет выполнять больше работы с течением времени в одном потоке ОС/оборудования.
Вот эталон при использовании конкурентной модели с параллелизмом.
Бенчмарк в листинге выше показывает, что добавление дополнительных потоков ОС/аппаратных средств не обеспечивает повышения производительности.
Итак, в этой теме мы постарались понять, как определить, подходит ли рабочая нагрузка для использования параллелизма.
Вы можете ясно видеть, что с рабочими нагрузками, связанными с вводом-выводом, параллелизм не нужен, чтобы получить большой прирост производительности. Что противоположно тому, что вы видели при работе с привязкой к процессору. Когда дело доходит до алгоритма вроде пузырьковой сортировки, использование параллелизма добавило бы сложности без какого-либо реального выигрыша в производительности. Важно определить, подходит ли твоя рабочая нагрузка для параллелизма, а затем определить тип рабочей нагрузки, которую нужно использовать для правильной семантики.
С оригиналом статьи ты можешь ознакомиться по ссылке.
Рекомендуем также изучить дополнительные материалы по ссылке: