Gettimeofday () có được đảm bảo là ở độ phân giải micro giây không?


97

Tôi đang chuyển một trò chơi, vốn được viết cho Win32 API, sang Linux (tốt, chuyển cổng OS X của cổng Win32 sang Linux).

Tôi đã triển khai QueryPerformanceCounterbằng cách đưa ra uSeconds kể từ khi quá trình bắt đầu:

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

Điều này, cùng với QueryPerformanceFrequency()việc đưa ra tần số 1000000 không đổi, hoạt động tốt trên máy của tôi , mang lại cho tôi một biến 64-bit chứa uSecondstừ khi chương trình khởi động.

Vậy cái này có phải hàng xách tay không? Tôi không muốn phát hiện ra nó hoạt động khác đi nếu hạt nhân được biên dịch theo một cách nhất định hoặc bất cứ điều gì tương tự. Tuy nhiên, tôi hài lòng với việc nó không di động đối với một thứ gì đó khác ngoài Linux.

Câu trả lời:


57

Có lẽ. Nhưng bạn có những vấn đề lớn hơn. gettimeofday()có thể dẫn đến thời gian không chính xác nếu có các quy trình trên hệ thống của bạn thay đổi bộ đếm thời gian (tức là ntpd). Tuy nhiên, trên linux "bình thường", tôi tin rằng độ phân giải của gettimeofday()là 10us. Do đó, nó có thể nhảy tới, lui và thời gian dựa trên các quy trình đang chạy trên hệ thống của bạn. Điều này thực sự làm cho câu trả lời cho câu hỏi của bạn là không.

Bạn nên xem xét clock_gettime(CLOCK_MONOTONIC)các khoảng thời gian. Nó gặp phải một số vấn đề ít hơn do những thứ như hệ thống đa lõi và cài đặt đồng hồ bên ngoài.

Ngoài ra, hãy xem xét clock_getres()chức năng.


1
clock_gettime chỉ có trên Linux mới nhất. hệ thống khác chỉ có gettimeofday ()
importanty.v.ch

3
@ importanty.v.ch đó là POSIX vì vậy nó không chỉ dành cho Linux và 'newist'? ngay cả các bản phân phối 'Enterprise' như Red Hat Enterprise Linux cũng dựa trên 2.6.18 có clock_gettime nên không, không mới lắm .. (ngày sử dụng trong RHEL là 2004-Tháng Ba-12 nên nó đã xuất hiện được một thời gian) trừ khi bạn ý bạn là nói về thực sự đang giải phóng hạt nhân WTF?
Spudd86

clock_gettime đã được đưa vào POSIX vào năm 2001. theo như tôi biết hiện tại clock_gettime () được triển khai trong Linux 2.6 và qnx. nhưng linux 2.4 hiện đang được sử dụng trong nhiều hệ thống sản xuất.
importanty.v.ch

Nó được giới thiệu vào năm 2001, nhưng không bắt buộc cho đến POSIX 2008.
R .. GitHub DỪNG TRỢ GIÚP ICE

2
Từ Câu hỏi thường gặp về Linux cho lock_gettime (xem câu trả lời của David Schlosnagle) "CLOCK_MONOTONIC ... được NTP điều chỉnh tần số thông qua adjtimex (). Trong tương lai (tôi vẫn đang cố gắng tải bản vá) sẽ có CLOCK_MONOTONIC_RAW sẽ không được sửa đổi chút nào và sẽ có mối tương quan tuyến tính với bộ đếm phần cứng. " Tôi không nghĩ rằng đồng hồ _RAW đã từng đưa nó vào hạt nhân (trừ khi nó được đổi tên thành _HR, nhưng nghiên cứu của tôi cho thấy rằng những nỗ lực cũng bị bỏ rơi).
Tony Delroy

41

Độ phân giải cao, thời gian chi phí thấp cho bộ xử lý Intel

Nếu bạn đang sử dụng phần cứng Intel, đây là cách đọc bộ đếm lệnh thời gian thực của CPU. Nó sẽ cho bạn biết số chu kỳ CPU được thực thi kể từ khi bộ xử lý được khởi động. Đây có lẽ là bộ đếm chi tiết tốt nhất mà bạn có thể nhận được để đo hiệu suất.

Lưu ý rằng đây là số chu kỳ CPU. Trên linux, bạn có thể lấy tốc độ CPU từ / proc / cpuinfo và chia để lấy số giây. Chuyển đổi này thành một đôi khá tiện dụng.

Khi tôi chạy cái này trên hộp của mình, tôi nhận được

11867927879484732
11867927879692217
it took this long to call printf: 207485

Đây là hướng dẫn của nhà phát triển Intel cung cấp rất nhiều chi tiết.

#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
Lưu ý rằng TSC có thể không phải lúc nào cũng được đồng bộ hóa giữa các lõi, có thể dừng hoặc thay đổi tần số của nó khi bộ xử lý chuyển sang chế độ năng lượng thấp hơn (và bạn không có cách nào để biết nó đã làm như vậy) và nói chung không phải lúc nào cũng đáng tin cậy. Hạt nhân có thể phát hiện khi nào nó đáng tin cậy, phát hiện các lựa chọn thay thế khác như bộ đếm thời gian HPET và ACPI PM, và tự động chọn lựa chọn tốt nhất. Bạn nên luôn sử dụng hạt nhân để tính thời gian trừ khi bạn thực sự chắc chắn TSC là ổn định và đơn điệu.
CesarB

12
TSC trên nền tảng Core trở lên của Intel được đồng bộ hóa trên nhiều CPU tăng dần ở tần số không đổi, độc lập với trạng thái quản lý nguồn. Xem Sách hướng dẫn dành cho nhà phát triển phần mềm của Intel, Vol. 3 Mục 18.10. Tuy nhiên, tốc độ mà bộ đếm tăng lên không giống với tần số của CPU. TSC tăng lên ở “tần số được phân giải tối đa của nền tảng, bằng tích của tần số bus có thể mở rộng và tỷ lệ bus được phân giải tối đa” Sách hướng dẫn dành cho nhà phát triển phần mềm của Intel, Vol. 3 Mục 18.18.5. Bạn nhận được các giá trị đó từ các thanh ghi dành riêng cho mô hình của CPU (MSR).
sstock,

7
Bạn có thể có được tần số bus có thể mở rộng và tỷ lệ bus được phân giải tối đa bằng cách truy vấn các thanh ghi dành riêng cho mô hình của CPU (MSR) như sau: Tần số bus có thể mở rộng == MSR_FSB_FREQ [2: 0] id 0xCD, Tỷ lệ bus được phân giải tối đa == MSR_PLATFORM_ID [12: 8] id 0x17. Tham khảo Phụ lục B.1 của Intel SDM Vol.3 để diễn giải các giá trị thanh ghi. Bạn có thể sử dụng msr-tools trên Linux để truy vấn sổ đăng ký. kernel.org/pub/linux/utils/cpu/msr-tools
sstock,

1
Mã của bạn có nên sử dụng CPUIDlại sau lệnh đầu tiên RDTSCvà trước khi thực thi mã được đánh giá chuẩn không? Nếu không, điều gì để ngăn mã chuẩn được thực thi trước / song song-với mã đầu tiên RDTSCvà do đó được trình bày không đầy đủ trong RDTSCvùng đồng bằng?
Tony Delroy

18

@Bernard:

Tôi phải thừa nhận rằng, hầu hết các ví dụ của bạn đã đi thẳng vào đầu tôi. Mặc dù vậy, nó có biên dịch và dường như hoạt động. Điều này có an toàn cho hệ thống SMP hoặc SpeedStep không?

Đó là một câu hỏi hay ... Tôi nghĩ mã vẫn ổn. Từ quan điểm thực tế, chúng tôi sử dụng nó trong công ty của tôi hàng ngày và chúng tôi chạy trên một loạt các hộp, mọi thứ từ 2-8 lõi. Tất nhiên, YMMV, v.v., nhưng nó có vẻ là một phương pháp xác định thời gian đáng tin cậy và chi phí thấp (vì nó không làm cho ngữ cảnh chuyển thành không gian hệ thống).

Nói chung nó hoạt động như thế nào:

  • khai báo khối mã là trình hợp dịch (và dễ bay hơi, vì vậy trình tối ưu hóa sẽ để nó một mình).
  • thực hiện lệnh CPUID. Ngoài việc lấy một số thông tin CPU (mà chúng tôi không làm gì với), nó đồng bộ hóa bộ đệm thực thi của CPU để thời gian không bị ảnh hưởng bởi việc thực thi không theo thứ tự.
  • thực hiện việc thực thi rdtsc (đọc dấu thời gian). Thao tác này tìm nạp số chu kỳ máy được thực thi kể từ khi bộ xử lý được đặt lại. Đây là giá trị 64-bit, vì vậy với tốc độ CPU hiện tại, nó sẽ quấn quanh 194 năm hoặc lâu hơn. Điều thú vị là trong tài liệu tham khảo Pentium ban đầu, họ lưu ý rằng nó kết thúc khoảng 5800 năm hoặc lâu hơn.
  • vài dòng cuối cùng lưu trữ các giá trị từ các thanh ghi vào các biến hi và lo, và đưa giá trị đó vào giá trị trả về 64-bit.

Ghi chú cụ thể:

  • thực thi không theo thứ tự có thể gây ra kết quả không chính xác, vì vậy chúng tôi thực hiện lệnh "cpuid", ngoài việc cung cấp cho bạn một số thông tin về cpu cũng đồng bộ hóa bất kỳ thực thi lệnh không theo thứ tự nào.

  • Hầu hết các hệ điều hành đều đồng bộ hóa các bộ đếm trên CPU khi chúng khởi động, vì vậy câu trả lời là rất hữu ích trong vòng vài nano giây.

  • Nhận xét về chế độ ngủ đông có thể đúng, nhưng trong thực tế, bạn có thể không quan tâm đến thời gian qua các ranh giới ngủ đông.

  • liên quan đến bước tốc độ: Các CPU Intel mới hơn bù đắp cho các thay đổi tốc độ và trả về số lượng đã điều chỉnh. Tôi đã quét nhanh một số hộp trên mạng của chúng tôi và chỉ tìm thấy một hộp không có hộp đó: Pentium 3 đang chạy một số máy chủ cơ sở dữ liệu cũ. (đây là các hộp linux, vì vậy tôi đã kiểm tra bằng: grep const_tsc / proc / cpuinfo)

  • Tôi không chắc về CPU AMD, chúng tôi chủ yếu là một cửa hàng của Intel, mặc dù tôi biết một số chuyên gia hệ thống cấp thấp của chúng tôi đã đánh giá AMD.

Hy vọng điều này thỏa mãn sự tò mò của bạn, nó là một lĩnh vực lập trình thú vị và (IMHO) đang được nghiên cứu. Bạn biết khi Jeff và Joel nói về việc một lập trình viên có nên biết C hay không? Tôi đã hét vào mặt họ, "này, quên rằng công cụ C cấp cao ... trình lắp ráp là thứ bạn nên học nếu bạn muốn biết máy tính đang làm gì!"


1
... Nhân viên đã cố gắng kêu gọi mọi người ngừng sử dụng rdtsc trong một thời gian ... và thường tránh sử dụng nó trong nhân bởi vì nó không đáng tin cậy.
Spudd86

1
Để tham khảo, câu hỏi tôi đã hỏi (Trong một câu trả lời riêng - trước phần bình luận) là: "Tôi phải thừa nhận rằng, hầu hết ví dụ của bạn đã đi thẳng vào đầu tôi. Nó có biên dịch và có vẻ hoạt động. Tuy nhiên, điều này có an toàn cho Hệ thống SMP hay SpeedStep? "
Bernard



9

Vì vậy, nó nói rõ ràng là micro giây, nhưng nói rằng độ phân giải của đồng hồ hệ thống là không xác định. Tôi cho rằng độ phân giải trong bối cảnh này có nghĩa là số lượng nhỏ nhất mà nó sẽ được tăng lên như thế nào?

Cấu trúc dữ liệu được định nghĩa là có micro giây làm đơn vị đo lường, nhưng điều đó không có nghĩa là đồng hồ hoặc hệ điều hành thực sự có khả năng đo lường chính xác.

Giống như những người khác đã đề xuất, gettimeofday()là không tốt vì cài đặt thời gian có thể làm lệch đồng hồ và làm sai lệch tính toán của bạn. clock_gettime(CLOCK_MONOTONIC)là những gì bạn muốn và clock_getres()sẽ cho bạn biết độ chính xác của đồng hồ của bạn.


Vì vậy, điều gì sẽ xảy ra trong mã của bạn khi gettimeofday () nhảy về phía trước hoặc phía sau với tiết kiệm ánh sáng ban ngày?
mpez0

3
clock_gettime chỉ có trên Linux mới nhất. hệ thống khác chỉ có gettimeofday ()
importanty.v.ch

8

Độ phân giải thực tế của gettimeofday () phụ thuộc vào kiến ​​trúc phần cứng. Bộ vi xử lý Intel cũng như máy SPARC cung cấp bộ đếm thời gian có độ phân giải cao, đo từng micro giây. Các kiến ​​trúc phần cứng khác rơi vào bộ đếm thời gian của hệ thống, thường được đặt thành 100 Hz. Trong những trường hợp như vậy, việc phân giải thời gian sẽ kém chính xác hơn.

Tôi đã nhận được câu trả lời này từ Đo lường thời gian độ phân giải cao và bộ hẹn giờ, Phần I


6

Câu trả lời này đề cập đến các vấn đề với đồng hồ được điều chỉnh. Cả vấn đề của bạn về việc đảm bảo đơn vị đánh dấu và vấn đề với thời gian được điều chỉnh đều được giải quyết trong C ++ 11 với <chrono>thư viện.

Đồng hồ std::chrono::steady_clockđược đảm bảo không bị điều chỉnh và hơn nữa nó sẽ tiến với tốc độ không đổi so với thời gian thực, vì vậy các công nghệ như SpeedStep không được ảnh hưởng đến nó.

Bạn có thể nhận các đơn vị an toàn kiểu chữ bằng cách chuyển đổi sang một trong các std::chrono::durationchuyên ngành, chẳng hạn như std::chrono::microseconds. Với kiểu này, không có sự mơ hồ về các đơn vị được sử dụng bởi giá trị đánh dấu. Tuy nhiên, hãy nhớ rằng đồng hồ không nhất thiết phải có độ phân giải này. Bạn có thể chuyển đổi thời lượng thành đơn vị giây mà không thực sự có đồng hồ chính xác.


4

Từ kinh nghiệm của tôi và từ những gì tôi đã đọc trên internet, câu trả lời là "Không", nó không được đảm bảo. Nó phụ thuộc vào tốc độ CPU, hệ điều hành, hương vị của Linux, v.v.


3

Việc đọc RDTSC không đáng tin cậy trong các hệ thống SMP, vì mỗi CPU duy trì bộ đếm của riêng chúng và mỗi bộ đếm không được đảm bảo bằng cách đồng bộ hóa với CPU khác.

Tôi có thể đề nghị thử clock_gettime(CLOCK_REALTIME). Sổ tay hướng dẫn posix chỉ ra rằng điều này nên được thực hiện trên tất cả các hệ thống tuân thủ. Nó có thể cung cấp số lượng nano giây, nhưng có thể bạn sẽ muốn kiểm tra clock_getres(CLOCK_REALTIME)hệ thống của mình để xem độ phân giải thực tế là bao nhiêu.


clock_getres(CLOCK_REALTIME)sẽ không đưa ra độ phân giải thực sự. Nó luôn luôn trở về "1 ns" (một nano giây) khi hrtimers có sẵn, kiểm tra include/linux/hrtimer.htập tin cho define HIGH_RES_NSEC 1(hơn tại stackoverflow.com/a/23044075/196561 )
osgx
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.