QueryPerformanceCounter nasıl kullanılır?


97

Kısa bir süre önce, Timer sınıfım için milisaniye kullanmaktan mikrosaniyeye geçmem gerektiğine karar verdim ve bazı araştırmalardan sonra QueryPerformanceCounter'ın muhtemelen en güvenli bahis olduğuna karar verdim. ( Boost::PosixWin32 API üzerinde çalışmayabileceğine dair uyarı beni biraz rahatsız etti). Ancak bunu nasıl uygulayacağımı gerçekten bilmiyorum.

Yaptığım şey, GetTicks()kullandığım esque işlevini çağırmak ve Timer startingTicksdeğişkenine atamak . Sonra geçen süreyi bulmak için fonksiyonun dönüş değerini 'den startingTicksçıkarıyorum ve zamanlayıcıyı sıfırladığımda sadece fonksiyonu tekrar çağırıyorum ve ona startTicks atıyorum. Ne yazık ki, gördüğüm koddan sadece aramak kadar basit QueryPerformanceCounter()değil ve argüman olarak neyi iletmem gerektiğinden emin değilim.


2
Ramonster'ın kod parçalarını aldım ve burada bir kitaplığa yerleştirdim: takipçiler için gist.github.com/1153062 .
rogerdpack

3
Kısa bir süre önce QueryPerformanceCounter belgelerini güncelledik ve uygun kullanım bilgileri ve SSS'nin yanıtlarını ekledik. Güncellenen belgeleri burada msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs

__rdtsc'den bahsetmek gibi , QueryPerformanceCounter bunu kullanır.
colin lamarre

Yanıtlar:


161
#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;
}

Bu program 1000'e yakın bir sayı vermelidir (Windows uyku o kadar doğru değildir, ancak 999 gibi olmalıdır).

StartCounter()İşlevi performans sayaç vardır keneler sayısını kaydeder CounterStartdeğişken. GetCounter()Fonksiyon itibaren milisaniye sayısını döndürür StartCounter()eğer öyleyse, son bir çift olarak adlandırılan GetCounter()bu yana döner 0.001 o zaman 1 mikrosaniye ilgili olmuştur StartCounter()çağrıldı.

Zamanlayıcının bunun yerine saniye kullanmasını istiyorsanız, değiştirin

PCFreq = double(li.QuadPart)/1000.0;

-e

PCFreq = double(li.QuadPart);

veya mikrosaniye istiyorsanız, o zaman kullanın

PCFreq = double(li.QuadPart)/1000000.0;

Ama bir çift döndürdüğü için gerçekten rahatlıkla ilgili.


5
Tam olarak, LARGE_INTEGER nedir?
Anonim

5
temelde taşınabilir bir 64 bit tam sayı olan bir Windows türüdür. Tanımı, hedef sistemin 64 bit tam sayıları destekleyip desteklemediğine bağlıdır. Sistem 64 bit girişi desteklemiyorsa, 2 32 bitlik, bir HighPart ve bir LowPart olarak tanımlanır. Sistem 64 bit girişi destekliyorsa, bu 2 32 bitlik giriş ile QuadPart adı verilen 64 bit int arasındaki bir birleşimdir.
Ramónster

9
Bu cevap çok kusurlu. QueryPerformanceCounter, çekirdeğe özgü bir döngü sayacı kaydını okur ve yürütme iş parçacığı başka bir çekirdekte yeniden planlandıysa, QueryPerformanceCounter'dan alınan iki ölçüm, yalnızca geçen süreyi değil, aynı zamanda iki çekirdek kaydı arasında genellikle sabit, büyük ve belirlenmesi zor bir delta içerir. Yani - bu yalnızca, süreciniz belirli bir çekirdeğe bağlıysa güvenilir bir şekilde çalışır.
Tony Delroy

15
@TonyD: MSDN dokümantasyonu şunu söylüyor: 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).Bu kod kötü bir şekilde kusurlu değil, ancak bazı BIOS veya HAL.
Lucas

4
@TonyD: Buna biraz daha baktım. Aşağıdaki çağrıyı StartCounterişleve ekledim : old_mask = SetThreadAffinityMask(GetCurrentThread,1);ve sonunda tekrar ayarladım SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Umarım bu hile yapar. Bu, iş parçacığımın 1. CPU çekirdeği dışında herhangi bir şeye yeniden planlanmasını engellemelidir. (Açıkçası bir test ortamı için sadece bir çözüm)
Lucas

20

Bu tanımları kullanıyorum:

/** 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;

Kullanım (yeniden tanımlamaları önlemek için parantezler):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Kullanım örneğinden çıktı:

1.00003 sec
1.23407 sec

2

Windows'ta olduğunuzu varsayarsak (öyleyse sorunuzu bu şekilde etiketlemelisiniz!), Bu MSDN sayfasındaHRTimer , ihtiyaç duyduğunuza çok yakın bir şey yapmak için gerekli sistem çağrılarını tamamlayan basit, kullanışlı bir C ++ sınıfının kaynağını bulabilirsiniz. ( GetTicks()Özellikle ihtiyacınız olanı tam olarak yapmak için ona bir yöntem eklemek kolay olacaktır ).

Windows olmayan platformlarda QueryPerformanceCounter işlevi yoktur, bu nedenle çözüm doğrudan taşınabilir olmayacaktır. Bununla birlikte, yukarıda bahsedilen gibi bir sınıfa yerleştirirseniz HRTimer, sınıfın uygulamasını mevcut platformun gerçekten sunabildiğini (belki Boost veya her neyse!) Kullanacak şekilde değiştirmek daha kolay olacaktır.


1

Bu soruyu zaman alma konusunda bir NDIS sürücü örneği ile genişleteceğim. Bilindiği gibi, KeQuerySystemTime (NdisGetCurrentSystemTime altında taklit edilen) milisaniyenin üzerinde düşük bir çözünürlüğe sahiptir ve ağ paketleri veya diğer IRP'ler gibi daha iyi bir zaman damgası gerektirebilecek bazı işlemler vardır;

Örnek de aynı derecede basit:

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

burada bölen gerekli çözünürlüğe bağlı olarak 10 ^ 3 veya 10 ^ 6'dır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.