วิธีใช้ QueryPerformanceCounter


97

เมื่อเร็ว ๆ นี้ฉันตัดสินใจว่าฉันจำเป็นต้องเปลี่ยนจากการใช้มิลลิวินาทีเป็นไมโครวินาทีสำหรับคลาส Timer ของฉันและหลังจากการวิจัยบางอย่างฉันตัดสินใจว่า QueryPerformanceCounter น่าจะเป็นทางออกที่ปลอดภัยที่สุดของฉัน (คำเตือนBoost::Posixว่าอาจไม่ทำงานบน Win32 API ทำให้ฉันผิดหวังเล็กน้อย) อย่างไรก็ตามฉันไม่แน่ใจว่าจะใช้งานอย่างไร

สิ่งที่ฉันกำลังทำคือเรียกGetTicks()ใช้ฟังก์ชัน esque ใด ๆ ที่ฉันใช้และกำหนดให้กับstartingTicksตัวแปรของตัวจับเวลา จากนั้นเพื่อหาระยะเวลาที่ผ่านไปฉันเพิ่งลบค่าส่งคืนของฟังก์ชันออกจากค่าstartingTicksและเมื่อฉันรีเซ็ตตัวจับเวลาฉันก็เรียกฟังก์ชันอีกครั้งและกำหนดค่าเริ่มต้นให้กับมัน น่าเสียดายที่จากรหัสที่ฉันเห็นมันไม่ง่ายเหมือนแค่การโทรQueryPerformanceCounter()และฉันไม่แน่ใจว่าฉันควรจะส่งผ่านอะไรไปเป็นข้อโต้แย้งของมัน


2
ฉันได้นำข้อมูลโค้ดของ Ramonster มาทำเป็นห้องสมุดที่นี่: gist.github.com/1153062สำหรับผู้ติดตาม
rogerdpack

3
เมื่อเร็ว ๆ นี้เราได้อัปเดตเอกสารสำหรับ QueryPerformanceCounter และเพิ่มข้อมูลเพิ่มเติมเกี่ยวกับการใช้งานที่เหมาะสมและคำตอบสำหรับ FAQ คุณสามารถค้นหาเอกสารฉบับปรับปรุงได้ที่นี่msdn.microsoft.com/en-us/library/windows/desktop/…
Ed Briggs

เช่นเดียวกับการพูดถึง__rdtscเป็นสิ่งที่ QueryPerformanceCounter ใช้
colin lamarre

คำตอบ:


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

โปรแกรมนี้ควรแสดงตัวเลขใกล้เคียงกับ 1000 (windows sleep ไม่แม่นยำขนาดนั้น แต่ควรเป็น 999)

StartCounter()ฟังก์ชั่นบันทึกจำนวนเห็บนับประสิทธิภาพการทำงานมีอยู่ในCounterStartตัวแปร GetCounter()ฟังก์ชันส่งกลับจำนวนมิลลิวินาทีนับตั้งแต่StartCounter()ถูกเรียกว่าที่ผ่านมาเป็นคู่ดังนั้นหากGetCounter()ผลตอบแทน 0.001 แล้วจะได้รับประมาณ 1 มิลลิตั้งแต่StartCounter()ถูกเรียกว่า

หากคุณต้องการให้ตัวจับเวลาใช้วินาทีแทนให้เปลี่ยน

PCFreq = double(li.QuadPart)/1000.0;

ถึง

PCFreq = double(li.QuadPart);

หรือถ้าคุณต้องการไมโครวินาทีให้ใช้

PCFreq = double(li.QuadPart)/1000000.0;

แต่จริงๆแล้วมันเกี่ยวกับความสะดวกสบายเพราะมันส่งคืนสองเท่า


5
LARGE_INTEGER คืออะไร
ไม่ระบุชื่อ

5
เป็นประเภท windows โดยทั่วไปเป็นจำนวนเต็ม 64 บิตแบบพกพา คำจำกัดความขึ้นอยู่กับว่าระบบเป้าหมายรองรับจำนวนเต็ม 64 บิตหรือไม่ หากระบบไม่รองรับ 64 บิต ints ระบบจะกำหนดเป็น 2 32 bit ints, HighPart และ LowPart หากระบบรองรับ 64 บิต ints จะเป็นการรวมกันระหว่าง 2 32 บิต ints และ 64 บิต int ที่เรียกว่า QuadPart
Ramónster

9
คำตอบนี้มีข้อบกพร่องอย่างมาก QueryPerformanceCounter อ่านรีจิสเตอร์ตัวนับวัฏจักรเฉพาะคอร์และถ้าเธรดของการดำเนินการถูกจัดตารางใหม่บนคอร์อื่นการวัดสองครั้งจาก QueryPerformanceCounter ไม่เพียง แต่รวมเวลาที่ผ่านไปเท่านั้น แต่มักจะระบุเดลต้าคงที่ขนาดใหญ่และยากที่จะระบุระหว่างการลงทะเบียนสองคอร์ ดังนั้น - สิ่งนี้จะทำงานได้อย่างน่าเชื่อถือตามที่นำเสนอหากกระบวนการของคุณถูกผูกไว้กับแกนที่เฉพาะเจาะจง
Tony Delroy

15
@TonyD: เอกสาร MSDN ระบุว่า: 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).รหัสนี้ไม่มีข้อบกพร่อง แต่มี BIOS หรือ HAL บางตัว
Lucas

4
@TonyD: ฉันเพิ่งดูเรื่องนี้อีกหน่อย ฉันจะเพิ่มสายต่อไปนี้ลงStartCounterฟังก์ชั่นและการตั้งค่ามันกลับมาที่สิ้นสุดแล้วold_mask = SetThreadAffinityMask(GetCurrentThread,1); SetThreadAffinityMask ( GetCurrentThread , old_mask ) ;ฉันหวังว่าจะทำเคล็ดลับ สิ่งนี้จะป้องกันไม่ให้เธรดของฉันถูกจัดตารางใหม่เป็นอะไรก็ได้ยกเว้นแกน CPU ตัวที่ 1 (ซึ่งเห็นได้ชัดว่าเป็นวิธีแก้ปัญหาสำหรับสภาพแวดล้อมการทดสอบเท่านั้น)
Lucas

20

ฉันใช้คำจำกัดความเหล่านี้:

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

การใช้งาน (วงเล็บเพื่อป้องกันการกำหนดใหม่):

TIMER_INIT

{
   TIMER_START
   Sleep(1000);
   TIMER_STOP
}

{
   TIMER_START
   Sleep(1234);
   TIMER_STOP
}

ผลลัพธ์จากตัวอย่างการใช้งาน:

1.00003 sec
1.23407 sec

2

สมมติว่าคุณใช้ Windows (ถ้าเป็นเช่นนั้นคุณควรแท็กคำถามของคุณเช่นนี้!) ในหน้า MSDN นี้คุณสามารถค้นหาแหล่งที่มาของHRTimerคลาส C ++ ที่เรียบง่ายและมีประโยชน์ซึ่งจะรวมการเรียกระบบที่จำเป็นเพื่อทำบางสิ่งที่ใกล้เคียงกับที่คุณต้องการ (มันจะง่ายต่อการเพิ่มGetTicks()วิธีการไปโดยเฉพาะอย่างยิ่งที่จะทำว่าสิ่งที่คุณต้องการ)

บนแพลตฟอร์มที่ไม่ใช่ Windows ไม่มีฟังก์ชัน QueryPerformanceCounter ดังนั้นโซลูชันจะไม่พกพาได้โดยตรง อย่างไรก็ตามหากคุณห่อในคลาสเช่นที่กล่าวมาข้างต้นHRTimerการเปลี่ยนการใช้งานของคลาสจะง่ายกว่าเพื่อใช้สิ่งที่แพลตฟอร์มปัจจุบันสามารถนำเสนอได้ (อาจจะผ่านทาง Boost หรืออะไรก็ได้!)


1

ฉันจะขยายคำถามนี้ด้วยตัวอย่างไดรเวอร์ NDIS ในการหาเวลา อย่างที่ทราบกันดีว่า KeQuerySystemTime (เลียนแบบภายใต้ NdisGetCurrentSystemTime) มีความละเอียดต่ำกว่ามิลลิวินาทีและมีกระบวนการบางอย่างเช่นแพ็กเก็ตเครือข่ายหรือ IRP อื่น ๆ ซึ่งอาจต้องมีการประทับเวลาที่ดีกว่า

ตัวอย่างง่าย ๆ :

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

โดยตัวหารคือ 10 ^ 3 หรือ 10 ^ 6 ขึ้นอยู่กับความละเอียดที่ต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.