Чи гарантовано 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). Однак на "звичайному" Linux я вважаю, що роздільна здатність gettimeofday()10us. Отже, він може переходити вперед і назад і час, відповідно, на основі процесів, що працюють у вашій системі. Це фактично робить відповідь на ваше запитання ні.

Вам слід шукати clock_gettime(CLOCK_MONOTONIC)інтервали часу. Він страждає від кількох менших проблем через такі речі, як багатоядерні системи та налаштування зовнішніх годинників.

Також загляньте у clock_getres()функцію.


1
clock_gettime присутній лише на найновішому Linux. інші системи мають тільки gettimeofday ()
vitaly.v.ch

3
@ vitaly.v.ch це POSIX, отже це не лише Linux і "ньюїст"? навіть дистрибутиви "Enterprise", такі як Red Hat Enterprise Linux, засновані на 2.6.18, який має clock_gettime, тому ні, не дуже новий .. (дата сторінки в RHEL - 2004-березень-12, так що це вже деякий час), якщо ви не говорячи про ДЕЙСТВИТЕЛЬНО ВИБУХНЕ СТАРІ ядра WTF, ти маєш на увазі?
Spudd86

clock_gettime був включений у POSIX у 2001 р., наскільки мені відомо на даний момент clock_gettime (), реалізований у Linux 2.6 та qnx. але Linux 2.4 в даний час використовується у багатьох виробничих системах.
vitaly.v.ch

Він був введений в 2001 році, але не обов’язковим до POSIX 2008.
R .. GitHub STOP HELPING ICE

2
З поширених запитань щодо Linux про lock_gettime (див. Відповідь Девіда Шлоснагле) "CLOCK_MONOTONIC ... регулюється частотою за допомогою NTP через adjtimex (). У майбутньому (я все ще намагаюся отримати патч) буде CLOCK_MONOTONIC_RAW, який не буде буде модифіковано взагалі і матиме лінійну кореляцію з апаратними лічильниками. " Я не думаю, що годинник _RAW коли-небудь потрапляв у ядро ​​(якщо він не був перейменований в _HR, але моє дослідження показує, що зусилля теж відмовляються).
Тоні Делрой,

41

Висока роздільна здатність, низький рівень накладних витрат для процесорів Intel

Якщо ви працюєте на апаратному забезпеченні Intel, ось як читати лічильник інструкцій процесора в режимі реального часу. Це покаже вам кількість циклів процесора, виконаних з часу завантаження процесора. Це, мабуть, найдрібніший лічильник, який ви можете отримати для вимірювання продуктивності.

Зверніть увагу, що це кількість циклів процесора. У Linux ви можете отримати швидкість процесора з / 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, і автоматично вибрати найкращий. Корисно завжди використовувати ядро ​​для хронометражу, якщо ви не впевнені, що TSC стабільний і монотонний.
CesarB

12
TSC на Core і вище платформ Intel синхронізується між кількома центральними процесорами та зростає з постійною частотою, незалежно від станів управління живленням. Див. Посібник розробника програмного забезпечення Intel, вип. 3 Розділ 18.10. Однак швидкість, з якою збільшується лічильник, не така, як частота процесора. TSC збільшується на "максимально дозволеній частоті платформи, яка дорівнює добутку масштабованої частоти шини та максимального дозволеного коефіцієнта шини" Посібник розробника програмного забезпечення Intel, вип. 3 Розділ 18.18.5. Ці значення ви отримуєте з регістрів процесора, специфічних для моделі (MSR).
sstock

7
Ви можете отримати масштабовану частоту шини та максимально дозволений коефіцієнт шини, запитуючи регістри (MSR) процесора наступним чином: Масштабована частота шини == MSR_FSB_FREQ [2: 0] id 0xCD, Максимальне дозволене відношення шини == MSR_PLATFORM_ID [12: 8] ідентифікатор 0x17. Зверніться до Intel SDM Vol.3 Додаток B.1, щоб інтерпретувати значення реєстру. Ви можете використовувати msr-інструменти в Linux для запитів реєстрів. kernel.org/pub/linux/utils/cpu/msr-tools
sstock

1
Чи не слід ваш код використовувати CPUIDзнову після першої RDTSCінструкції та перед виконанням коду, який тестується? В іншому випадку, що зупинити виконання тестового коду до / паралельно з першим RDTSCі, отже, недостатньо представленим у RDTSCдельті?
Тоні Делрой,

18

@ Бернар:

Я повинен визнати, що більша частина вашого прикладу пішла мені прямо над головою. Однак він компілюється і, здається, працює. Це безпечно для систем SMP або SpeedStep?

Це хороше питання ... Я думаю, що код добре. З практичної точки зору, ми використовуємо його в моїй компанії щодня, і ми працюємо на досить широкому спектрі коробок, все від 2-8 ядер. Звичайно, YMMV тощо, але це, здається, надійний і маловитратний (оскільки він не робить перехід контексту в системний простір) метод хронометражу.

Як правило, це працює:

  • оголосити блок коду асемблером (і нестійким, тому оптимізатор залишить його в спокої).
  • виконати інструкцію CPUID. На додаток до отримання деякої інформації про центральний процесор (з якою ми нічого не робимо), вона синхронізує буфер виконання центрального процесора, так що на таймінги не впливає порушення порядку.
  • виконати виконання rdtsc (читати позначку часу). Це отримує кількість машинних циклів, виконаних з моменту скидання процесора. Це 64-бітне значення, тому при поточній швидкості процесора воно буде обертатися приблизно кожні 194 роки або близько того. Цікаво, що в оригінальній довідці про Pentium вони зазначають, що вона обгортається приблизно кожні 5800 років або близько того.
  • останні пара рядків зберігають значення з регістрів у змінних hi і lo, і поміщають це у 64-бітове повертане значення.

Конкретні примітки:

  • виконання, що не працює в порядку, може призвести до неправильних результатів, тому ми виконуємо інструкцію "cpuid", яка на додаток до надання певної інформації про процесор також синхронізує будь-яке виконання інструкцій, що не працює.

  • Більшість ОС синхронізують лічильники на центральних процесорах, коли вони запускаються, тому відповідь хороша протягом декількох наносекунд.

  • Коментар до сплячого режиму, мабуть, відповідає дійсності, але на практиці вам, мабуть, байдуже до часу, що перетинає межі сплячого режиму.

  • щодо кроку швидкості: Нові процесори 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 Гц. У таких випадках вирішення часу буде менш точним.

Цю відповідь я отримав із вимірювання часу та таймерів високої роздільної здатності, частина I


6

У цій відповіді згадуються проблеми з регулюванням годинника. Як ваші проблеми із гарантуванням одиниць галочок, так і проблеми з часом, що регулюються, вирішені в C ++ 11 з <chrono>бібліотекою.

std::chrono::steady_clockГарантія годинника гарантовано не регулюється, і, крім того, вона буде рухатися з постійною швидкістю відносно реального часу, тому такі технології, як SpeedStep, не повинні впливати на неї.

Ви можете отримати безпечні одиниці, перетворившись на одну зі std::chrono::durationспеціалізацій, наприклад std::chrono::microseconds. При цьому типі немає двозначності щодо одиниць виміру, що використовуються значенням галочки. Однак майте на увазі, що годинник не обов’язково має таку роздільну здатність. Ви можете перетворити тривалість у аттосекунди, фактично не маючи годинника, який є точним.


4

З мого досвіду та з прочитаного в Інтернеті, відповідь: "Ні", це не гарантується. Це залежить від швидкості процесора, операційної системи, смаку Linux тощо.


3

Зчитування RDTSC не є надійним у системах SMP, оскільки кожен центральний процесор підтримує власний лічильник, і кожен лічильник не гарантується за рахунок синхронізації щодо іншого процесора.

Я можу запропонувати спробувати clock_gettime(CLOCK_REALTIME). Посібник posix вказує, що це слід застосовувати у всіх сумісних системах. Він може забезпечити кількість наносекунд, але ви, мабуть, захочете перевірити clock_getres(CLOCK_REALTIME)свою систему, щоб побачити, яка фактична роздільна здатність.


clock_getres(CLOCK_REALTIME)не дасть реального дозволу. Він завжди повертає «1 нс» (один наносекунд) , коли hrtimers доступні, перевірка include/linux/hrtimer.hфайлів для define HIGH_RES_NSEC 1(більше на stackoverflow.com/a/23044075/196561 )
osgx
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.