Bagaimana cara menggunakan QueryPerformanceCounter?

97

Saya baru-baru ini memutuskan bahwa saya perlu mengubah dari menggunakan milidetik ke mikrodetik untuk kelas Timer saya, dan setelah beberapa penelitian, saya memutuskan bahwa QueryPerformanceCounter mungkin adalah taruhan teraman saya. (Peringatan Boost::Posixbahwa itu mungkin tidak berfungsi pada Win32 API membuat saya pergi sedikit). Namun, saya tidak begitu yakin bagaimana menerapkannya.

Yang saya lakukan adalah memanggil GetTicks()fungsi esque apa pun yang saya gunakan dan menugaskannya ke startingTicksvariabel Timer . Kemudian untuk menemukan jumlah waktu yang berlalu, saya hanya mengurangi nilai kembalian fungsi dari startingTicks, dan ketika saya mengatur ulang pengatur waktu, saya hanya memanggil fungsi itu lagi dan menetapkan startingTicks ke sana. Sayangnya, dari kode yang saya lihat tidak sesederhana hanya menelepon QueryPerformanceCounter(), dan saya tidak yakin apa yang harus saya sampaikan sebagai argumennya.

Anonim
sumber
2
Saya telah mengambil potongan kode Ramonster dan membuatnya menjadi perpustakaan di sini: gist.github.com/1153062 untuk pengikut.
rogerdpack
3
Kami baru saja memperbarui dokumentasi untuk QueryPerformanceCounter, dan menambahkan informasi tambahan tentang penggunaan yang tepat, dan jawaban untuk FAQ. Anda dapat menemukan dokumentasi yang diperbarui di sini msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs
hanya ingin menyebutkan __rdtsc , itulah yang digunakan QueryPerformanceCounter.
colin lamarre

Jawaban:

159
#include <windows.h>

double PCFreq = 0.0;
__int64 CounterStart = 0;

void StartCounter()
{
    LARGE_INTEGER li;
    if(!QueryPerformanceFrequency(&li))
    cout << "QueryPerformanceFrequency failed!\n";

    PCFreq = double(li.QuadPart)/1000.0;

    QueryPerformanceCounter(&li);
    CounterStart = li.QuadPart;
}
double GetCounter()
{
    LARGE_INTEGER li;
    QueryPerformanceCounter(&li);
    return double(li.QuadPart-CounterStart)/PCFreq;
}

int main()
{
    StartCounter();
    Sleep(1000);
    cout << GetCounter() <<"\n";
    return 0;
}

Program ini harus mengeluarkan angka mendekati 1000 (windows sleep tidak terlalu akurat, tapi seharusnya seperti 999).

The StartCounter()Fungsi mencatat jumlah kutu counter kinerja memiliki dalam CounterStartvariabel. The GetCounter()fungsi mengembalikan jumlah milidetik sejak StartCounter()terakhir disebut sebagai ganda, jadi jika GetCounter()kembali 0.001 maka itu sudah sekitar 1 mikrodetik sejak StartCounter()dipanggil.

Jika Anda ingin menggunakan pengatur waktu, gunakan detik, lalu ubah

PCFreq = double(li.QuadPart)/1000.0;

untuk

PCFreq = double(li.QuadPart);

atau jika Anda ingin mikrodetik kemudian gunakan

PCFreq = double(li.QuadPart)/1000000.0;

Tapi sebenarnya ini tentang kenyamanan karena mengembalikan ganda.

Ramónster
sumber
5
Tepatnya, apakah LARGE_INTEGER itu?
Anonim
5
Ini adalah jenis windows, pada dasarnya integer 64 bit portabel. Definisi itu tergantung pada apakah sistem target mendukung 64 bit integer atau tidak. Jika sistem tidak mendukung 64 bit int, maka itu didefinisikan sebagai 2 int 32 bit, HighPart dan LowPart. Jika sistem mendukung 64 bit ints maka itu adalah gabungan antara 2 int 32 bit dan int 64 bit yang disebut QuadPart.
Ramónster
8
Jawaban ini sangat salah. QueryPerformanceCounter membaca register penghitung siklus spesifik inti, dan jika rangkaian eksekusi telah dijadwalkan ulang pada inti lain, dua pengukuran dari QueryPerformanceCounter tidak hanya menggabungkan waktu yang telah berlalu, tetapi seringkali delta tetap, besar dan sulit untuk menunjukkan antara dua register inti. Jadi - ini hanya berfungsi dengan andal seperti yang disajikan jika proses Anda terikat pada inti tertentu.
Tony Delroy
15
@TonyD: Dokumentasi MSDN mengatakan: On a multiprocessor computer, it should not matter which processor is called. However, you can get different results on different processors due to bugs in the basic input/output system (BIOS) or the hardware abstraction layer (HAL).Kode ini tidak terlalu cacat, tetapi beberapa BIOS atau HAL.
Lucas
3
@TonyD: Saya baru saja melihat ini sedikit lagi. Saya menambahkan panggilan berikut ke dalam StartCounterfungsi: old_mask = SetThreadAffinityMask(GetCurrentThread,1);dan kemudian mengaturnya kembali di akhir SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Saya harap itu akan berhasil. Ini seharusnya mencegah utas saya dijadwalkan ulang ke apa pun kecuali inti CPU pertama. (Yang jelas hanya solusi untuk lingkungan pengujian)
Lucas
19

Saya menggunakan definisi ini:

/** Use to init the clock */
#define TIMER_INIT \
    LARGE_INTEGER frequency; \
    LARGE_INTEGER t1,t2; \
    double elapsedTime; \
    QueryPerformanceFrequency(&frequency);


/** Use to start the performance timer */
#define TIMER_START QueryPerformanceCounter(&t1);

/** Use to stop the performance timer and output the result to the standard stream. Less verbose than \c TIMER_STOP_VERBOSE */
#define TIMER_STOP \
    QueryPerformanceCounter(&t2); \
    elapsedTime=(float)(t2.QuadPart-t1.QuadPart)/frequency.QuadPart; \
    std::wcout<<elapsedTime<<L" sec"<<endl;

Penggunaan (tanda kurung untuk mencegah definisi ulang):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Output dari contoh penggunaan:

1.00003 sec
1.23407 sec
lubang angin
sumber
2

Dengan asumsi Anda menggunakan Windows (jika demikian, Anda harus menandai pertanyaan Anda seperti itu!), Pada halaman MSDN ini Anda dapat menemukan sumber untuk HRTimerkelas C ++ sederhana dan berguna yang membungkus panggilan sistem yang diperlukan untuk melakukan sesuatu yang sangat dekat dengan apa yang Anda butuhkan (itu akan mudah untuk menambahkan GetTicks()metode untuk itu, khususnya, untuk melakukan persis apa yang Anda butuhkan).

Pada platform non-Windows, tidak ada fungsi QueryPerformanceCounter, jadi solusinya tidak akan langsung portabel. Namun, jika Anda membungkusnya dalam kelas seperti yang disebutkan di atas HRTimer, akan lebih mudah untuk mengubah implementasi kelas untuk menggunakan apa yang memang dapat ditawarkan platform saat ini (mungkin melalui Boost atau apa pun!).

Alex Martelli
sumber
1

Saya akan memperluas pertanyaan ini dengan contoh driver NDIS tentang mendapatkan waktu. Seperti yang diketahui, KeQuerySystemTime (meniru di bawah NdisGetCurrentSystemTime) memiliki resolusi rendah di atas milidetik, dan ada beberapa proses seperti paket jaringan atau IRP lain yang mungkin memerlukan stempel waktu yang lebih baik;

Contohnya sesederhana:

LONG_INTEGER data, frequency;
LONGLONG diff;
data = KeQueryPerformanceCounter((LARGE_INTEGER *)&frequency)
diff = data.QuadPart / (Frequency.QuadPart/$divisor)

dengan pembagi adalah 10 ^ 3, atau 10 ^ 6 tergantung resolusi yang diperlukan.

kagali-san
sumber