Skip to content

Прибавление чисел

Нам не нужен сложный код для визуализации и понимания семантики, которую мы рассмотрели в прошлых главах. Посмотри на следующую функцию с именем add, которая суммирует набор целых чисел.

В листинге №1 выше в строке 36 объявлена названная функция add, которая принимает набор целых чисел и возвращает сумму набора. Он начинается в строке 37 с объявления v переменной, содержащей сумму. Затем в строке 38 функция линейно обходит коллекцию, и каждое число добавляется к текущей сумме в строке 39. Наконец, в строке 41 функция возвращает окончательную сумму вызывающей стороне.

Вопрос: является ли функция add рабочей нагрузкой, подходящей для выполнения не по порядку? Скорее всего да. Коллекция целых чисел может быть разбита на более мелкие списки, и эти списки могут обрабатываться конкурентно. После суммирования всех меньших списков набор сумм можно сложить вместе, чтобы получить тот же ответ, что и в последовательной версии.

Однако есть еще один вопрос, который приходит на ум. Сколько меньших списков нужно создать и обработать независимо, чтобы получить наилучшую пропускную способность? Чтобы ответить на этот вопрос, нужно знать, какая рабочая нагрузка add выполняется. Функция add выполняет рабочую нагрузку, связанную с ЦП, потому что алгоритм выполняет чистую математику, и ничто из того, что он делает, не приведет к переходу горутины в естественное состояние ожидания. Это означает, что использование одной Goroutine на каждый поток ОС/аппаратного обеспечения — это все, что необходимо для хорошей пропускной способности.

В листинге №2 ниже представлена наша конкурентная версия add.

Примечание. Есть несколько способов и вариантов, которые можно использовать при написании конкурентной версии add. Пока не зацикливайся на эт__ой конкретной реализации.

Пришло время поработать с кодом!

В листинге №2 представлена функция addConcurrent, которая является конкурентной версией функции add. Конкурентная версия использует 26 строк кода по сравнению с пятью строками кода для неконкурентной версии. Кода много, поэтому выделим только важные для понимания строки.

Строка 48. Каждая горутина получит собственный уникальный, но меньший список чисел для добавления. Размер списка рассчитывается путем деления размера коллекции на количество горутин.

Строка 53. Пул горутин создается для выполнения работы по добавлению.

Строка 57-59. Последняя горутина добавит оставшийся список чисел, который может быть больше, чем в других горутинах.

Строка 66. Сумма меньших списков суммируется в окончательную сумму.

Конкурентная версия определенно сложнее, чем последовательная, но стоит ли она того? Лучший способ ответить на этот вопрос — создать эталон. Для этих тестов использовался набор из 10 миллионов чисел с отключенным сборщиком мусора. Существует последовательная версия, использующая функцию add, и конкурентная версия, использующая функцию addConcurrent.

В листинге №3 выше показаны эталонные функции. Вот результаты, когда для всех горутин доступен только один поток ОС/аппаратного обеспечения. Последовательная версия использует одну Горутину, а конкурентная версия использует runtime.NumCPU или восемь Горутин в нашем случае. В этом случае конкурентная версия использует конкурентность без параллелизма.

Примечание. Запустить тест на локальном компьютере сложно. Существует много факторов, которые могут привести к неточности тестов__. У__беди__сь, что твоя машина (ПК) максимально простаивает, и несколько раз запусти тесты для точного результата__. Дважды запускаемый инструментом тестирования тест дает наиболее стабильные результаты.

Бенчмарк в листинге №4 выше показывает, что версия Sequential примерно на 10-13% быстрее, чем Concurrent, когда для всех Goroutines доступен только один поток ОС/аппаратного обеспечения. Это то, что мы ожидали, поскольку конкурентная версия имеет накладные операции на переключение контекста в этом единственном потоке ОС и управление горутинами.

Вот результаты, когда для каждой горутины доступен отдельный поток ОС/оборудования. Последовательная версия использует одну Горутину, а конкурентная версия использует runtime.NumCPU или восемь Горутин на используемой машине. В этом случае конкурентная версия использует конкурентность с параллелизмом.

Тест в листинге №5 выше показывает, что конкурентная версия примерно на 41-43 процента быстрее, чем последовательная версия, когда для каждой Goroutine доступен отдельный поток ОС/аппаратного обеспечения. Это то, чего следует ожидать, поскольку все горутины теперь работают параллельно, восемь горутин выполняют свою параллельную работу одновременно.