std::chrono

Durasi, clock, dan pengukuran waktu kode yang benar.

Daftar isi

Topik ini membahas pustaka std::chrono, yaitu cara standar C++ untuk menyatakan durasi, memilih clock, dan mengukur lama eksekusi kode. Pembahasan bergerak dari tipe durasi beserta konversinya, dilanjutkan tiga clock standar beserta jaminan masing-masing, lalu ditutup dengan pola pengukuran kinerja yang benar dan kesalahan yang sering terjadi. Contoh yang digunakan bertumpu pada pencatatan log sensor suhu, sehingga setiap API langsung terlihat perannya pada pekerjaan nyata.

Durasi dan konversi antarsatuan#

Tipe inti pada std::chrono adalah std::chrono::duration, yaitu templat kelas yang menyatakan rentang waktu sebagai sejumlah tick dengan tipe representasi dan periode tetap. Tick adalah satuan hitungan terkecil dari durasi tersebut, misalnya satu milidetik pada durasi bertipe milidetik. Pustaka menyediakan alias siap pakai, yaitu nama tipe pendek dari std::chrono::nanoseconds sampai std::chrono::hours, sehingga satuan cukup dituliskan sebagai nama tipe. Sebuah durasi dibangun dari nilai literal, misalnya std::chrono::milliseconds{2500}, dan nilai internalnya dibaca melalui fungsi anggota count().

Program berikut menyatakan interval pencatatan sensor suhu sebesar 2500 milidetik, memecahnya menjadi detik dan sisa milidetik, lalu menghitung total waktu untuk 12 kali pencatatan.

cpp durasi.cpp
#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;

    // interval pencatatan sensor suhu: 2500 milidetik
    milliseconds interval{2500};

    // konversi eksplisit ke detik, lalu hitung sisanya
    seconds interval_detik = duration_cast<seconds>(interval);
    milliseconds sisa = interval - interval_detik;

    std::cout << "Interval : " << interval_detik.count() << " detik "
              << sisa.count() << " milidetik\n";

    // total waktu untuk 12 kali pencatatan
    milliseconds total = interval * 12;
    std::cout << "Total    : " << total.count() << " milidetik\n";
    std::cout << "Total    : "
              << duration_cast<seconds>(total).count() << " detik\n";
    return 0;
}
text Keluaran
Interval : 2 detik 500 milidetik
Total    : 30000 milidetik
Total    : 30 detik

Konversi dari milidetik ke detik ditulis dengan std::chrono::duration_cast karena konversi itu memangkas presisi. Aturan ini disengaja oleh perancang pustaka. Konversi yang tidak menghilangkan informasi, misalnya detik ke milidetik, berlangsung secara implisit, sedangkan konversi yang memotong nilai wajib dinyatakan eksplisit agar niatnya terbaca di kode. Operator aritmetika juga bekerja lintas satuan: pengurangan interval - interval_detik menghasilkan durasi bertipe milidetik karena tipe umum kedua operand adalah milidetik.

Perintah berikut mengompilasi dan menjalankan program contoh di terminal.

bash
# kompilasi durasi.cpp dan simpan biner di /tmp
g++ durasi.cpp -o /tmp/durasi
# jalankan program hasil kompilasi
/tmp/durasi
text Keluaran
Interval : 2 detik 500 milidetik
Total    : 30000 milidetik
Total    : 30 detik

Konversi arah sebaliknya berlangsung tanpa duration_cast karena tidak ada informasi yang hilang, sehingga pernyataan std::chrono::milliseconds m = std::chrono::seconds{2}; dikompilasi tanpa keluhan dan menyimpan nilai 2000. Penulisan durasi juga dapat diringkas dengan literal operator dari namespace std::chrono_literals. Setelah using namespace std::chrono_literals;, pernyataan auto interval = 2500ms; menghasilkan durasi bertipe milidetik, sedangkan 2s menghasilkan durasi dua detik. Kedua bentuk itu menyimpan objek bertipe duration yang sama, sehingga seluruh aturan konversi di atas tetap berlaku.

Tiga clock standar#

Clock adalah sumber waktu yang dapat dibaca program, dan std::chrono menyediakan tiganya, yaitu system_clock, steady_clock, dan high_resolution_clock. Setiap clock menyediakan fungsi statis now() yang mengembalikan time_point, yaitu satu pembacaan waktu dari clock tersebut. Setiap clock juga mendefinisikan epoch, yaitu titik nol yang menjadi acuan seluruh pembacaan waktunya. Ketiganya membawa jaminan yang berbeda, sehingga pemilihan clock menentukan benar atau tidaknya sebuah pengukuran.

ClockSifat waktuKegunaan yang cocok
system_clockWaktu dinding yang dapat maju atau mundurMenampilkan tanggal dan jam pada log
steady_clockMonoton, tidak pernah mundurMengukur durasi eksekusi kode
high_resolution_clockPeriode terpendek dengan jaminan terlemahAlternatif ketika kebutuhan belum spesifik

system_clock mewakili jam sistem yang dibaca manusia, sehingga nilainya dapat berubah karena penyetelan waktu melalui NTP (Network Time Protocol) maupun oleh operator. Fungsi to_time_t() dan from_time_t() menjembatani tipe ini dengan time_t bila tanggal perlu ditampilkan pada laporan. steady_clock memberikan jaminan monotonik, artinya setiap pembacaan berikutnya tidak pernah menghasilkan nilai yang lebih kecil daripada pembacaan sebelumnya. high_resolution_clock hanya menjanjikan periode terpendek tanpa menjamin sifat monotoniknya, dan pada libstdc++ tipe ini merupakan alias dari steady_clock. Karena jaminan itulah system_clock dipakai untuk stempel waktu pada log, sedangkan steady_clock dipakai untuk stopwatch internal program.

Mengukur durasi eksekusi kode dengan benar#

Pola pengukuran selalu sama: ambil dua time_point dari steady_clock menggunakan now(), kurangkan keduanya, lalu konversi selisihnya ke satuan yang mudah dibaca. Pengurangan dua time_point menghasilkan durasi, sehingga seluruh aritmetika pada bagian pertama berlaku juga di sini. Contoh berikut mengukur lamanya sebuah perulangan berat. Bloknya ditandai tidak dijalankan karena keluaran pengukuran bergantung pada kecepatan mesin dan tidak mungkin dibuat tetap.

cpp Contoh: pola pengukuran dengan steady_clock
// tidak-dijalankan (keluaran bergantung kecepatan mesin dan bukan nilai tetap)
#include <chrono>
#include <iostream>

int main() {
    const auto mulai = std::chrono::steady_clock::now();

    int total = 0;
    for (int i = 0; i < 10'000'000; ++i) {
        total += i % 7;
    }

    const auto selesai = std::chrono::steady_clock::now();
    const auto durasi =
        std::chrono::duration_cast<std::chrono::milliseconds>(selesai - mulai);
    std::cout << "total=" << total
              << " dalam " << durasi.count() << " milidetik\n";
    return 0;
}

Hasil pengukuran tunggal jarang stabil karena kecepatan cache, penjadwalan sistem operasi, dan tingkat optimasi compiler turut berperan. Laporkan rentang hasil dari beberapa kali pengulangan alih-alih satu angka tunggal, dan pastikan seluruh kode yang diukur benar-benar selesai dieksekusi sebelum pembacaan now() kedua dilakukan. Untuk pengukuran yang serius, jalankan program versi rilis dengan optimasi, karena angka versi debug dapat menyimpang berkali-kali lipat.

Panggilan now() sendiri juga memakan waktu, sehingga operasi yang sangat singkat sebaiknya tidak diukur satu per satu. Kelompokkan operasi tersebut ke dalam satu blok kerja yang diulang ratusan kali, ukur lamanya keseluruhan blok, lalu bagi hasilnya dengan jumlah pengulangan. Cara ini menenggelamkan biaya tetap pembacaan clock ke dalam total yang jauh lebih besar, dan selisih antarpengukuran pun menjadi lebih kecil.

Kesalahan umum#

  • Mengukur durasi eksekusi dengan system_clock. Nilai jam sistem dapat melompat karena penyetelan NTP, sehingga selisih yang tercatat tidak mencerminkan waktu yang benar-benar berlalu. Perbaikannya adalah menggunakan steady_clock pada setiap pengukuran durasi.
  • Mengira duration_cast<seconds>(milliseconds{2500}) menghasilkan 2,5. Konversi ini memotong menuju nol, sehingga hasilnya 2 dan bagian 500 milidetik hilang tanpa peringatan apa pun. Apabila nilai pecahan dikehendaki, gunakan durasi bertipe representasi double, misalnya duration_cast<std::chrono::duration<double>>.
  • Mencetak objek durasi langsung ke std::cout. Operator keluaran untuk durasi baru tersedia pada C++20, sehingga program gagal dikompilasi. Perbaikannya adalah mencetak durasi.count() dan menyebutkan satuannya secara manual.

Ringkasan#

  • std::chrono::duration menyatakan rentang waktu dengan satuan sebagai bagian dari tipenya, dan konversi yang memangkas presisi wajib melalui duration_cast.
  • system_clock digunakan untuk tanggal dan jam yang dibaca manusia, sedangkan steady_clock digunakan untuk pengukuran karena menjamin hasil yang monotonik.
  • Pola pengukuran yang benar terdiri atas dua panggilan steady_clock::now(), pengurangan kedua hasilnya, duration_cast ke satuan tujuan, dan pelaporan rentang dari beberapa kali pengulangan.

Referensi#