testing.b.loop: beberapa pembandingan yang lebih dapat diprediksi untuk Anda

Pengembang go yang telah menulis tolok ukur menggunakan testing Paket mungkin telah menemukan beberapa jebakannya. GO 1.24 memperkenalkan cara baru untuk menulis tolok ukur yang mudah digunakan, tetapi pada saat yang sama jauh lebih kuat: testing.B.Loop.

Secara tradisional, tolok ukur pergi ditulis menggunakan loop dari 0 ke b.N:

func Benchmark(b *testing.B) {
  for range b.N {
    ... code to measure ...
  }
}

Menggunakan b.Loop Sebaliknya adalah perubahan sepele:

func Benchmark(b *testing.B) {
  for b.Loop() {
    ... code to measure ...
  }
}

testing.B.Loop memiliki banyak manfaat:

  • Ini mencegah optimasi kompiler yang tidak diinginkan dalam loop benchmark.
  • Secara otomatis mengecualikan kode pengaturan dan pembersihan dari waktu tolok ukur.
  • Kode tidak dapat secara tidak sengaja bergantung pada jumlah total iterasi atau iterasi saat ini.

Ini semua adalah kesalahan mudah untuk dilakukan b.N-Sato tolok ukur gaya yang secara diam -diam menghasilkan hasil benchmark palsu. Sebagai bonus tambahan, b.Loop-Sato tolok ukur gaya bahkan lengkap dalam waktu yang lebih singkat!

Mari kita jelajahi keuntungannya testing.B.Loop dan bagaimana menggunakannya secara efektif.

Masalah Loop Benchmark Lama

Sebelum GO 1.24, sementara struktur dasar tolok ukur sederhana, tolok ukur yang lebih canggih membutuhkan lebih banyak perawatan:

func Benchmark(b *testing.B) {
  ... setup ...
  b.ResetTimer() // if setup may be expensive
  for range b.N {
    ... code to measure ...
    ... use sinks or accumulation to prevent dead-code elimination ...
  }
  b.StopTimer() // if cleanup or reporting may be expensive
  ... cleanup ...
  ... report ...
}

Jika pengaturan atau pembersihan tidak sepele, pengembang perlu mengelilingi loop benchmark dengan ResetTimer dan/atau StopTimer panggilan. Ini mudah dilupakan, dan bahkan jika pengembang ingat mereka mungkin diperlukan, mungkin sulit untuk menilai apakah pengaturan atau pembersihan “cukup mahal” untuk memerlukannya.

Tanpa ini, testing Paket hanya dapat mengatur waktu seluruh fungsi benchmark. Jika fungsi benchmark menghilangkannya, kode pengaturan dan pembersihan akan dimasukkan dalam pengukuran waktu keseluruhan, secara diam -diam memiringkan hasil benchmark akhir.

Ada perangkap lain yang lebih halus yang membutuhkan pemahaman yang lebih dalam: (sumber contoh)

func isCond(b byte) bool {
  if b%3 == 1 && b%7 == 2 && b%17 == 11 && b%31 == 9 {
    return true
  }
  return false
}

func BenchmarkIsCondWrong(b *testing.B) {
  for range b.N {
    isCond(201)
  }
}

Dalam contoh ini, pengguna mungkin mengamati isCond mengeksekusi dalam waktu sub-nanosecond. CPU cepat, tapi tidak secepat itu! Hasil yang tampaknya anomali ini berasal dari fakta bahwa isCond Dikatakan, dan karena hasilnya tidak pernah digunakan, kompiler menghilangkannya sebagai kode mati. Akibatnya, tolok ukur ini tidak mengukur isCond sama sekali; Ini mengukur berapa lama waktu yang dibutuhkan untuk tidak melakukan apa pun.

Dalam hal ini, hasil sub-nanosecond adalah bendera merah yang jelas, tetapi dalam tolok ukur yang lebih kompleks, eliminasi kode mati parsial dapat menyebabkan hasil yang terlihat masuk akal tetapi masih tidak mengukur apa yang dimaksudkan.

Bagaimana testing.B.Loop Membantu

Tidak seperti b.N-Tentang Benchmark, testing.B.Loop mampu melacak ketika pertama kali dipanggil dalam tolok ukur ketika iterasi akhir berakhir. Itu b.ResetTimer di awal loop dan b.StopTimer pada akhirnya diintegrasikan ke dalam testing.B.Loopmenghilangkan kebutuhan untuk mengelola timer benchmark secara manual untuk kode pengaturan dan pembersihan.

Selanjutnya, kompiler go sekarang mendeteksi loop di mana kondisinya hanya panggilan testing.B.Loop dan mencegah eliminasi kode mati dalam loop. Dalam GO 1.24, ini diimplementasikan dengan melarang inlining ke dalam tubuh loop seperti itu, tetapi kami berencana untuk memperbaiki ini di masa depan.

Fitur bagus lainnya testing.B.Loop adalah pendekatan ramp-up sekali tembak. Dengan a b.N-Tentang Benchmark, Paket Pengujian harus memanggil fungsi tolok ukur beberapa kali dengan nilai yang berbeda dari b.Nmeningkat sampai waktu yang diukur mencapai ambang batas.

Sebaliknya, b.Loop dapat dengan mudah menjalankan loop benchmark sampai mencapai ambang waktu, dan hanya perlu memanggil fungsi benchmark sekali. Secara internal, b.Loop Masih menggunakan proses ramp-up untuk mengamortisasi overhead pengukuran, tetapi ini tersembunyi dari penelepon dan bisa lebih efisien.

Kendala tertentu dari b.N-Ding Loop masih berlaku untuk b.Loop-Ding Loop. Tetap menjadi tanggung jawab pengguna untuk mengelola timer dalam loop benchmark, bila perlu: (contoh sumber)

func BenchmarkSortInts(b *testing.B) {
  ints := make([]int, N)
  for b.Loop() {
    b.StopTimer()
    fillRandomInts(ints)
    b.StartTimer()
    slices.Sort(ints)
  }
}

Dalam contoh ini, untuk membandingkan kinerja penyortiran di tempat slices.Sortarray yang diinisialisasi secara acak diperlukan untuk setiap iterasi. Pengguna harus tetap mengelola timer secara manual dalam kasus seperti itu.

Selain itu, masih perlu ada satu loop seperti itu di badan fungsi benchmark (a b.N-Ding loop tidak dapat hidup berdampingan dengan a b.Loop-Ding Loop), dan setiap iterasi loop harus melakukan hal yang sama.

Kapan harus digunakan

Itu testing.B.Loop Metode sekarang merupakan cara yang disukai untuk menulis tolok ukur:

func Benchmark(b *testing.B) {
  ... setup ...
  for b.Loop() {
    // optional timer control for in-loop setup/cleanup
    ... code to measure ...
  }
  ... cleanup ...
}

testing.B.Loop Menawarkan pembandingan yang lebih cepat, lebih akurat, dan lebih intuitif.

Ucapan Terima Kasih

Terima kasih banyak kepada semua orang di komunitas yang memberikan umpan balik tentang masalah proposal dan melaporkan bug saat fitur ini dirilis! Saya juga berterima kasih kepada Eli Bendersky atas ringkasan blognya yang bermanfaat. Dan akhirnya terima kasih banyak kepada Austin Clements, Cherry Mui dan Michael Pratt atas ulasan mereka, pekerjaan yang bijaksana tentang opsi desain dan peningkatan dokumentasi. Terima kasih atas kontribusi Anda!


Kredit: JUNYANG SHAO

Foto oleh Will Paterson di Unsplash

Artikel ini tersedia di Blog Go di bawah CC dengan lisensi 4,0 akta.