Да, я сказал это! Обратные вызовы выполняются быстрее, если они выполняются параллельно с async/await. И только быстрее, когда мы запускаем последовательные обратные вызовы. в 4,8 раза в 1,9 раза Я несколько изменил эту статью после того, как получил несколько полезных и добрых комментариев по поводу моего хитроумного теста. 😂🙏 Спасибо тебе и за то, что нашли время направить показатели в правильном направлении. Моей первой оплошностью было то, что я на самом деле не дождался завершения выполнения кода, что безумно исказило результаты. Во-вторых, я сравнивал параллельное и последовательное время выполнения, что делает тесты бесполезными. Рикардо Лопес Райан По Итак, это второй раунд, который устраняет мои первоначальные ошибки. Раньше я говорил: Изначально я писал, что NodeJS будет в 34,7 раза быстрее, если вернуться к обратным вызовам! 🤣 Неправильно. Не так впечатляюще, как мои предыдущие плохие тесты (см. комментарии для контекста), но все же значительная разница. Итак, что именно я тестировал? Я сравнивал обратные вызовы с обещаниями и async/await при чтении файла 10 000 раз. И, возможно, это глупый тест, но я хотел знать, что быстрее при вводе-выводе. Затем я наконец сравнил обратные вызовы в Node.js с Go! Теперь угадайте, кто победил? Я не буду злым. . Голанг! TLDR Ниже - лучше. Результаты в . ms Итак, настоящие бенчмаркеры? Полегче со мной в этом вопросе. Но, пожалуйста, оставляйте свои комментарии, чтобы я стал лучше. Все говорят, что Node.js медленный! И это меня бесит. Ведь что значит медленно? Как и все тесты, мой является контекстным. Я начал читать о , просто чтобы хотя бы начать понимать . Цикл событий как это работает Но главное, что я понял, это то, что Node.js передает задачи ввода-вывода в очередь, которая находится вне основного исполняемого потока Node.js. Эта очередь работает . Несколько потоков потенциально могут обрабатывать эти операции ввода-вывода. И именно здесь Node.js может проявить себя, обрабатывая ввод-вывод. чистый С Однако промисы обрабатываются в основном единственном исполняемом потоке. А async/await — это хорошо, обещает, но теперь с добавленной блокировкой. Так обратные вызовы быстрее, чем обещания? Давайте проверим это. Прежде всего. Моя ! Дополнения к работе с . Важно отметить, с какими ресурсами мы работаем. Много памяти и процессора. машина Камма MacBook Pro (14-inch, 2021) Chip Apple M1 Pro Memory 32 GB Cores 10 NodeJS v20.8.1 Итак, у нас есть файл с сообщением . text.txt исходным Hello, world echo "Hello, world" > text.txt И мы будем читать этот текстовый файл, используя собственный Node.js, что означает отсутствие зависимостей от узловых модулей, поскольку мы не хотим снижать скорость при работе с самыми тяжелыми объектами во вселенной. Обратные вызовы Параллельные обратные вызовы Во-первых, давайте начнем с обратных вызовов. Меня интересует, насколько быстро можно прочитать один и тот же файл, причем максимально быстро, весь сразу. А что быстрее параллельного? параллельных // > file-callback-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs"; test('reading file 10,000 times with callback parallel', (t, done) => { let count = 0; for (let i = 0; i < 10000; i++) { fs.readFile("./text.txt", { encoding: 'utf-8'}, (err, data) => { assert.strictEqual(data, "Hello, world"); count++ if (count === 10000) { done() } }) } }); Последовательные обратные вызовы Во-вторых, у нас снова есть обратные вызовы, но последовательные (вернее блокирующие). Меня интересует, насколько быстро можно прочитать один и тот же файл последовательно. Я давно не делал обратных вызовов, поэтому было интересно попробовать еще раз. Хоть и выглядит это некрасиво. // > file-callback-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs"; let read = (i, callback) => { fs.readFile("./text.txt", { encoding: 'utf-8'}, (err, data) => { assert.strictEqual(data, "Hello, world"); i += 1 if (i === 10000) { return callback() } read(i, callback) }) } test('reading file 10,000 times with callback blocking', (t, done) => { read(0, done) }); Асинхронный/ожидающий Затем у нас есть async/await. Мой любимый способ работы с Nodejs. Параллельный асинхронный/ожидающий Это настолько параллельно, насколько это возможно с помощью async/await. Я загружаю все операции в массив и жду их всех, используя . readFile Promise.all // > file-async-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with async parallel', async (t) => { let allFiles = [] for (let i = 0; i < 10000; i++) { allFiles.push(fs.readFile("./text.txt", { encoding: 'utf-8'})) } return await Promise.all(allFiles) .then(allFiles => { return allFiles.forEach((data) => { assert.strictEqual(data, "Hello, world"); }) }) }); Последовательный асинхронный/ожидающий Это было самое простое и лаконичное письмо. // > file-async-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with async blocking', async (t) => { for (let i = 0; i < 10000; i++) { let data = await fs.readFile("./text.txt", { encoding: 'utf-8'}) assert.strictEqual(data, "Hello, world"); } }); Обещания Наконец, у нас есть промисы без async/await. Я давно перестал использовать их в пользу , но меня интересовало, насколько они производительны или нет. async/await Параллельные обещания // > file-promise-parallel.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with promise parallel', (t, done) => { let allFiles = [] for (let i = 0; i < 10000; i++) { allFiles.push(fs.readFile("./text.txt", { encoding: 'utf-8'})) } Promise.all(allFiles) .then(allFiles => { for (let i = 0; i < 10000; i++) { assert.strictEqual(allFiles[i], "Hello, world"); } done() }) }); Последовательные обещания. Опять же, мы хотим дождаться выполнения всех операций . readFile // > file-promise-blocking.test.mjs import test from 'node:test'; import assert from 'node:assert'; import fs from "node:fs/promises"; test('reading file 10,000 times with promises blocking', (t, done) => { let count = 0; for (let i = 0; i < 10000; i++) { let data = fs.readFile("./text.txt", { encoding: 'utf-8'}) .then(data => { assert.strictEqual(data, "Hello, world") count++ if (count === 10000) { done() } }) } }); И вуаля! Результаты 🎉! Я даже пробежал его несколько раз, чтобы лучше читать. Я запускал каждый тест, выполняя: node --test <file>.mjs Это также в 4,7 раза быстрее, чем при параллельном выполнении обещаний! Чтение файла 10 000 раз с помощью обратных вызовов происходит более чем в 5,8 раз быстрее, чем при параллельном использовании async/await! Итак, в мире Node.js обратные вызовы производительны! более Теперь Go быстрее, чем Node.js? Ну, я не пишу на Go, так что это может быть действительно ужасный код, потому что я попросил ChatGPT помочь мне, и тем не менее, он довольно приличным. кажется Хей-хо. Пойдем. Наш код Голанга. package main import ( "fmt" "io/ioutil" "time" ) func main() { startTime := time.Now() for i := 0; i < 10000; i++ { data, err := ioutil.ReadFile("./text.txt") if err != nil { fmt.Printf("Error reading file: %v\n", err) return } if string(data) != "Hello, world" { fmt.Println("File content mismatch: got", string(data), ", want Hello, world") return } } duration := time.Since(startTime) fmt.Printf("Test execution time: %v\n", duration) } И мы запускаем это так: go run main.go И результаты? Test execution time: 58.877125ms 🤯 Go в 4,9 раза быстрее, чем Node.js, при использовании последовательных обратных вызовов. Node.js приближается только к параллельному выполнению. Node.js Async/await в 9,2 раза медленнее, чем Go. Так да. Node.js медленнее. Тем не менее, 10 000 файлов менее чем за 300 мс не заслуживают насмешек. Но я был ошеломлен скоростью Go! Теперь просто примечание. Есть ли у меня плохие ориентиры? У меня действительно были ужасные тесты. Еще раз спасибо Рикардо и Райану. Да, я сделал. Надеюсь, теперь им стало лучше. Но вы можете спросить: кто на самом деле будет читать один и тот же файл снова и снова? Но я надеюсь, что для сравнительного теста между вещами это будет полезное сравнение. Я также не знаю, сколько потоков использует Node.js. Я не знаю, как ядра моего процессора влияют на производительность Go и Node.js. Я мог бы просто арендовать машину AWS с одним ядром и сравнить. Это потому, что я на Mac M1? Как Node.js будет работать в Linux или... Windows? 😱 И есть практичность: да, чтение файла — это одно, но в какой-то момент вам все равно придется подождать, пока файл будет прочитан, чтобы что-то сделать с данными в файле. Таким образом, скорость основного потока по-прежнему очень важна. Итак, вы действительно хотите использовать обратные вызовы? Я имею в виду, ты действительно, действительно этого хочешь? Я не знаю. Я определенно не хочу никому говорить, что делать. Но мне нравится чистый синтаксис async/awaits. Они выглядят лучше. Они читают лучше. Я знаю, что здесь все субъективно, но я помню ад обратных вызовов и был благодарен, когда появились обещания. Это сделало Javascript терпимым. Теперь Golang явно быстрее, чем Node.js в оптимальном состоянии, с обратными вызовами и с async/await, в 9,2 раза! Так что, если нам нужна хорошая читаемость и производительность, Golang — победитель. Хотя мне бы хотелось узнать, как Golang выглядит под капотом. Кто угодно. Это было весело. Это было скорее упражнение, которое помогло мне понять, как обратные вызовы и ввод-вывод работают в цикле событий. Итак, чтобы выйти Node.js медленный? Или мы просто используем Node.js в медленном режиме? Вероятно, там, где важна производительность, Golang стоит того. Я обязательно буду больше использовать Golang в будущем. Также появляется . здесь