Насколько быстро да в Node?
Когда разработчики устали говорить да на такие вещи, как пользовательские соглашения, установки или подтверждения в терминале, они пишут такие программы, как да. Вот история достижения высокой пропускной способности y \ n в Node.
Вдохновения и как
Источником вдохновения для этой истории послужила статья Как быстро да в Go, которая сама по себе была вдохновлена этой дискуссией. В нашем случае мы также будем полагаться на pv для измерения пропускной способности и записи пиковых результатов, повторяя поведение по умолчанию да, выводя символ y, за которым следует соответствующий символ конца строки. Мы будем использовать 8,01 ГиБ / с из yes | pv > /dev/null
в качестве базового показателя для измерения. Настройка оборудования доступна ниже.
Использование console.log и process.stdout.write
Возможно, первое, что приходит в голову, это использование console.log
, нашего старого доброго друга, который вряд ли подведет нас, и использование ~ бесконечного цикла while для поддержания работы приложения ... Кроме того, поскольку console.log
автоматически добавляет EOL после каждого журнала, шаг сохраняется :
Как видите, производительность ужасная, она почти в 10 000 раз медленнее, чем наш базовый уровень. Что, если мы откажемся от console
абстракции и сразу начнем писать на stdout
? Все, что нам сейчас нужно, это добавить EOL, импортированный из ОС, и измерить:
Это относительно хорошо, но мы можем добиться большего. Давайте попробуем проследить буферизацию нашего вывода на основе предыдущей статьи, мы будем использовать наименьший размер объекта страницы 4096
, чтобы повысить эффективность при записи из памяти в базовый файловый дескриптор (fd):
В течение короткого периода времени мы получаем некоторую пропускную способность, а затем ничего. process.stdout
- это скрытый поток, который записывает в файловый дескриптор всякий раз, когда может протолкнуть данные, в противном случае он буферизует их обратно в память, пока данные не будут снова отправлены. К сожалению, мы перегружаем асинхронный поток (применяются некоторые исключения) нашим синхронным циклом, особенно с тем количеством данных, которые буферизируются обратно в поток, мы можем исправить это, написав полусинхронный цикл:
Это намного лучше! Это помещает нас на территорию ГиБ, но я считаю, что мы можем продвинуть Node еще дальше.
Обратите внимание, что второй параметр в Buffer.alloc
заполняет весь буфер заданной строкой.
Использование потоков и других оптимизаций
Я уже упоминал, что process.stdout
- это своего рода экзотический поток, так почему бы не передать ему поток? По сути, потоки работают путем записи / чтения данных в зависимости от доступной пропускной способности и буферизации остальных на будущее. Мы можем поумничать с нашей программой yes.js и отправлять только то, что необходимо.
Мы начнем с создания Читаемого потока, который будет помещать буфер равного размера, так как важно всегда соответствовать всему набору символов и не пропускать строку разрыва:
Метод _read
в нашем Stream будет вызываться с размером того, сколько данных нужно прочитать или, в нашем случае, произвести. Затем мы можем создать новый экземпляр этого потока и передать его process.stdout
, что приведет к пропускной способности 3,25 ГБ / с, что совсем неплохо! Но мы выделяем новый буфер и заполняем его каждый раз, когда вызывается метод _read
. Давайте закэшируем наши буферы и создадим финальную версию:
Окончательная пиковая пропускная способность составляет 4,67 ГиБ / с, что в 50 000 раз быстрее, чем мы начали, и превышает половину нашего базового уровня. На той же машине Голанг смог достичь 7,75 ГиБ / с с исходным кодом Мэта Эванса.
Кстати, console.log
не является эффективным решением для ведения журнала, если вы полностью от него зависите, но имеет целый набор встроенных функций, которых нет в простом регистраторе.
Какая была установка для проведения измерений?
Для измерения пиковой пропускной способности все тесты выполнялись отдельно на той же удаленной машине, на которой был установлен Intel Xeon E3–1240 v3 (3,4 ГГц) с 32 ГБ памяти DDR3 под управлением Ubuntu 17.0. На стороне приложения использовались Node.js версий 8.3.0 и 7.10.0 и Golang 1.7.4.
Последние мысли
Вы можете установить и запустить финальный код из npm. Есть много отличных способов продемонстрировать возможности языка / фреймворка, но да начинает становиться одним из моих личных фаворитов, он демонстрирует чистую однопоточную пропускную способность без ущерба для простоты.