Làm thế nào để sử dụng QueryPerformanceCounter?


97

Gần đây tôi đã quyết định rằng tôi cần phải thay đổi từ sử dụng mili giây sang micro giây cho lớp Timer của mình và sau một số nghiên cứu, tôi đã quyết định rằng QueryPerformanceCounter có lẽ là đặt cược an toàn nhất cho tôi. (Cảnh báo Boost::Posixrằng nó có thể không hoạt động trên Win32 API khiến tôi hơi khó chịu). Tuy nhiên, tôi không thực sự chắc chắn về cách thực hiện nó.

Những gì tôi đang làm là gọi bất kỳ GetTicks()hàm esque nào tôi đang sử dụng và gán nó cho startingTicksbiến Timer . Sau đó, để tìm khoảng thời gian đã qua, tôi chỉ cần trừ giá trị trả về của hàm startingTicksvà khi tôi đặt lại bộ đếm thời gian, tôi chỉ cần gọi lại hàm và gán startTicks cho nó. Thật không may, từ đoạn mã tôi đã thấy, nó không đơn giản như chỉ gọi QueryPerformanceCounter()và tôi không chắc những gì tôi phải chuyển như đối số của nó.


2
Tôi đã lấy các đoạn mã của Ramonster và tạo chúng thành một thư viện tại đây: gist.github.com/1153062 cho những người theo dõi.
rogerdpack

3
Gần đây, chúng tôi đã cập nhật tài liệu cho QueryPerformanceCounter và thêm thông tin bổ sung về cách sử dụng thích hợp và câu trả lời cho Câu hỏi thường gặp. Bạn có thể tìm thấy tài liệu cập nhật tại đây msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs

giống như đề cập đến __rdtsc , đó là những gì QueryPerformanceCounter sử dụng.
colin lamarre

Câu trả lời:


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

Chương trình này sẽ xuất ra một số gần 1000 (chế độ ngủ của windows không chính xác lắm, nhưng nó phải giống như 999).

Các StartCounter()chức năng ghi lại số tick hiệu suất truy cập có trong CounterStartbiến. Các GetCounter()hàm trả về số mili giây kể từ khi StartCounter()được gọi là cuối cùng như là một đôi, vì vậy nếu GetCounter()lợi nhuận 0,001 sau đó nó đã được khoảng 1 micro giây kể từ khi StartCounter()được gọi.

Nếu bạn muốn có bộ đếm thời gian sử dụng giây thì hãy thay đổi

PCFreq = double(li.QuadPart)/1000.0;

đến

PCFreq = double(li.QuadPart);

hoặc nếu bạn muốn micro giây thì hãy sử dụng

PCFreq = double(li.QuadPart)/1000000.0;

Nhưng thực sự đó là về sự tiện lợi vì nó trả về gấp đôi.


5
Chính xác, LARGE_INTEGER là gì?
Ẩn danh

5
đó là một loại cửa sổ, về cơ bản là một số nguyên 64 bit di động. Định nghĩa của nó phụ thuộc vào việc hệ thống đích có hỗ trợ số nguyên 64 bit hay không. Nếu hệ thống không hỗ trợ int 64 bit thì nó được định nghĩa là 2 int 32 bit, HighPart và LowPart. Nếu hệ thống hỗ trợ các int 64 bit thì đó là sự kết hợp giữa 2 int 32 bit và một int 64 bit được gọi là QuadPart.
Ramónster

8
Câu trả lời này thật là thiếu sót. QueryPerformanceCounter đọc một thanh ghi bộ đếm chu kỳ cụ thể của lõi và nếu luồng thực thi đã được lên lịch lại trên một lõi khác, hai phép đo từ QueryPerformanceCounter không chỉ kết hợp thời gian đã trôi qua mà thường là một delta cố định, lớn và khó xác định giữa hai thanh ghi lõi. Vì vậy - điều này chỉ hoạt động đáng tin cậy như đã trình bày nếu quy trình của bạn bị ràng buộc với một lõi cụ thể.
Tony Delroy

15
@TonyD: Tài liệu MSDN cho biết: 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).Mã này không có sai sót nặng, nhưng một số BIOS hoặc HAL.
Lucas

3
@TonyD: Tôi vừa mới xem xét vấn đề này một chút. Tôi đã thêm lời gọi sau vào StartCounterhàm: old_mask = SetThreadAffinityMask(GetCurrentThread,1);và sau đó đặt lại ở cuối SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;. Tôi hy vọng điều đó sẽ làm được thủ thuật. Điều này sẽ ngăn luồng của tôi bị lên lịch thành bất kỳ thứ gì ngoại trừ lõi CPU thứ nhất. (Rõ ràng chỉ là giải pháp cho môi trường thử nghiệm)
Lucas

19

Tôi sử dụng các định nghĩa sau:

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

Cách sử dụng (dấu ngoặc để ngăn định nghĩa lại):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

Đầu ra từ ví dụ sử dụng:

1.00003 sec
1.23407 sec

2

Giả sử bạn đang sử dụng Windows (nếu vậy, bạn nên gắn thẻ câu hỏi của mình như vậy!), Trên trang MSDN này, bạn có thể tìm thấy nguồn cho một HRTimerlớp C ++ đơn giản, hữu ích bao bọc các lệnh gọi hệ thống cần thiết để thực hiện điều gì đó rất gần với những gì bạn yêu cầu (sẽ dễ dàng thêm một GetTicks()phương thức vào đó, đặc biệt, để thực hiện chính xác những gì bạn yêu cầu).

Trên các nền tảng không phải Windows, không có chức năng QueryPerformanceCounter, vì vậy giải pháp sẽ không trực tiếp di động. Tuy nhiên, nếu bạn bọc nó trong một lớp như đã đề cập ở trên HRTimer, sẽ dễ dàng hơn trong việc thay đổi việc triển khai của lớp để sử dụng những gì mà nền tảng hiện tại thực sự có thể cung cấp (có thể thông qua Boost hoặc bất cứ điều gì!).


1

Tôi sẽ mở rộng câu hỏi này với một ví dụ về trình điều khiển NDIS về thời gian. Như người ta đã biết, KeQuerySystemTime (được bắt chước theo NdisGetCurrentSystemTime) có độ phân giải thấp trên mili giây và có một số quy trình như gói mạng hoặc IRP khác có thể cần dấu thời gian tốt hơn;

Ví dụ đơn giản như sau:

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

trong đó số chia là 10 ^ 3 hoặc 10 ^ 6 tùy thuộc vào độ phân giải được yêu cầu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.