Masalah Mengukur Runtime aplikasi Node.js Anda

Mar 07 2023
Mengukur runtime aplikasi Node.js adalah aspek penting dari pengoptimalan kinerja.
Foto oleh Chris Gonzalez dari Burst Disalin

Mengukur runtime aplikasi Node.js adalah aspek penting dari pengoptimalan kinerja. Dengan mengukur berapa lama waktu yang diperlukan untuk mengeksekusi berbagai bagian aplikasi, Anda dapat mengidentifikasi kemacetan dan mengoptimalkan kode untuk meningkatkan performa.

Bagaimana kita bisa mengukur waktu eksekusi suatu fungsi?

Ada banyak cara untuk mengukur waktu di node.js, cara paling beresolusi tinggi untuk melakukannya adalah menggunakan process.hrtime() atau Perf_hooks , keduanya lebih baik daripada menggunakan kelas Date karena tidak bersandar pada jam sistem Anda, tapi tetap saja, ada beberapa perbedaan di antara mereka:

process.hrtime() memberikan resolusi tingkat nanodetik, sedangkan perf_hooks memberikan resolusi tingkat mikrodetik. Ini berarti process.hrtime() umumnya lebih tepat tetapi juga lebih mahal untuk dihubungi.

Sekarang mari kita lihat contoh mengukur runtime dari logika berikut. Kami memiliki logika asinkron (kami dapat menganggap ini adalah beberapa panggilan HTTP) yang diselesaikan setelah 1000 ms. Kami akan menggunakan perf_hooks untuk mengukur berapa lama waktu yang diperlukan untuk memenuhi janji ini.

const {performance} = require('perf_hooks');
const SOME_BIG_NUMBER = 4000000000;

async function asyncLogic(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

const start = performance.now();
asyncLogic(1000).then(() => {
    const time = performance.now() - start;
    console.log(`Time taken: ${time}ms`);
});

let x = 0;
for (let i = 0; i < SOME_BIG_NUMBER; i++) {
    x += Math.sqrt(SOME_BIG_NUMBER);
}

Time taken: 3948ms

Event Loop dan antrian Event

Model runtime JavaScript didasarkan pada loop peristiwa, ini adalah bagian yang bertanggung jawab untuk mengeksekusi kode kita dan memungkinkan kita menggunakan logika asinkron.

Saat kami mengeksekusi kode JavaScript, hal berikut terjadi:

  1. JavaScript mengeksekusi semua kode sinkron sesuai urutan kemunculannya di program.
  2. Jika ada kode asinkron (seperti fungsi asyncLogic kami), JavaScript akan menambahkannya ke antrean acara alih-alih mengeksekusinya secara langsung.
  3. Perulangan acara terus -menerus memeriksa antrean acara untuk melihat apakah ada acara yang menunggu untuk dieksekusi.
  4. Ketika suatu peristiwa siap untuk dieksekusi, perulangan peristiwa memindahkannya dari antrian peristiwa ke tumpukan panggilan , tempat peristiwa itu dapat dieksekusi.
  5. Setelah acara dijalankan, kode sinkron tambahan apa pun dijalankan sebelum loop acara kembali untuk memeriksa antrean acara untuk lebih banyak acara.

Itu sebabnya dalam contoh kita, pengukuran waktunya adalah 4000ms,

  1. Kami menjalankan fungsi asyncLogic, yang ditambahkan ke antrean acara.
  2. Kemudian kami menjalankan logika sinkron kami, yang membutuhkan waktu sekitar 4000 md untuk dijalankan.
  3. Perulangan peristiwa memindahkan panggilan asyncLogic() kita kembali dari antrean peristiwa ke tumpukan panggilan
  4. Kami kemudian menjalankan fungsi callback dan menghitung waktu akhir

Lag Loop Peristiwa

Oleh karena itu, saat memantau kinerja aplikasi kita, ada metrik penting yang harus kita perhatikan, kelambatan loop peristiwa, waktu yang diperlukan untuk loop peristiwa antara menambahkan panggilan balik baru ke antrean peristiwa hingga eksekusi panggilan balik ini.

Kita dapat dengan mudah membuat loop yang memungkinkan kita mengukur kelambatan loop peristiwa kita dengan kode berikut:

function measureEventLoopLag() {
    let lastLoopTime = performance.now();
    setTimeout(() => {
        const delay = performance.now() - lastLoopTime - 1000;
        lastLoopTime = performance.now();
        console.log(`Event Loop lag is: ${delay} ms`)
        measureEventLoopLag();
    }, 1000)
}

Peningkatan waktu proses

Mari kita asumsikan bahwa kode kita memiliki panggilan HTTP yang membutuhkan waktu 4000 md, dan kita juga memiliki putaran lambat yang juga membutuhkan waktu sekitar 4000 md untuk dijalankan. Seperti yang kita lihat di contoh sebelumnya, ketika kita mengukur runtime ini, kita akan mengetahui bahwa kita membutuhkan waktu 4000ms untuk menjalankan seluruh kode.

Setelah memfaktorkan ulang kode kami, kami mengurangi runtime loop lambat sebesar 50%. Saat kami menjalankan ulang kode, runtime keseluruhan menurut pengukuran kami tidak benar-benar berubah, kami masih menunggu sekitar 4000 md untuk menyelesaikan keseluruhan alur karena kami menunggu alur asinkron kami selesai. Sepertinya refactor kami tidak terlalu membantu.

Tidak juga, jika kami melihat dari segi CPU kami membuat perbedaan besar. Panggilan HTTP kami tidak menghabiskan banyak CPU, kebanyakan kami hanya menunggu tanggapan. Jadi sebagian besar waktu, CPU kami digunakan untuk mengeksekusi loop lambat kami, ketika kami memfaktorkan ulang kode dan menguranginya hingga 50%, itu tidak mengubah keseluruhan waktu tetapi memiliki efek signifikan pada CPU kami.

Kesimpulan

Dalam contoh kami, kami memiliki logika yang sangat kecil untuk dipantau, segalanya menjadi sangat rumit ketika Anda memiliki sistem yang kompleks dengan banyak logika untuk dijalankan, dalam hal ini Anda harus mencoba mengukur bagian kecil dari logika Anda untuk memantau penurunan kinerja dengan lebih baik.

Ada dua hal penting yang perlu diperhatikan saat memantau waktu berjalan aplikasi Anda:

  • Mengukur waktu fungsi asinkron dapat dipengaruhi oleh kode sinkron (lag loop peristiwa)
  • Mengukur fungsi yang memiliki logika asinkron dan sinkron dapat menyembunyikan peningkatan (atau penurunan) runtime.
  • Lag loop peristiwa yang lebih tinggi dari ~30ms sangat buruk untuk aplikasi Anda.