Setelah 26 iterasi, Linux meningkatkan CPU hingga kecepatan clock maksimum karena proses Anda menggunakan potongan waktu penuhnya beberapa kali berturut-turut.
Jika Anda memeriksa dengan penghitung kinerja alih-alih waktu jam dinding, Anda akan melihat bahwa siklus jam inti per loop penundaan tetap konstan, mengonfirmasi bahwa itu hanya efek DVFS (yang digunakan semua CPU modern untuk berjalan dengan lebih banyak energi- frekuensi dan voltase yang efisien di sebagian besar waktu).
Jika Anda menguji pada Skylake dengan dukungan kernel untuk mode manajemen daya baru (di mana perangkat keras mengambil kendali penuh atas kecepatan clock), peningkatan akan terjadi jauh lebih cepat.
Jika Anda membiarkannya berjalan sebentar pada CPU Intel dengan Turbo, Anda mungkin akan melihat waktu per iterasi sedikit meningkat lagi setelah batas termal memerlukan kecepatan jam untuk dikurangi kembali ke frekuensi berkelanjutan maksimum. (Lihat Mengapa CPU saya tidak dapat mempertahankan performa puncak di HPC untuk informasi lebih lanjut tentang Turbo yang membuat CPU bekerja lebih cepat daripada yang dapat dipertahankannya untuk beban kerja berdaya tinggi.)
Memperkenalkan usleep
mencegah pengatur frekuensi CPU Linux meningkatkan kecepatan clock, karena prosesnya tidak menghasilkan beban 100% bahkan pada frekuensi minimum. (Yaitu heuristik kernel memutuskan bahwa CPU berjalan cukup cepat untuk beban kerja yang berjalan di atasnya.)
komentar tentang teori lain :
re:teori David bahwa konteks potensial beralih dari usleep
dapat mencemari cache:Itu bukan ide yang buruk secara umum, tetapi tidak membantu menjelaskan kode ini.
Polusi cache/TLB sama sekali tidak penting untuk eksperimen ini . Pada dasarnya tidak ada apa pun di dalam jendela pengaturan waktu yang menyentuh memori selain bagian akhir tumpukan. Sebagian besar waktu dihabiskan dalam lingkaran kecil (1 baris cache instruksi) yang hanya menyentuh satu int
memori tumpukan. Setiap potensi polusi cache selama usleep
adalah sebagian kecil dari waktu untuk kode ini (kode sebenarnya akan berbeda)!
Lebih detail untuk x86:
Panggilan ke clock()
itu sendiri mungkin kehilangan cache, tetapi kehilangan cache pengambilan kode menunda pengukuran waktu mulai, alih-alih menjadi bagian dari apa yang diukur. Panggilan kedua ke clock()
hampir tidak akan pernah tertunda, karena seharusnya masih panas di cache.
run
fungsi mungkin berada di baris cache yang berbeda dari main
(karena gcc menandai main
sebagai "dingin", sehingga kurang dioptimalkan dan ditempatkan dengan fungsi/data dingin lainnya). Kita dapat mengharapkan satu atau dua cache instruksi hilang. Mereka mungkin masih berada di halaman 4k yang sama, jadi main
akan memicu potensi kehilangan TLB sebelum memasuki wilayah waktu program.
gcc -O0 akan mengkompilasi kode OP menjadi seperti ini (Godbolt Compiler explorer):menyimpan penghitung loop dalam memori di stack.
Loop kosong menyimpan penghitung loop dalam memori tumpukan, jadi pada CPU Intel x86 tipikal, loop berjalan pada satu iterasi per ~6 siklus pada CPU IvyBridge OP, berkat latensi penerusan toko yang merupakan bagian dari add
dengan tujuan memori (baca-modifikasi-tulis). 100k iterations * 6 cycles/iteration
adalah 600k siklus, yang mendominasi kontribusi dari paling banyak beberapa cache yang hilang (masing-masing ~200 siklus untuk kesalahan pengambilan kode yang mencegah instruksi lebih lanjut dikeluarkan hingga diselesaikan).
Eksekusi out-of-order dan store-forwarding sebagian besar harus menyembunyikan potensi cache miss saat mengakses stack (sebagai bagian dari call
instruksi).
Bahkan jika penghitung loop disimpan dalam register, 100k siklus itu banyak.
Panggilan ke usleep
mungkin atau mungkin tidak menghasilkan peralihan konteks. Jika ya, akan memakan waktu lebih lama daripada jika tidak.