ฟังก์ชันจับเวลาเพื่อให้เวลาเป็นนาโนวินาทีโดยใช้ C ++


101

ฉันต้องการคำนวณเวลาที่ API ใช้ในการส่งคืนค่า เวลาที่ใช้ในการดำเนินการดังกล่าวอยู่ในช่วงนาโนวินาที เนื่องจาก API เป็นคลาส / ฟังก์ชัน C ++ ฉันจึงใช้ timer.h เพื่อคำนวณค่าเดียวกัน:

  #include <ctime>
  #include <cstdio>

  using namespace std;

  int main(int argc, char** argv) {

      clock_t start;
      double diff;
      start = clock();
      diff = ( std::clock() - start ) / (double)CLOCKS_PER_SEC;
      cout<<"printf: "<< diff <<'\n';

      return 0;
  }

รหัสด้านบนให้เวลาเป็นวินาที ฉันจะได้รับสิ่งเดียวกันในนาโนวินาทีและแม่นยำมากขึ้นได้อย่างไร


รหัสด้านบนคำนวณเป็นวินาทีฉันต้องการได้รับคำตอบในนาโนวินาที ...
gagneet

จำเป็นต้องเพิ่มแพลตฟอร์มให้กับคำถาม (และควรอยู่ในชื่อเรื่องด้วย) เพื่อให้ได้คำตอบที่ดี
Patrick Johnmeyer

นอกจากนี้ในการหาเวลาเราจำเป็นต้องค้นหาปัญหาเกี่ยวกับ microbenchmarking (ซึ่งมีความซับซ้อนมาก) - การดำเนินการเพียงครั้งเดียวและการรับเวลาตั้งแต่เริ่มต้นและสิ้นสุดไม่น่าจะให้ความแม่นยำเพียงพอ
Blaisorblade

@Blaisorblade: โดยเฉพาะอย่างยิ่งเมื่อฉันได้ค้นพบในการทดสอบบางอย่างของฉันที่clock()ไม่เร็วเท่าที่ฉันคิด
Mooing Duck

คำตอบ:


83

สิ่งที่คนอื่นโพสต์เกี่ยวกับการเรียกใช้ฟังก์ชันซ้ำ ๆ ในลูปนั้นถูกต้อง

สำหรับลินุกซ์ (และ BSD) คุณต้องการที่จะใช้clock_gettime ()

#include <sys/time.h>

int main()
{
   timespec ts;
   // clock_gettime(CLOCK_MONOTONIC, &ts); // Works on FreeBSD
   clock_gettime(CLOCK_REALTIME, &ts); // Works on Linux
}

สำหรับหน้าต่างที่คุณต้องการใช้QueryPerformanceCounter และนี่คือข้อมูลเพิ่มเติมเกี่ยวกับQPC

เห็นได้ชัดว่ามีปัญหาที่ทราบเกี่ยวกับ QPC ในชิปเซ็ตบางตัวดังนั้นคุณอาจต้องการตรวจสอบให้แน่ใจว่าคุณไม่มีชิปเซ็ตเหล่านั้น นอกจากนี้บาง AMDs เป็น dual core ยังอาจก่อให้เกิดปัญหา ดูโพสต์ที่สองโดย sebbbi ซึ่งเขากล่าวว่า:

QueryPerformanceCounter () และ QueryPerformanceFrequency () ให้ความละเอียดที่ดีขึ้นเล็กน้อย แต่มีปัญหาที่แตกต่างกัน ตัวอย่างเช่นใน Windows XP ซีพียู AMD Athlon X2 ดูอัลคอร์ทั้งหมดจะส่งคืนพีซีของคอร์อย่างใดอย่างหนึ่ง "แบบสุ่ม" (บางครั้งพีซีจะกระโดดถอยหลังเล็กน้อย) เว้นแต่คุณจะติดตั้งแพ็คเกจไดรเวอร์ AMD dual core เป็นพิเศษเพื่อแก้ไขปัญหา เราไม่ได้สังเกตเห็นซีพียู dual + core อื่น ๆ ที่มีปัญหาคล้ายกัน (p4 dual, p4 ht, core2 dual, core2 quad, phenom quad)

แก้ไข 2013/07/16:

ดูเหมือนว่าจะมีข้อโต้แย้งเกี่ยวกับประสิทธิภาพของ QPC ในบางสถานการณ์ตามที่ระบุไว้ในhttp://msdn.microsoft.com/en-us/library/windows/desktop/ee417693(v=vs.85).aspx

... ในขณะที่ QueryPerformanceCounter และ QueryPerformanceFrequency โดยทั่วไปจะปรับสำหรับโปรเซสเซอร์หลายตัวข้อบกพร่องใน BIOS หรือไดรเวอร์อาจส่งผลให้รูทีนเหล่านี้คืนค่าที่แตกต่างกันเมื่อเธรดย้ายจากโปรเซสเซอร์หนึ่งไปยังอีกโปรเซสเซอร์หนึ่ง ...

อย่างไรก็ตามคำตอบ StackOverflow นี้https://stackoverflow.com/a/4588605/34329ระบุว่า QPC ควรทำงานได้ดีบน MS OS ใด ๆ หลังจาก Win XP service pack 2

บทความนี้แสดงให้เห็นว่า Windows 7 สามารถตรวจสอบได้ว่าโปรเซสเซอร์มี TSC ที่ไม่แปรผันหรือไม่และถอยกลับไปที่ตัวจับเวลาภายนอกหากไม่มี http://performancebydesign.blogspot.com/2012/03/high-resolution-clocks-and-timers-for.html การซิงโครไนซ์ระหว่างโปรเซสเซอร์ยังคงเป็นปัญหา

การอ่านละเอียดอื่น ๆ ที่เกี่ยวข้องกับตัวจับเวลา:

ดูความคิดเห็นสำหรับรายละเอียดเพิ่มเติม


1
ฉันเคยเห็นนาฬิกา TSC เอียงบนพีซี Xeon คู่รุ่นเก่า แต่ก็ไม่ได้แย่เท่าบน Athlon X2 ที่เปิดใช้งานทางลาดนาฬิกา C1 ด้วยการเพิ่มสัญญาณนาฬิกา C1 การรันคำสั่ง HLT จะทำให้นาฬิกาช้าลงทำให้ TSC บนคอร์ที่ไม่ได้ใช้งานเพิ่มขึ้นช้ากว่าบนคอร์ที่ใช้งานอยู่
bk1e

6
CLOCK_MONOTONIC ทำงานบน Linux เวอร์ชันที่ฉันมีให้ใช้งาน
เบอร์นาร์ด

1
@ เบอร์นาร์ด - ต้องเพิ่มใหม่ตั้งแต่ฉันดูสิ่งนี้ครั้งล่าสุด ขอบคุณสำหรับหัวขึ้น.
เสียใจ

3
ในความเป็นจริงคุณต้องใช้CLOCK_MONOTONIC_RAWถ้ามีเพื่อให้ได้เวลาของฮาร์ดแวร์ที่ไม่ได้ปรับโดย NTP

ตามที่กล่าวไว้ที่นี่การใช้งาน QPC อย่างถูกต้องไม่ใช้ตัวนับ TSC อย่างน้อยที่สุดก็เป็นที่รู้กันว่าไม่น่าเชื่อถือ: stackoverflow.com/q/510462/53974
Blaisorblade

69

คำตอบใหม่นี้ใช้<chrono>สิ่งอำนวยความสะดวกของ C ++ 11 แม้ว่าจะมีคำตอบอื่น ๆ ที่แสดงวิธีการใช้งาน<chrono>แต่ก็ไม่มีคำตอบใดแสดงวิธีใช้<chrono>กับRDTSCสิ่งอำนวยความสะดวกที่กล่าวถึงในคำตอบอื่น ๆ ที่นี่ ดังนั้นผมจึงคิดว่าฉันจะแสดงวิธีการใช้ด้วยRDTSC <chrono>นอกจากนี้ผมจะแสดงให้เห็นถึงวิธีการที่คุณสามารถ templatize รหัสการทดสอบบนนาฬิกาเพื่อให้คุณได้อย่างรวดเร็วสามารถสลับระหว่างRDTSCและระบบของคุณในตัวสิ่งอำนวยความสะดวกนาฬิกา (ซึ่งมีแนวโน้มที่จะต้องอยู่บนพื้นฐานclock(), และclock_gettime() / หรือQueryPerformanceCounter

โปรดทราบว่าRDTSCคำสั่งเป็นแบบ x86 โดยเฉพาะ QueryPerformanceCounterเป็น Windows เท่านั้น และclock_gettime()เป็น POSIX เท่านั้น ด้านล่างนี้ฉันขอแนะนำนาฬิกาใหม่สองนาฬิกา: std::chrono::high_resolution_clockและstd::chrono::system_clockซึ่งถ้าคุณสามารถสมมติว่า C ++ 11 เป็นแบบข้ามแพลตฟอร์ม

ขั้นแรกนี่คือวิธีสร้างนาฬิกาที่เข้ากันได้กับ C ++ 11 จากrdtscคำแนะนำการประกอบของ Intel ฉันจะเรียกมันว่าx::clock:

#include <chrono>

namespace x
{

struct clock
{
    typedef unsigned long long                 rep;
    typedef std::ratio<1, 2'800'000'000>       period; // My machine is 2.8 GHz
    typedef std::chrono::duration<rep, period> duration;
    typedef std::chrono::time_point<clock>     time_point;
    static const bool is_steady =              true;

    static time_point now() noexcept
    {
        unsigned lo, hi;
        asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
        return time_point(duration(static_cast<rep>(hi) << 32 | lo));
    }
};

}  // x

นาฬิกาทั้งหมดนี้นับรอบ CPU และเก็บไว้ในจำนวนเต็ม 64 บิตที่ไม่ได้ลงชื่อ คุณอาจต้องปรับแต่งไวยากรณ์ภาษาแอสเซมบลีสำหรับคอมไพเลอร์ของคุณ หรือคอมไพเลอร์ของคุณอาจเสนออินทรินซิคที่คุณสามารถใช้แทนได้ (เช่นnow() {return __rdtsc();})

ในการสร้างนาฬิกาคุณต้องให้มันเป็นตัวแทน (ประเภทการจัดเก็บ) คุณต้องระบุช่วงเวลานาฬิกาด้วยซึ่งต้องเป็นค่าคงที่ของเวลาคอมไพล์แม้ว่าเครื่องของคุณอาจเปลี่ยนความเร็วสัญญาณนาฬิกาในโหมดพลังงานที่แตกต่างกัน และจากสิ่งเหล่านี้คุณสามารถกำหนดระยะเวลาและจุดเวลาแบบ "ดั้งเดิม" ของนาฬิกาได้อย่างง่ายดายในแง่ของปัจจัยพื้นฐานเหล่านี้

หากสิ่งที่คุณต้องการทำคือแสดงจำนวนขีดของนาฬิกาไม่สำคัญว่าคุณจะให้ตัวเลขใดสำหรับช่วงเวลานาฬิกา ค่าคงที่นี้จะเข้ามาเล่นก็ต่อเมื่อคุณต้องการแปลงจำนวนเห็บนาฬิกาเป็นหน่วยเรียลไทม์เช่นนาโนวินาที และในกรณีนี้ยิ่งคุณสามารถระบุความเร็วสัญญาณนาฬิกาได้แม่นยำมากเท่าไหร่การแปลงเป็นนาโนวินาทีก็จะแม่นยำมากขึ้น (มิลลิวินาทีก็ตาม)

x::clockด้านล่างเป็นโค้ดตัวอย่างที่แสดงให้เห็นถึงวิธีการใช้งาน อันที่จริงฉันได้เทมเพลตโค้ดบนนาฬิกาแล้วเพราะฉันต้องการแสดงให้เห็นว่าคุณสามารถใช้นาฬิกาหลายแบบที่มีไวยากรณ์เดียวกันได้อย่างไร การทดสอบเฉพาะนี้แสดงให้เห็นว่าค่าใช้จ่ายในการวนซ้ำคืออะไรเมื่อเรียกใช้สิ่งที่คุณต้องการเวลาภายใต้ลูป:

#include <iostream>

template <class clock>
void
test_empty_loop()
{
    // Define real time units
    typedef std::chrono::duration<unsigned long long, std::pico> picoseconds;
    // or:
    // typedef std::chrono::nanoseconds nanoseconds;
    // Define double-based unit of clock tick
    typedef std::chrono::duration<double, typename clock::period> Cycle;
    using std::chrono::duration_cast;
    const int N = 100000000;
    // Do it
    auto t0 = clock::now();
    for (int j = 0; j < N; ++j)
        asm volatile("");
    auto t1 = clock::now();
    // Get the clock ticks per iteration
    auto ticks_per_iter = Cycle(t1-t0)/N;
    std::cout << ticks_per_iter.count() << " clock ticks per iteration\n";
    // Convert to real time units
    std::cout << duration_cast<picoseconds>(ticks_per_iter).count()
              << "ps per iteration\n";
}

สิ่งแรกที่โค้ดนี้ทำคือสร้างหน่วย "เรียลไทม์" เพื่อแสดงผลลัพธ์ฉันได้เลือก picoseconds แล้ว แต่คุณสามารถเลือกหน่วยใดก็ได้ที่คุณต้องการไม่ว่าจะเป็นอินทิกรัลหรือทศนิยมตาม ตัวอย่างเช่นมีstd::chrono::nanosecondsยูนิตสำเร็จรูปที่ฉันสามารถใช้ได้

เป็นอีกตัวอย่างหนึ่งที่ฉันต้องการพิมพ์จำนวนรอบนาฬิกาโดยเฉลี่ยต่อการวนซ้ำเป็นจุดลอยตัวดังนั้นฉันจึงสร้างช่วงเวลาอื่นขึ้นอยู่กับคู่ซึ่งมีหน่วยเดียวกับที่ขีดของนาฬิกาทำ (เรียกCycleในรหัส)

ลูปจะหมดเวลาโดยมีการโทรไปclock::now()ที่ด้านใดด้านหนึ่ง หากคุณต้องการตั้งชื่อประเภทที่ส่งกลับจากฟังก์ชันนี้คือ:

typename clock::time_point t0 = clock::now();

(ดังที่แสดงไว้อย่างชัดเจนในx::clockตัวอย่างและยังเป็นจริงของนาฬิกาที่ระบบให้มาด้วย)

ในการหาระยะเวลาในรูปของนาฬิกาจุดลอยตัวให้ทำเครื่องหมายเพียงหนึ่งลบสองจุดเวลาและเพื่อให้ได้ค่าการวนซ้ำให้หารระยะเวลานั้นด้วยจำนวนการวนซ้ำ

คุณสามารถรับการนับในช่วงเวลาใดก็ได้โดยใช้count()ฟังก์ชันสมาชิก สิ่งนี้ส่งกลับการแสดงภายใน ในที่สุดฉันก็ใช้std::chrono::duration_castเพื่อแปลงระยะเวลาCycleเป็นระยะเวลาpicosecondsและพิมพ์ออกมา

ในการใช้รหัสนี้ทำได้ง่าย:

int main()
{
    std::cout << "\nUsing rdtsc:\n";
    test_empty_loop<x::clock>();

    std::cout << "\nUsing std::chrono::high_resolution_clock:\n";
    test_empty_loop<std::chrono::high_resolution_clock>();

    std::cout << "\nUsing std::chrono::system_clock:\n";
    test_empty_loop<std::chrono::system_clock>();
}

ดังกล่าวข้างต้นผมออกกำลังกายโดยใช้การทดสอบของเราทำที่บ้านx::clockและเปรียบเทียบผลผู้ที่มีการใช้สองของนาฬิการะบบจัด: และstd::chrono::high_resolution_clock std::chrono::system_clockสำหรับฉันสิ่งนี้พิมพ์ออกมา:

Using rdtsc:
1.72632 clock ticks per iteration
616ps per iteration

Using std::chrono::high_resolution_clock:
0.620105 clock ticks per iteration
620ps per iteration

Using std::chrono::system_clock:
0.00062457 clock ticks per iteration
624ps per iteration

สิ่งนี้แสดงให้เห็นว่านาฬิกาแต่ละเรือนเหล่านี้มีช่วงเวลาเห็บที่แตกต่างกันเนื่องจากเห็บต่อการวนซ้ำจะแตกต่างกันอย่างมากสำหรับนาฬิกาแต่ละเรือน อย่างไรก็ตามเมื่อแปลงเป็นหน่วยเวลาที่ทราบ (เช่น picoseconds) ฉันจะได้ผลลัพธ์ที่เหมือนกันโดยประมาณสำหรับนาฬิกาแต่ละเรือน (ระยะทางของคุณอาจแตกต่างกันไป)

สังเกตว่ารหัสของฉันไม่มี "ค่าคงที่การแปลงเวทมนตร์" โดยสิ้นเชิง อันที่จริงมีตัวเลขวิเศษเพียงสองตัวในตัวอย่างทั้งหมด:

  1. x::clockความเร็วนาฬิกาของเครื่องของฉันเพื่อกำหนด
  2. จำนวนการทำซ้ำที่จะทดสอบ หากการเปลี่ยนตัวเลขนี้ทำให้ผลลัพธ์ของคุณแตกต่างกันไปมากคุณควรเพิ่มจำนวนการทำซ้ำให้สูงขึ้นหรือทำให้คอมพิวเตอร์ของคุณว่างเปล่าจากกระบวนการแข่งขันขณะทดสอบ

5
โดย "RDTSC is Intel-only" คุณหมายถึงสถาปัตยกรรม x86 และอนุพันธ์จริงๆใช่ไหม ชิป AMD, Cyrix, Transmeta x86 มีคำสั่งและโปรเซสเซอร์ Intel RISC และ ARM ไม่มี
Ben Voigt

1
@BenVoigt: +1 ใช่การแก้ไขของคุณค่อนข้างถูกต้องขอบคุณ
Howard Hinnant

1
การควบคุมปริมาณ CPU จะส่งผลต่อสิ่งนี้อย่างไร? ความเร็วสัญญาณนาฬิกาไม่เปลี่ยนแปลงตามโหลด CPU หรือไม่?
Tejas Kale

@TejasKale: สิ่งนี้อธิบายไว้ในคำตอบในสองย่อหน้าต่อเนื่องกันโดยเริ่มจาก "เพื่อสร้างนาฬิกาให้คุณ ... " โดยทั่วไปรหัสเวลาไม่ได้วัดการทำงานที่บล็อกเธรด (แต่ทำได้) ดังนั้นโดยทั่วไปแล้ว CPU ของคุณจะไม่เค้น แต่ถ้าคุณกำลังวัดรหัสที่เกี่ยวข้องกับการนอนหลับการล็อก mutex การรอแบบ condition_variable ฯลฯrdtscนาฬิกามีแนวโน้มที่จะมีการแปลงเป็นหน่วยอื่นที่ไม่ถูกต้อง เป็นความคิดที่ดีที่จะตั้งค่าการวัดของคุณเพื่อให้คุณสามารถเปลี่ยนและเปรียบเทียบนาฬิกาได้อย่างง่ายดาย (ดังแสดงในคำตอบนี้)
Howard Hinnant

28

ด้วยความแม่นยำระดับนั้นจะเป็นการดีกว่าที่จะใช้เหตุผลในการติ๊ก CPU มากกว่าในการเรียกระบบ เช่นนาฬิกา () และอย่าลืมว่าหากต้องใช้เวลามากกว่าหนึ่งนาโนวินาทีในการดำเนินการคำสั่ง ... การมีความแม่นยำระดับนาโนวินาทีนั้นเป็นไปไม่ได้เลยทีเดียว

ยัง สิ่งที่ต้องการที่เป็นการเริ่มต้นที่:

นี่คือรหัสจริงเพื่อดึงจำนวนเห็บนาฬิกา CPU 80x86 ที่ส่งผ่านไปตั้งแต่ CPU เริ่มทำงานครั้งล่าสุด มันจะทำงานบน Pentium ขึ้นไป (ไม่รองรับ 386/486) รหัสนี้เป็นรหัสเฉพาะของ MS Visual C ++ แต่อาจเป็นเรื่องง่ายมากที่จะย้ายไปยังสิ่งอื่นใดตราบเท่าที่รองรับการประกอบแบบอินไลน์

inline __int64 GetCpuClocks()
{

    // Counter
    struct { int32 low, high; } counter;

    // Use RDTSC instruction to get clocks count
    __asm push EAX
    __asm push EDX
    __asm __emit 0fh __asm __emit 031h // RDTSC
    __asm mov counter.low, EAX
    __asm mov counter.high, EDX
    __asm pop EDX
    __asm pop EAX

    // Return result
    return *(__int64 *)(&counter);

}

ฟังก์ชั่นนี้ยังมีข้อดีคือเร็วมาก - โดยปกติจะใช้ cpu ไม่เกิน 50 รอบในการดำเนินการ

การใช้ตัวเลขเวลา :
หากคุณต้องการแปลจำนวนนาฬิกาให้เป็นเวลาที่ผ่านไปจริงให้หารผลลัพธ์ด้วยความเร็วสัญญาณนาฬิกาของชิปของคุณ โปรดจำไว้ว่า GHz ที่ "จัดอันดับ" มีแนวโน้มที่จะแตกต่างจากความเร็วจริงของชิปของคุณเล็กน้อย ในการตรวจสอบความเร็วที่แท้จริงของชิปของคุณคุณสามารถใช้ยูทิลิตี้ที่ดีมากมายหรือเรียก Win32, QueryPerformanceFrequency ()


ขอบคุณสำหรับข้อมูลนี้มีประโยชน์ ฉันไม่ได้คิดถึงวงจรซีพียูในการคำนวณเวลาฉันคิดว่านั่นเป็นจุดที่ดีมากที่ต้องจำไว้ :-)
gagneet

4
การใช้ QueryPerformanceFrequency () เพื่อเปลี่ยนการนับ TSC เป็นเวลาที่ผ่านไปอาจไม่ได้ผล QueryPerformanceCounter () ใช้ HPET (High Precision Event Timer) บน Vista เมื่อมี ใช้ตัวจับเวลาการจัดการพลังงาน ACPI หากผู้ใช้เพิ่ม / USEPMTIMER เพื่อ boot.ini
bk1e

23

การทำเช่นนี้ได้อย่างถูกต้องคุณสามารถใช้หนึ่งในสองวิธีทั้งไปกับหรือRDTSC clock_gettime()อย่างที่สองเร็วกว่าประมาณ 2 เท่าและมีข้อดีคือให้เวลาสัมบูรณ์ที่เหมาะสม โปรดทราบว่าเพื่อRDTSCให้ทำงานได้อย่างถูกต้องคุณต้องใช้ตามที่ระบุไว้ (ความคิดเห็นอื่น ๆ ในหน้านี้มีข้อผิดพลาดและอาจให้ค่าเวลาที่ไม่ถูกต้องในโปรเซสเซอร์บางตัว)

inline uint64_t rdtsc()
{
    uint32_t lo, hi;
    __asm__ __volatile__ (
      "xorl %%eax, %%eax\n"
      "cpuid\n"
      "rdtsc\n"
      : "=a" (lo), "=d" (hi)
      :
      : "%ebx", "%ecx" );
    return (uint64_t)hi << 32 | lo;
}

และสำหรับ clock_gettime: (ฉันเลือกความละเอียดระดับไมโครวินาทีโดยพลการ)

#include <time.h>
#include <sys/timeb.h>
// needs -lrt (real-time lib)
// 1970-01-01 epoch UTC time, 1 mcs resolution (divide by 1M to get time_t)
uint64_t ClockGetTime()
{
    timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    return (uint64_t)ts.tv_sec * 1000000LL + (uint64_t)ts.tv_nsec / 1000LL;
}

เวลาและค่าที่ผลิต:

Absolute values:
rdtsc           = 4571567254267600
clock_gettime   = 1278605535506855

Processing time: (10000000 runs)
rdtsc           = 2292547353
clock_gettime   = 1031119636

22

ฉันใช้สิ่งต่อไปนี้เพื่อให้ได้ผลลัพธ์ที่ต้องการ:

#include <time.h>
#include <iostream>
using namespace std;

int main (int argc, char** argv)
{
    // reset the clock
    timespec tS;
    tS.tv_sec = 0;
    tS.tv_nsec = 0;
    clock_settime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    ...
    ... <code to check for the time to be put here>
    ...
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tS);
    cout << "Time taken is: " << tS.tv_sec << " " << tS.tv_nsec << endl;

    return 0;
}

2
ฉันลงคะแนนเนื่องจากพยายามใช้รหัสนี้ฉันต้องใช้ Google ก่อนเหตุใดจึงไม่กำหนด timespec จากนั้นฉันต้อง google POSIX คืออะไร ... และตามที่ฉันเข้าใจรหัสนี้ไม่เกี่ยวข้องกับผู้ใช้ Windows ที่จะยึดติดกับไลบรารีมาตรฐาน
Daniel Katz

8

สำหรับC ++ 11นี่คือ Wrapper แบบธรรมดา:

#include <iostream>
#include <chrono>

class Timer
{
public:
    Timer() : beg_(clock_::now()) {}
    void reset() { beg_ = clock_::now(); }
    double elapsed() const {
        return std::chrono::duration_cast<second_>
            (clock_::now() - beg_).count(); }

private:
    typedef std::chrono::high_resolution_clock clock_;
    typedef std::chrono::duration<double, std::ratio<1> > second_;
    std::chrono::time_point<clock_> beg_;
};

หรือสำหรับ C ++ 03 บน * nix

class Timer
{
public:
    Timer() { clock_gettime(CLOCK_REALTIME, &beg_); }

    double elapsed() {
        clock_gettime(CLOCK_REALTIME, &end_);
        return end_.tv_sec - beg_.tv_sec +
            (end_.tv_nsec - beg_.tv_nsec) / 1000000000.;
    }

    void reset() { clock_gettime(CLOCK_REALTIME, &beg_); }

private:
    timespec beg_, end_;
};

ตัวอย่างการใช้งาน:

int main()
{
    Timer tmr;
    double t = tmr.elapsed();
    std::cout << t << std::endl;

    tmr.reset();
    t = tmr.elapsed();
    std::cout << t << std::endl;
    return 0;
}

จากhttps://gist.github.com/gongzhitaao/7062087


5

โดยทั่วไปแล้วหากต้องการกำหนดเวลาที่ใช้ในการเรียกใช้ฟังก์ชันคุณจะต้องทำหลาย ๆ ครั้งมากกว่าหนึ่งครั้ง หากคุณเรียกใช้ฟังก์ชันของคุณเพียงครั้งเดียวและใช้เวลาสั้นมากในการทำงานคุณยังคงมีค่าใช้จ่ายในการเรียกใช้ฟังก์ชันจับเวลาจริง ๆ และคุณไม่รู้ว่าจะใช้เวลานานเท่าใด

ตัวอย่างเช่นหากคุณประเมินว่าฟังก์ชันของคุณอาจใช้เวลา 800 ns ในการทำงานให้เรียกใช้วนซ้ำสิบล้านครั้ง (ซึ่งจะใช้เวลาประมาณ 8 วินาที) แบ่งเวลาทั้งหมดสิบล้านเพื่อรับเวลาต่อการโทร


จริงฉันกำลังพยายามรับประสิทธิภาพของ api สำหรับการโทรเฉพาะ สำหรับการวิ่งแต่ละครั้งอาจให้เวลาที่แตกต่างกันซึ่งอาจส่งผลต่อกราฟที่ฉันสร้างขึ้นเพื่อการปรับปรุงประสิทธิภาพ ... ดังนั้นเวลาเป็นนาโนวินาที แต่ใช่นี่เป็นความคิดที่ดีจะพิจารณา
gagneet

5

คุณสามารถใช้ฟังก์ชันต่อไปนี้กับ gcc ที่ทำงานภายใต้โปรเซสเซอร์ x86:

unsigned long long rdtsc()
{
  #define rdtsc(low, high) \
         __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

  unsigned int low, high;
  rdtsc(low, high);
  return ((ulonglong)high << 32) | low;
}

ด้วย Digital Mars C ++:

unsigned long long rdtsc()
{
   _asm
   {
        rdtsc
   }
}

ซึ่งอ่านตัวจับเวลาประสิทธิภาพสูงบนชิป ฉันใช้สิ่งนี้เมื่อทำโปรไฟล์


2
สิ่งนี้มีประโยชน์ฉันจะตรวจสอบว่าโปรเซสเซอร์เป็น x86 หรือไม่เนื่องจากฉันใช้ apple mac สำหรับการทดลอง ... ขอบคุณ :-)
gagneet

1
ผู้ใช้ควรให้ค่าใดสูงและต่ำ เหตุใดคุณจึงกำหนดมาโครภายในเนื้อหาของฟังก์ชัน นอกจากนี้ ulonglong ซึ่งน่าจะเป็นพิมพ์ดีดที่ไม่ได้ลงนาม long long ไม่ใช่ประเภทมาตรฐาน ฉันต้องการใช้สิ่งนี้ แต่ไม่แน่ใจว่าอย่างไร)
Joseph Garvin

1
ความยาวที่ไม่ได้ลงนามไม่ใช่สิ่งที่ถูกต้องในการใช้ linux คุณอาจต้องการพิจารณาใช้ int แทนเนื่องจาก long และ long เป็นแบบ 64 บิตบน Linux 64 บิต
Marius

3
ปัจจุบันตัวนับ TSC มักไม่น่าเชื่อถือ: มันเปลี่ยนความเร็วของโปรเซสเซอร์หลายตัวเมื่อความถี่มีการเปลี่ยนแปลงและไม่สอดคล้องกันในคอร์ต่างๆดังนั้น TSC จึงไม่เติบโตเสมอไป
Blaisorblade

1
@ มาริอุส: ฉันใช้ความคิดเห็นของคุณโดยใช้unsigned intเป็นประเภทภายใน
Blaisorblade

3

หากคุณต้องการความแม่นยำรองลงมาคุณจะต้องใช้ส่วนขยายเฉพาะระบบและจะต้องตรวจสอบกับเอกสารสำหรับระบบปฏิบัติการ POSIX รองรับได้ถึงไมโครวินาทีด้วยgettimeofdayแต่ไม่มีอะไรแม่นยำไปกว่านี้เนื่องจากคอมพิวเตอร์ไม่มีความถี่สูงกว่า 1GHz

หากคุณกำลังใช้ Boost คุณสามารถตรวจสอบเพิ่ม :: posix_time


ต้องการเก็บรหัสแบบพกพาจะเห็นไลบรารีเพิ่มและตรวจสอบว่าฉันสามารถรวมรหัสนี้กับรหัสได้หรือไม่ ขอบคุณ :-)
gagneet

3

ฉันใช้รหัสบอร์แลนด์นี่คือรหัส ti_hund ให้ฉันเป็นจำนวนเชิงลบ แต่เวลาค่อนข้างดี

#include <dos.h>

void main() 
{
struct  time t;
int Hour,Min,Sec,Hun;
gettime(&t);
Hour=t.ti_hour;
Min=t.ti_min;
Sec=t.ti_sec;
Hun=t.ti_hund;
printf("Start time is: %2d:%02d:%02d.%02d\n",
   t.ti_hour, t.ti_min, t.ti_sec, t.ti_hund);
....
your code to time
...

// read the time here remove Hours and min if the time is in sec

gettime(&t);
printf("\nTid Hour:%d Min:%d Sec:%d  Hundreds:%d\n",t.ti_hour-Hour,
                             t.ti_min-Min,t.ti_sec-Sec,t.ti_hund-Hun);
printf("\n\nAlt Ferdig Press a Key\n\n");
getch();
} // end main

3

ใช้วิธีการของ Brock Adams กับคลาสง่ายๆ:

int get_cpu_ticks()
{
    LARGE_INTEGER ticks;
    QueryPerformanceFrequency(&ticks);
    return ticks.LowPart;
}

__int64 get_cpu_clocks()
{
    struct { int32 low, high; } counter;

    __asm cpuid
    __asm push EDX
    __asm rdtsc
    __asm mov counter.low, EAX
    __asm mov counter.high, EDX
    __asm pop EDX
    __asm pop EAX

    return *(__int64 *)(&counter);
}

class cbench
{
public:
    cbench(const char *desc_in) 
         : desc(strdup(desc_in)), start(get_cpu_clocks()) { }
    ~cbench()
    {
        printf("%s took: %.4f ms\n", desc, (float)(get_cpu_clocks()-start)/get_cpu_ticks());
        if(desc) free(desc);
    }
private:
    char *desc;
    __int64 start;
};

ตัวอย่างการใช้งาน:

int main()
{
    {
        cbench c("test");
        ... code ...
    }
    return 0;
}

ผลลัพธ์:

ใช้เวลาทดสอบ: 0.0002 ms

มีค่าใช้จ่ายในการเรียกใช้ฟังก์ชันบางอย่าง แต่ควรจะเร็วกว่าพอสมควร :)


3

คุณสามารถใช้Embedded Profiler (ฟรีสำหรับ Windows และ Linux) ซึ่งมีอินเทอร์เฟซสำหรับตัวจับเวลาหลายแพลตฟอร์ม (ในการนับรอบตัวประมวลผล) และสามารถให้จำนวนรอบต่อวินาที:

EProfilerTimer timer;
timer.Start();

... // Your code here

const uint64_t number_of_elapsed_cycles = timer.Stop();
const uint64_t nano_seconds_elapsed =
    mumber_of_elapsed_cycles / (double) timer.GetCyclesPerSecond() * 1000000000;

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


2

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


clock_gettime () ควรทำเคล็ดลับในตอนนี้ จะลองใช้แบบเดียวกันเพื่อจุดประสงค์ของฉัน ...
gagneet

2

คุณคิดยังไงเกี่ยวกับที่:

    int iceu_system_GetTimeNow(long long int *res)
    {
      static struct timespec buffer;
      // 
    #ifdef __CYGWIN__
      if (clock_gettime(CLOCK_REALTIME, &buffer))
        return 1;
    #else
      if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &buffer))
        return 1;
    #endif
      *res=(long long int)buffer.tv_sec * 1000000000LL + (long long int)buffer.tv_nsec;
      return 0;
    }

2

นี่คือตัวจับเวลาBoost ที่ดีที่ใช้งานได้ดี:

//Stopwatch.hpp

#ifndef STOPWATCH_HPP
#define STOPWATCH_HPP

//Boost
#include <boost/chrono.hpp>
//Std
#include <cstdint>

class Stopwatch
{
public:
    Stopwatch();
    virtual         ~Stopwatch();
    void            Restart();
    std::uint64_t   Get_elapsed_ns();
    std::uint64_t   Get_elapsed_us();
    std::uint64_t   Get_elapsed_ms();
    std::uint64_t   Get_elapsed_s();
private:
    boost::chrono::high_resolution_clock::time_point _start_time;
};

#endif // STOPWATCH_HPP


//Stopwatch.cpp

#include "Stopwatch.hpp"

Stopwatch::Stopwatch():
    _start_time(boost::chrono::high_resolution_clock::now()) {}

Stopwatch::~Stopwatch() {}

void Stopwatch::Restart()
{
    _start_time = boost::chrono::high_resolution_clock::now();
}

std::uint64_t Stopwatch::Get_elapsed_ns()
{
    boost::chrono::nanoseconds nano_s = boost::chrono::duration_cast<boost::chrono::nanoseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(nano_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_us()
{
    boost::chrono::microseconds micro_s = boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(micro_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_ms()
{
    boost::chrono::milliseconds milli_s = boost::chrono::duration_cast<boost::chrono::milliseconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(milli_s.count());
}

std::uint64_t Stopwatch::Get_elapsed_s()
{
    boost::chrono::seconds sec = boost::chrono::duration_cast<boost::chrono::seconds>(boost::chrono::high_resolution_clock::now() - _start_time);
    return static_cast<std::uint64_t>(sec.count());
}

2

การคัดลอกและวางโครงสร้างที่เรียบง่าย + การใช้งานที่ขี้เกียจ

หากแนวคิดคือการมีโครงสร้างแบบเรียบง่ายที่คุณสามารถใช้สำหรับการทดสอบอย่างรวดเร็วฉันขอแนะนำให้คุณคัดลอกและวางที่ใดก็ได้ในไฟล์ C ++ ของคุณหลังจาก#includeนั้น นี่เป็นอินสแตนซ์เดียวที่ฉันสละการจัดรูปแบบสไตล์ Allman

คุณสามารถปรับความแม่นยำในบรรทัดแรกของโครงสร้างได้อย่างง่ายดาย ค่าที่เป็นไปได้: nanoseconds, microseconds, milliseconds, seconds, หรือminuteshours

#include <chrono>
struct MeasureTime
{
    using precision = std::chrono::microseconds;
    std::vector<std::chrono::steady_clock::time_point> times;
    std::chrono::steady_clock::time_point oneLast;
    void p() {
        std::cout << "Mark " 
                << times.size()/2
                << ": " 
                << std::chrono::duration_cast<precision>(times.back() - oneLast).count() 
                << std::endl;
    }
    void m() {
        oneLast = times.back();
        times.push_back(std::chrono::steady_clock::now());
    }
    void t() {
        m();
        p();
        m();
    }
    MeasureTime() {
        times.push_back(std::chrono::steady_clock::now());
    }
};

การใช้งาน

MeasureTime m; // first time is already in memory
doFnc1();
m.t(); // Mark 1: next time, and print difference with previous mark
doFnc2();
m.t(); // Mark 2: next time, and print difference with previous mark
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.t(); // prints 'Mark 3: 123123' etc...

ผลลัพธ์เอาต์พุตมาตรฐาน

Mark 1: 123
Mark 2: 32
Mark 3: 433234

หากคุณต้องการสรุปหลังการดำเนินการ

หากคุณต้องการรายงานในภายหลังเนื่องจากตัวอย่างเช่นโค้ดของคุณระหว่างนั้นจะเขียนไปยังเอาต์พุตมาตรฐาน จากนั้นเพิ่มฟังก์ชันต่อไปนี้ในโครงสร้าง (ก่อน MeasureTime ()):

void s() { // summary
    int i = 0;
    std::chrono::steady_clock::time_point tprev;
    for(auto tcur : times)
    {
        if(i > 0)
        {
            std::cout << "Mark " << i << ": "
                    << std::chrono::duration_cast<precision>(tprev - tcur).count()
                    << std::endl;
        }
        tprev = tcur;
        ++i;
    }
}

ดังนั้นคุณสามารถใช้:

MeasureTime m;
doFnc1();
m.m();
doFnc2();
m.m();
doStuff = doMoreStuff();
andDoItAgain = doStuff.aoeuaoeu();
m.m();
m.s();

ซึ่งจะแสดงรายการเครื่องหมายทั้งหมดเหมือนเมื่อก่อน แต่หลังจากเรียกใช้โค้ดอื่นแล้ว โปรดทราบว่าคุณไม่ควรใช้ทั้งสองm.s()และm.t().


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