gettimeofday () รับประกันความละเอียดระดับไมโครวินาทีหรือไม่


97

ฉันกำลังพอร์ตเกมซึ่งเดิมเขียนขึ้นสำหรับ Win32 API ไปยัง Linux (ดีคือพอร์ต OS X ของพอร์ต Win32 ไปยัง Linux)

ฉันได้ดำเนินการQueryPerformanceCounterโดยให้ uSeconds ตั้งแต่เริ่มกระบวนการ:

BOOL QueryPerformanceCounter(LARGE_INTEGER* performanceCount)
{
    gettimeofday(&currentTimeVal, NULL);
    performanceCount->QuadPart = (currentTimeVal.tv_sec - startTimeVal.tv_sec);
    performanceCount->QuadPart *= (1000 * 1000);
    performanceCount->QuadPart += (currentTimeVal.tv_usec - startTimeVal.tv_usec);

    return true;
}

สิ่งนี้ควบคู่ไปกับการQueryPerformanceFrequency()ให้ความถี่คงที่ 1000000 ทำงานได้ดีบนเครื่องของฉันทำให้ฉันมีตัวแปร 64 บิตที่มีuSecondsตั้งแต่เริ่มต้นโปรแกรม

ดังนั้นนี้เป็นแบบพกพา? ฉันไม่ต้องการที่จะค้นพบว่ามันทำงานแตกต่างออกไปหากเคอร์เนลถูกคอมไพล์ด้วยวิธีใดวิธีหนึ่งหรืออะไรทำนองนั้น อย่างไรก็ตามฉันสบายดีที่ไม่สามารถพกพาไปยังสิ่งอื่นที่ไม่ใช่ Linux ได้

คำตอบ:


57

อาจจะ. แต่คุณมีปัญหาที่ใหญ่กว่า gettimeofday()อาจส่งผลให้การกำหนดเวลาไม่ถูกต้องหากมีกระบวนการในระบบของคุณที่เปลี่ยนตัวจับเวลา (เช่น ntpd) สำหรับลินุกซ์ "ปกติ" ฉันเชื่อว่าความละเอียดgettimeofday()คือ 10us มันสามารถข้ามไปข้างหน้าและถอยหลังและเวลาได้ดังนั้นขึ้นอยู่กับกระบวนการที่ทำงานบนระบบของคุณ สิ่งนี้ทำให้ตอบคำถามของคุณได้อย่างมีประสิทธิภาพ

คุณควรมองหาclock_gettime(CLOCK_MONOTONIC)ช่วงเวลา มีปัญหาน้อยกว่าหลายประการเนื่องจากสิ่งต่างๆเช่นระบบมัลติคอร์และการตั้งค่านาฬิกาภายนอก

นอกจากนี้ให้ดูที่clock_getres()ฟังก์ชัน


1
clock_gettime มีอยู่ใน Linux ใหม่ล่าสุดเท่านั้น ระบบอื่นมีเฉพาะ gettimeofday ()
vitaly.v.ch

3
@ vitaly.v.ch เป็น POSIX ดังนั้นจึงไม่ใช่เฉพาะ Linux และ 'newist'? แม้แต่ 'Enterprise' distros เช่น Red Hat Enterprise Linux ก็ใช้ 2.6.18 ซึ่งมี clock_gettime ดังนั้นไม่ใช่ไม่ใช่เรื่องใหม่มาก .. (วันที่ของ manpage ใน RHEL คือ 2004-12 มีนาคม -12 ดังนั้นจึงเป็นเวลาประมาณหนึ่ง) เว้นแต่คุณจะ พูดถึงเมล็ดเก่าที่แตกสลายจริงๆ WTF คุณหมายถึง?
Spudd86

clock_gettime รวมอยู่ใน POSIX ในปี 2544 เท่าที่ฉันรู้ในปัจจุบัน clock_gettime () ใช้งานใน Linux 2.6 และ qnx แต่ปัจจุบัน linux 2.4 ถูกใช้ในระบบการผลิตจำนวนมาก
vitaly.v.ch

เปิดตัวในปี 2544 แต่ไม่บังคับจนถึง POSIX 2008
R .. GitHub STOP HELPING ICE

2
จากคำถามที่พบบ่อยของ Linux สำหรับ lock_gettime (ดูคำตอบของ David Schlosnagle) "CLOCK_MONOTONIC ... ถูกปรับความถี่โดย NTP ผ่าน adjtimex () ในอนาคต (ฉันยังคงพยายามดึงแพตช์เข้ามา) จะมี CLOCK_MONOTONIC_RAW ที่จะไม่ ได้รับการแก้ไขเลยและจะมีความสัมพันธ์เชิงเส้นกับตัวนับฮาร์ดแวร์ " ฉันไม่คิดว่านาฬิกา _RAW เคยสร้างมันลงในเคอร์เนล (เว้นแต่จะเปลี่ยนชื่อเป็น _HR แต่งานวิจัยของฉันชี้ให้เห็นว่ามีการละทิ้งความพยายามเช่นกัน)
Tony Delroy

41

ความละเอียดสูงระยะเวลาค่าโสหุ้ยต่ำสำหรับโปรเซสเซอร์ Intel

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

โปรดทราบว่านี่คือจำนวนรอบของ CPU บน linux คุณสามารถรับความเร็ว CPU จาก / proc / cpuinfo และหารเพื่อรับจำนวนวินาที การแปลงค่านี้เป็นสองเท่านั้นค่อนข้างมีประโยชน์

เมื่อฉันเรียกใช้สิ่งนี้บนกล่องฉันจะได้รับ

11867927879484732
11867927879692217
it took this long to call printf: 207485

นี่คือคู่มือสำหรับนักพัฒนา Intelที่ให้รายละเอียดมากมาย

#include <stdio.h>
#include <stdint.h>

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

main()
{
    unsigned long long x;
    unsigned long long y;
    x = rdtsc();
    printf("%lld\n",x);
    y = rdtsc();
    printf("%lld\n",y);
    printf("it took this long to call printf: %lld\n",y-x);
}

11
โปรดทราบว่า TSC อาจไม่ซิงโครไนซ์ระหว่างคอร์เสมอไปอาจหยุดหรือเปลี่ยนความถี่เมื่อโปรเซสเซอร์เข้าสู่โหมดพลังงานต่ำกว่า (และคุณไม่มีทางรู้ได้เลย) และโดยทั่วไปแล้วไม่น่าเชื่อถือเสมอไป เคอร์เนลสามารถตรวจจับได้เมื่อเชื่อถือได้ตรวจจับทางเลือกอื่น ๆ เช่น HPET และ ACPI PM timer และเลือกตัวจับเวลาที่ดีที่สุดโดยอัตโนมัติ เป็นความคิดที่ดีที่จะใช้เคอร์เนลเพื่อกำหนดเวลาเสมอเว้นแต่คุณจะแน่ใจจริงๆว่า TSC เสถียรและเป็นแบบโมโนโทนิค
CesarB

12
TSC บน Core และสูงกว่าแพลตฟอร์ม Intel จะซิงโครไนซ์กับ CPU หลายตัวและเพิ่มขึ้นที่ความถี่คงที่โดยไม่ขึ้นกับสถานะการจัดการพลังงาน โปรดดูคู่มือสำหรับนักพัฒนาซอฟต์แวร์ Intel ฉบับที่ 3 ส่วน 18.10. อย่างไรก็ตามอัตราการเพิ่มขึ้นของตัวนับจะไม่เหมือนกับความถี่ของ CPU TSC จะเพิ่มขึ้นที่“ ความถี่ที่แก้ไขสูงสุดของแพลตฟอร์มซึ่งเท่ากับผลคูณของความถี่บัสที่ปรับขนาดได้และอัตราส่วนบัสที่แก้ไขได้สูงสุด” คู่มือสำหรับนักพัฒนาซอฟต์แวร์ Intel ฉบับที่ 3 ส่วน 18.18.5. คุณได้รับค่าเหล่านั้นจากการลงทะเบียนเฉพาะรุ่นของ CPU (MSRs)
sstock

7
คุณสามารถรับความถี่บัสที่ปรับขนาดได้และอัตราส่วนบัสที่แก้ไขได้สูงสุดโดยการสอบถามการลงทะเบียนเฉพาะรุ่นของ CPU (MSR) ดังต่อไปนี้: ความถี่บัสที่ปรับขนาดได้ == MSR_FSB_FREQ [2: 0] id 0xCD, อัตราส่วนบัสที่แก้ไขได้สูงสุด == MSR_PLATFORM_ID [12: 8] รหัส 0x17 ดู Intel SDM Vol.3 Appendix B.1 เพื่อตีความค่า register คุณสามารถใช้ msr-tools บน Linux เพื่อค้นหารีจิสเตอร์ kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
ไม่ควรใช้รหัสของคุณCPUIDอีกครั้งหลังจากRDTSCคำสั่งแรกและก่อนที่จะเรียกใช้รหัสที่ถูกเปรียบเทียบ? มิฉะนั้นจะต้องทำอย่างไรเพื่อหยุดการทำงานของโค้ดที่ถูกเปรียบเทียบก่อน / ในแบบคู่ขนานกับรหัสแรกRDTSCและส่งผลให้มีการนำเสนอน้อยกว่าในRDTSCเดลต้า
Tony Delroy

18

@ เบอร์นาร์ด:

ฉันต้องยอมรับว่าตัวอย่างส่วนใหญ่ของคุณตรงเข้ามาในหัวของฉัน มันรวบรวมและดูเหมือนว่าจะใช้งานได้ สิ่งนี้ปลอดภัยสำหรับระบบ SMP หรือ SpeedStep หรือไม่?

นั่นเป็นคำถามที่ดี ... ฉันคิดว่ารหัสก็โอเค จากมุมมองที่ใช้งานได้จริงเราใช้มันใน บริษัท ของฉันทุกวันและเราทำงานบนกล่องที่หลากหลายทุกอย่างตั้งแต่ 2-8 คอร์ แน่นอนว่า YMMV และอื่น ๆ แต่ดูเหมือนว่าจะเป็นวิธีการกำหนดเวลาที่เชื่อถือได้และมีค่าใช้จ่ายต่ำ (เพราะไม่ได้เปลี่ยนบริบทเป็นพื้นที่ระบบ)

โดยทั่วไปวิธีการทำงานคือ:

  • ประกาศบล็อกของรหัสที่จะเป็นแอสเซมเบลอร์ (และระเหยได้ดังนั้นเครื่องมือเพิ่มประสิทธิภาพจะปล่อยให้อยู่คนเดียว)
  • ดำเนินการคำสั่ง CPUID นอกเหนือจากการรับข้อมูล CPU บางอย่าง (ซึ่งเราไม่ได้ทำอะไรเลย) มันจะซิงโครไนซ์บัฟเฟอร์การดำเนินการของ CPU เพื่อให้การกำหนดเวลาไม่ได้รับผลกระทบจากการดำเนินการนอกลำดับ
  • เรียกใช้การดำเนินการ rdtsc (อ่านการประทับเวลา) สิ่งนี้ดึงจำนวนรอบเครื่องที่ดำเนินการนับตั้งแต่รีเซ็ตโปรเซสเซอร์ นี่คือค่า 64 บิตดังนั้นด้วยความเร็วของ CPU ในปัจจุบันจะมีการพันรอบทุกๆ 194 ปีหรือมากกว่านั้น ที่น่าสนใจคือในการอ้างอิง Pentium ดั้งเดิมพวกเขาสังเกตว่ามันครอบคลุมทุก ๆ 5800 ปีหรือมากกว่านั้น
  • สองสามบรรทัดสุดท้ายเก็บค่าจากรีจิสเตอร์ลงในตัวแปร hi และ lo และใส่ลงในค่าส่งคืน 64 บิต

หมายเหตุเฉพาะ:

  • การดำเนินการนอกคำสั่งอาจทำให้ได้ผลลัพธ์ที่ไม่ถูกต้องดังนั้นเราจึงดำเนินการคำสั่ง "cpuid" ซึ่งนอกจากจะให้ข้อมูลบางอย่างเกี่ยวกับซีพียูแล้วยังซิงโครไนซ์การดำเนินการคำสั่งนอกคำสั่ง

  • ระบบปฏิบัติการส่วนใหญ่จะซิงโครไนซ์ตัวนับบนซีพียูเมื่อเริ่มต้นดังนั้นคำตอบจึงดีภายในไม่กี่วินาทีนาโนวินาที

  • ความคิดเห็นเกี่ยวกับการจำศีลอาจเป็นความจริง แต่ในทางปฏิบัติคุณอาจไม่สนใจเกี่ยวกับการกำหนดเวลาข้ามขอบเขตการจำศีล

  • เกี่ยวกับ speedstep: ซีพียู Intel รุ่นใหม่จะชดเชยการเปลี่ยนแปลงความเร็วและส่งคืนจำนวนที่ปรับแล้ว ฉันทำการสแกนอย่างรวดเร็วในบางกล่องบนเครือข่ายของเราและพบเพียงกล่องเดียวที่ไม่มี: Pentium 3 ที่ใช้เซิร์ฟเวอร์ฐานข้อมูลเก่า (นี่คือกล่อง linux ดังนั้นฉันจึงตรวจสอบด้วย: grep constant_tsc / proc / cpuinfo)

  • ฉันไม่แน่ใจเกี่ยวกับซีพียู AMD เราเป็นร้านค้าของ Intel เป็นหลักแม้ว่าฉันจะรู้ว่าผู้เชี่ยวชาญด้านระบบระดับต่ำของเราทำการประเมิน AMD

หวังว่าสิ่งนี้จะตอบสนองความอยากรู้อยากเห็นของคุณมันเป็นพื้นที่ที่น่าสนใจและ (IMHO) ที่อยู่ภายใต้การศึกษาของการเขียนโปรแกรม คุณรู้ไหมว่าเมื่อเจฟฟ์และโจเอลพูดถึงโปรแกรมเมอร์ควรรู้จัก C หรือไม่? ฉันตะโกนใส่พวกเขาว่า "เดี๋ยวก่อนลืมไปว่า C ระดับสูง ... แอสเซมเบลอร์คือสิ่งที่คุณควรเรียนรู้หากคุณต้องการรู้ว่าคอมพิวเตอร์กำลังทำอะไรอยู่!"


1
... คนเคอร์เนลพยายามให้คนเลิกใช้ rdtsc มาระยะหนึ่งแล้ว ... และโดยทั่วไปจะหลีกเลี่ยงการใช้ในเคอร์เนลเพราะมันไม่น่าเชื่อถือ
Spudd86

1
สำหรับการอ้างอิงคำถามที่ฉันถาม (ในการตอบกลับแยกกัน - ก่อนแสดงความคิดเห็น) คือ: "ฉันต้องยอมรับว่าตัวอย่างส่วนใหญ่ของคุณตรงไปตรงมาในหัวของฉันมันรวบรวมและดูเหมือนว่าจะใช้ได้ผลหรือไม่สิ่งนี้ปลอดภัยสำหรับ ระบบ SMP หรือ SpeedStep? "
เบอร์นาร์ด



9

ดังนั้นจึงบอกว่าไมโครวินาทีอย่างชัดเจน แต่บอกว่าความละเอียดของนาฬิการะบบไม่ได้ระบุไว้ ฉันคิดว่าความละเอียดในบริบทนี้หมายถึงจำนวนเงินที่น้อยที่สุดที่จะเพิ่มขึ้นได้อย่างไร?

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

เช่นเดียวกับที่คนอื่น ๆ แนะนำgettimeofday()ไม่ดีเพราะการตั้งเวลาอาจทำให้นาฬิกาเอียงและทำให้การคำนวณของคุณไม่ดี clock_gettime(CLOCK_MONOTONIC)คือสิ่งที่คุณต้องการและclock_getres()จะบอกคุณถึงความแม่นยำของนาฬิกา


จะเกิดอะไรขึ้นในรหัสของคุณเมื่อ gettimeofday () กระโดดไปข้างหน้าหรือข้างหลังด้วยการออมแสง?
mpez0

3
clock_gettime มีอยู่ใน Linux ใหม่ล่าสุดเท่านั้น ระบบอื่นมีเฉพาะ gettimeofday ()
vitaly.v.ch

8

ความละเอียดที่แท้จริงของ gettimeofday () ขึ้นอยู่กับสถาปัตยกรรมฮาร์ดแวร์ โปรเซสเซอร์ Intel และเครื่อง SPARC มีตัวจับเวลาความละเอียดสูงที่วัดระดับไมโครวินาที สถาปัตยกรรมฮาร์ดแวร์อื่น ๆ ถอยกลับไปที่ตัวจับเวลาของระบบซึ่งโดยทั่วไปจะตั้งค่าไว้ที่ 100 Hz ในกรณีเช่นนี้การแก้ไขเวลาจะแม่นยำน้อยลง

ฉันได้รับคำตอบนี้จากการวัดเวลาและตัวจับเวลาความละเอียดสูงตอนที่ 1


6

คำตอบนี้กล่าวถึงปัญหาเกี่ยวกับการปรับนาฬิกา ทั้งปัญหาของคุณในการรับประกันหน่วยเห็บและปัญหาเกี่ยวกับเวลาที่ปรับเปลี่ยนได้รับการแก้ไขใน C ++ 11 ด้วยไฟล์<chrono>ไลบรารี

นาฬิกา std::chrono::steady_clockรับประกันจะไม่ถูกปรับและนอกจากนี้จะเลื่อนไปในอัตราคงที่เมื่อเทียบกับเวลาจริงดังนั้นเทคโนโลยีอย่าง SpeedStep จะต้องไม่ส่งผลกระทบต่อ

คุณสามารถรับหน่วย typesafe ได้โดยการแปลงเป็นหนึ่งในstd::chrono::durationความเชี่ยวชาญเช่นstd::chrono::microseconds. ด้วยประเภทนี้ไม่มีความคลุมเครือเกี่ยวกับหน่วยที่ใช้โดยค่าติ๊ก อย่างไรก็ตามโปรดทราบว่านาฬิกาไม่จำเป็นต้องมีความละเอียดเท่านี้ คุณสามารถแปลงระยะเวลาเป็นวินาทีโดยไม่ต้องมีนาฬิกาที่เที่ยงตรง


4

จากประสบการณ์ของฉันและจากสิ่งที่ฉันได้อ่านในอินเทอร์เน็ตคำตอบคือ "ไม่" ไม่รับประกัน ขึ้นอยู่กับความเร็วของ CPU ระบบปฏิบัติการรสชาติของลินุกซ์ ฯลฯ


3

การอ่าน RDTSC ไม่น่าเชื่อถือในระบบ SMP เนื่องจาก CPU แต่ละตัวจะมีตัวนับของตัวเองและตัวนับแต่ละตัวจะไม่ได้รับการประกันโดยซิงโครไนซ์กับ CPU อื่น

clock_gettime(CLOCK_REALTIME)ผมอาจจะแนะนำให้คุณลอง คู่มือ posix ระบุว่าควรนำสิ่งนี้ไปใช้กับระบบที่เข้ากันได้ทั้งหมด สามารถนับจำนวนนาโนวินาทีได้ แต่คุณอาจต้องการตรวจสอบclock_getres(CLOCK_REALTIME)ระบบของคุณเพื่อดูว่าความละเอียดที่แท้จริงคือเท่าใด


clock_getres(CLOCK_REALTIME)จะไม่ให้ความละเอียดที่แท้จริง มันจะส่งคืน "1 ns" (หนึ่งนาโนวินาที) เสมอเมื่อมี hrtimers ให้ตรวจสอบinclude/linux/hrtimer.hไฟล์define HIGH_RES_NSEC 1(เพิ่มเติมที่stackoverflow.com/a/23044075/196561 )
osgx
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.