Максимизация пропускной способности сопрограмм, будь то в JavaScript, Python или даже C++, — это искусство, но есть несколько практических правил, позволяющих сделать правильный выбор.

Рассмотрим массив из 100 элементов, для обработки каждого из которых требуется ввод-вывод. Цель состоит в том, чтобы обработать как можно больше элементов массива за единицу времени (элементов в секунду = пропускная способность).

С традиционными потоками (упреждающая многозадачность) можно было бы использовать пул потоков, о котором легко рассуждать; однако потоки дороги и их мало, а кодирование с мьютексами — это следующий уровень. С сопрограммами программа должна работать с одним потоком, то есть совместной многозадачностью.

Сколько сопрограмм должно обработать этот массив? Запуск 100 сопрограмм одновременно является расточительным, поскольку они потребляют память и нагружают планировщик и сборщик мусора. Выделение памяти и ожидание ввода-вывода снижает пропускную способность. На самом деле такой подход может насытить ресурс на другом конце провода и уменьшить объем памяти, доступной процессу для выполнения несвязанной работы (например, для обслуживания другого входящего запроса REST).

Еще одним недостатком планирования 100 сопрограмм одновременно является обработка ошибок. Одной ошибки обычно достаточно, чтобы прервать обработку всего массива. При возникновении ошибки каждая сопрограмма представляет собой потраченные впустую ресурсы. Поэтому проверяйте успешность всех готовых сопрограмм, прежде чем запускать новые. Это можно сделать с помощью общей переменной ошибки. Более сложные требования к обработке ошибок могут легко занять большую часть вашего бюджета на кодирование и тестирование.

Компьютеры, конечно, не идеально масштабируются. Масштабируемость ограничена общими ресурсами, такими как память и системные дескрипторы ввода-вывода. Внешние ресурсы, такие как базы данных и службы REST, ограничены сходством. Успешные инженеры предвосхищают эти ограничения и обходят их как можно лучше.

Оптимальное количество параллельных сопрограмм часто не интуитивно понятно. Степень параллелизма определяется (в смысле нотации Big O) числом ожиданий ввода-вывода в процессоре элементов данных. Если есть одно ожидание, две сопрограммы удвоят время по сравнению с одной. Если есть два ожидания, три сопрограммы почти гарантированно заметно увеличат пропускную способность. Дополнительные числа сопрограмм сверх ‹Ожидания ввода-вывода›+1 могут обеспечить только номинальные преимущества, в зависимости от емкости и пропускной способности соответствующих систем.

Например, чтение из DynamoDb внутри сети AWS происходит настолько быстро, что однопоточная программа едва может поставить в очередь более нескольких одновременных запросов за раз  — возможно, не более 25. Распределяются ли ресурсы DynamoDb в многопользовательском приложении? Если это так, одиночная работа не должна быть слишком жадной.

Экспериментируйте с реальными средами, чтобы сбалансировать многие проблемы экосистемы.

JavaScript не предоставляет стандартной библиотеки для ограничения количества сопрограмм, создаваемых для пакетных операций. В JavaScript до сих пор нет эквивалента ThreadPool. Поэтому разработчики NodeJS, в частности, часто пишут программы, которые под нагрузкой вылетают из-за ошибок памяти. Облегченная очередь задач хорошего программного обеспечения и подобные пакеты (например, универсальный, но гигантский асинхронный пакет npm) подходят для этого варианта использования.

Заключение

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

Максимизация пропускной способности — это искусство, которое зависит от конкретной задачи и характера всех соответствующих ресурсов, локальных и удаленных. Поиск наилучшего решения требует тщательных экспериментов.