Простой http сервер на nodejs: response time 1 милисекунда это много?

господа. Написал простейший http сервер на ноде (v15.11) на fastify (v3.20.2) (как одной из самых производительных библиотек на ноде для этих целей). И результаты меня несколько удручают:

  • при отправке 1 запроса время ответа +/- 1 милисекунда
  • при отправке 10 одновременных запросов среднее время ответа = 5,6 милисекунд
Min time: 4 msec 5 µsec 800 nsec
Max time: 5 msec 913 µsec 200 nsec
Avg time: 5 msec 560 µsec 150 nsec
  • при отправке 100 одновременных запросов среднее время ответа = 41 милисекунд
Min time: 21 msec 212 µsec 300 nsec
Max time: 41 msec 570 µsec 300 nsec
Avg time: 40 msec 918 µsec 113 nsec

Как мне кажется для сервера который ничего не делает это ОЧЕНЬ большие тайминги. Может я что-то не правильно делаю? Какие будут мысли на этот счет?

Исходный код:

// server.js
const server = require('fastify')({ keepAliveTimeout: 20000 })

server.get('/ping', async (req, res) => {
  res.code(200).send('')
})

server.listen(3000)
// benchmark.js
const http = require('http')

const ITER = 100
const agent = new http.Agent({ keepAlive: true, maxFreeSockets: 500 })
const { hrtime } = process

console.log('Starting benchmark...')
const hrStart = hrtime.bigint()

makeRequest(0)             // make 1 request for a handshake
  .then(() => start(ITER)) // start benchmark
  .then(results => {       // display benchmark results
    console.log(`Benchmark finished in ${stringify(hrtime.bigint() - hrStart)}`)
    const min = results.reduce((res, v) => Math.min(res, v), Number.MAX_SAFE_INTEGER)
    const max = results.reduce((res, v) => Math.max(res, v), 0)
    const total = results.reduce((res, v) => res + v, 0)
    console.log(`Min time: ${stringify(min)}`)
    console.log(`Max time: ${stringify(max)}`)
    console.log(`Avg time: ${stringify(Math.round(total / ITER))}`)
  })


async function start(iterCount) {
  const results = new Array(ITER) // an array of elapsed time for each request
  for(let nIter = 0; nIter < iterCount; ++nIter) {
    results[nIter] = makeRequest(nIter).then(({ iter, elapsed }) => results[iter] = elapsed)
  }
  return Promise.all(results).then(() => results)
}

function makeRequest(iter) {
  return new Promise(resolve => {
    const start = hrtime.bigint()
    http.get('http://localhost:3000/ping', { agent } , res => {
      res.resume().on('end', () => resolve({ iter, elapsed: Number(hrtime.bigint() - start) }))
    })
  })
}

function stringify(time) {
  time = Number(time)
  if(Number.isNaN(time)) {
    throw new Error(`time is not a number`)
  }
  const nsec = time % 1000
  time = (time - nsec) / 1000
  const mcsec = time % 1000
  time = (time - mcsec) / 1000
  const msec = time
  return `${msec} msec ${mcsec} µsec ${nsec} nsec`
}

Ответы (1 шт):

Автор решения: S.H.

Александр,

позвольте для начала Вас похвалить за отлично проделанную работу!

Вы не только написали сервер, померили, задумались о скорости, задали вопрос - вы еще, фактически, дали отвечающим свою методику измерений.

Теперь - по сути: ну вроде 1 мс (то есть одна тысячная доля секунды) на запрос - это неплохо. Ведь там огромные накладные расходы: открывается tcp коннекшн, происходит начальный хэндшейкинг, потом начинается запихивание в этот коннекшн http заголовков с одной стороны и их обработка на другой стороне. Целое дело!

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

А вообще вопрос интересный. Если он останется открытым и актуальным - я могу попробовать через день-два померить время чисто открытия tcp коннекшна и время такого же сервера, который хостит статику, но написанного на C++.

Еще, говорят, в Golang довольно популярны пузомерки на самый быстрый http ответ. Хотя, если нужна именно скорость - то, может, просто не использовать http? Например, многие биржевые протоколы основаны чисто на udp пакетах.

→ Ссылка