2013-11-12 4 views
13

Я пишу код C для измерения количества тактовых циклов, необходимых для получения семафора. Я использую rdtsc, и прежде чем делать измерения на семафоре, я вызываю rdtsc два раза подряд, чтобы измерить накладные расходы. Я повторяю это много раз, в for-loop, а затем использую среднее значение как служебные данные rdtsc. Правильно ли это, прежде всего, использовать среднее значение? Тем не менее, большая проблема заключается в том, что иногда я получаю отрицательные значения для накладных расходов (не обязательно усредненные, но, по крайней мере, частичные внутри цикла for). Это также влияет на последовательный расчет количества циклов процессора, необходимых для операции sem_wait(), которая иногда также оказывается отрицательной. Если то, что я написал, неясно, вот часть кода, над которым я работаю. Почему я получаю такие отрицательные значения?неправильные измерения тактового цикла с rdtsc

#include <semaphore.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <inttypes.h> 

static inline uint64_t get_cycles() 
{ 
    uint64_t t; 
    __asm volatile ("rdtsc" : "=A"(t)); 
    return t; 
} 

int num_measures = 10; 

int main() 
{ 
    int i, value, res1, res2; 
    uint64_t c1, c2; 
    int tsccost, tot, a; 

    tot=0;  

    for(i=0; i<num_measures; i++) 
    {  
     c1 = get_cycles(); 
     c2 = get_cycles(); 

     tsccost=(int)(c2-c1); 


     if(tsccost<0) 
     { 
     printf("#### ERROR!!! "); 
     printf("rdtsc took %d clock cycles\n", tsccost); 
     return 1; 
     } 
     tot = tot+tsccost; 
    } 

    tsccost=tot/num_measures; 
    printf("rdtsc takes on average: %d clock cycles\n", tsccost);  

    return EXIT_SUCCESS; 
} 
+0

См http://stackoverflow.com/questions/3388134/rdtsc-accuracy-across-cpu-cores – nos

+1

'__asm ​​энергозависимой (" RDTSC ":" = A "(t));' является проблематичным (или неожиданным?) В GCC (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=21249). Ограничение '= A' означает' rax' в x86_64, а не 'edx: eax'. SHL 'rdx' by 32 и OR в' rax', или SHLD 'rdx' слева при смещении в битах' rax' справа. –

ответ

0

В условиях теплового и простоя дросселирования, мыши-движения и сетевого трафика прерываний, все, что делает с GPU, и все другие накладные расходы, что современная система многоядерный может поглотить без любого большого ухода, я думаю, ваш единственный разумный курс для этого состоит в том, чтобы накапливать несколько тысяч отдельных образцов и просто бросать выбросы, прежде чем принимать медианную или среднюю (а не статистику, но я рискну, что здесь не будет иметь большого значения).

Я думаю, что все, что вы делаете, чтобы устранить шум запущенной системы, будет искажать результаты намного хуже, чем просто принимать то, что вы никогда не сможете надежно предсказать, сколько времени займет что-нибудь чтобы закончить эти дни.

+0

Вы не выбрасываете выбросы, вы просто берете самое низкое значение многих тысяч 1000 прогонов. Это правильно. – Johan

44

Когда Intel впервые изобрела TSC, он измерил циклы процессора. Из-за различных возможностей управления питанием «циклы в секунду» не являются постоянными; поэтому TSC изначально был полезен для измерения производительности кода (и плохо для измерения пройденного времени).

Для лучшего или худшего; тогда у процессоров не было слишком много управления питанием, часто CPU работали с фиксированными «циклами в секунду» в любом случае. Некоторые программисты получили неправильную идею и неправильно использовали TSC для измерения времени, а не циклов. Позже (когда использование функций управления питанием стало более распространенным) эти люди злоупотребляли TSC, чтобы измерить время, скулящее по всем проблемам, вызванным их неправильным использованием. Производители процессоров (начиная с AMD) изменили TSC, поэтому он измеряет время, а не циклы (что делает его сломанным для измерения производительности кода, но верно для измерения пройденного времени). Это вызвало путаницу (программному обеспечению было сложно определить, что фактически измеряет TSC), поэтому немного позже AMD добавила флаг CPU TSC к CPUID, так что, если этот флаг установлен, программисты знают, что TSC сломан (для измерения циклов) или фиксированных (для измерения времени).

Intel последовала за AMD и изменила поведение своих TSC, чтобы также измерить время, а также приняла флаг AMD TSC Invariant.

Это дает 4 различных случая:

  • меры TSC как время, так и производительность (циклов в секунду постоянна)

  • меры TSC производительность не время

  • ТСК измеряет время и не но не использует флаг «TSC Invariant», чтобы сказать так

  • TSC меры ti я и не производительность и делает использование флага «ТСК» Инвариантным сказать так (самые современные процессоры)

В тех случаях, когда меры TSC времени, для измерения производительности/циклов правильно, вы должны использовать счетчики мониторинга производительности. К сожалению, счетчики контроля производительности различны для разных процессоров (для конкретной модели) и требуют доступа к MSR (привилегированный код). Это делает невозможным применение приложений для измерения «циклов».

Также обратите внимание, что если TSC измеряет время, вы не можете знать, какой временной масштаб он возвращает (сколько наносекунд в «притворном цикле»), не используя какой-либо другой источник времени для определения коэффициента масштабирования.

Вторая проблема заключается в том, что для многопроцессорных систем большинство операционных систем сосут. Правильный способ ОС для обработки TSC - запретить приложениям использовать его напрямую (установив флаг TSD в CR4, чтобы команда RDTSC вызывала исключение). Это предотвращает различные уязвимости безопасности (временные побочные каналы). Он также позволяет ОС эмулировать TSC и гарантировать, что он вернет правильный результат. Например, когда приложение использует инструкцию RDTSC и вызывает исключение, обработчик исключений ОС может найти правильную «глобальную метку времени» для возврата.

Конечно, разные ЦП имеют свой собственный TSC. Это означает, что если приложение напрямую использует TSC, они получают разные значения для разных ЦП. Чтобы помочь людям справиться с неполадками ОС для устранения проблемы (путем эмуляции RDTSC, как и следовало ожидать); AMD добавила инструкцию RDTSCP, которая возвращает TSC и «идентификатор процессора» (Intel закончила работу с инструкцией RDTSCP). Приложение, работающее на сломанной ОС, может использовать «идентификатор процессора» для обнаружения, когда они работают на другом процессоре с последнего времени; и таким образом (используя команду RDTSCP) они могут знать, когда «elapsed = TSC - previous_TSC» дает действительный результат. Однако; «Идентификатор процессора», возвращаемый этой инструкцией, является просто значением в MSR, и ОС должна установить это значение для каждого CPU на что-то другое, иначе RDTSCP скажет, что «идентификатор процессора» равен нулю для всех процессоров.

В принципе; если процессоры поддерживают команду RDTSCP, и если ОС правильно установила «идентификатор процессора» (используя MSR); то инструкция RDTSCP может помочь приложениям узнать, когда у них получилось плохое «прошедшее время» (но это никак не связано с исправлением или недопущением плохих результатов).

So; коротко сократить длинную историю, если вы хотите получить точное измерение производительности, вы в основном завинчиваетесь. Лучшее, на что вы можете надеяться, это точное измерение времени; но только в некоторых случаях (например, при запуске на однопроцессорной машине или «закреплении» на конкретном процессоре или при использовании RDTSCP на ОС, которые правильно настроили его, пока вы обнаруживаете и отбрасываете недопустимые значения).

Конечно, даже тогда вы получите изворотливые измерения из-за таких вещей, как IRQ.По этой причине; лучше всего запускать код много раз в цикле и отбрасывать любые результаты, которые намного выше других результатов.

Наконец, если вы действительно хотите сделать это правильно, вы должны измерить накладные расходы. Для этого вы бы определили, сколько времени потребуется, чтобы ничего не делать (только инструкция RDTSC/RDTSCP, в то же время отбрасывая изворотливые измерения); затем вычесть издержки измерения из результатов «измерения чего-то». Это дает вам более точную оценку времени, которое «что-то» на самом деле занимает.

Примечание. Если вы можете выкопать копию Руководства по системному программированию Intel от того, когда был выпущен Pentium (в середине 1990-х годов - не уверен, что он доступен в Интернете, я архивировал копии с 1980-х годов), вы обнаружите, что Intel документировала счетчик временных меток как нечто, что «может использоваться для мониторинга и идентификации относительного времени появления событий процессора». Они гарантировали, что (за исключением 64-битного обтекания) он будет монотонно увеличиваться (но не то, что он будет увеличиваться с фиксированной скоростью), и что он займет минимум 10 лет, прежде чем он обернется. Последняя редакция руководства документирует счетчик времени с более подробной информацией о том, что для более старых процессоров (P6, Pentium M, более старый Pentium 4) счетчик временных меток «увеличивается с каждым внутренним тактовым циклом процессора» и что «Intel (r) Переходы технологии SpeedStep (r) могут влиять на часы процессора »; и что более новые процессоры (более новый Pentium 4, Core Solo, Core Duo, Core 2, Atom) TSC увеличивается с постоянной скоростью (и что это «архитектурное поведение движется вперед»). По сути, с самого начала это был (переменный) «счетчик внутреннего цикла» для использования для отметки времени (а не счетчик времени, который будет использоваться для отслеживания времени «настенных часов»), и это поведение изменилось вскоре после 2000 год (на основе даты выпуска Pentium 4).

+0

Брендан, отличный ответ. Можете ли вы добавить к нему некоторые ссылки? – osgx

+0

@Brendan: Фактически, в современных процессорах Intel TSC должен рассчитываться на той же частоте, независимо от тактовой частоты, состояния мощности или используемого ядра. –

+2

Я бы назвал это по-другому: AMD и Intel поняли, что высокоточный низкоуровневый источник времени более полезен, чем счетчик циклов. В современных процессорах аппаратные счетчики производительности могут это сделать, поэтому вам не нужен 'rdtsc'. И вы можете измерять события, отличные от циклов для микробизнеса. Также обратите внимание, что некоторые ранние CPU TSC с постоянной скоростью останавливали TSC при выполнении команды 'hlt', что делало ее непригодной для использования в качестве источника времени. (Linux/proc/cpuinfo показывает 'nonstop_tsc' для процессоров без этой проблемы и' constant_tsc' для функции с фиксированной ставкой.) –

1

Главным моментом моего вопроса является не точность результата, а тот факт, что я получаю отрицательные значения время от времени (первый вызов rdstc дает большее значение, чем второй вызов). Выполняя больше исследований (и читая другие вопросы на этом сайте), я узнал, что для работы с rdtsc можно использовать команду cpuid перед этим. Эта команда сериализует код. Это, как я делаю вещи сейчас:

static inline uint64_t get_cycles() 
{ 
    uint64_t t;   

    volatile int dont_remove __attribute__((unused)); 
    unsigned tmp; 
    __asm volatile ("cpuid" : "=a"(tmp), "=b"(tmp), "=c"(tmp), "=d"(tmp) 
     : "a" (0)); 

    dont_remove = tmp; 




    __asm volatile ("rdtsc" : "=A"(t)); 
    return t; 
} 

Я до сих пор получаю Отрицательную разницу между вторым вызовом и первым вызовом функции get_cycles. ЗАЧЕМ? Я не уверен на 100% о синтаксисе встроенного кода сборки cpuid, это то, что я нашел в Интернете.

0

rdtsc может использоваться для получения надежного и точного истекшего времени. Если вы используете linux, вы можете увидеть, поддерживает ли ваш процессор постоянную скорость tsc, просматривая/proc/cpuinfo, чтобы определить, определен ли константа_tsc.

Убедитесь, что вы остаетесь на одном ядре. У каждого ядра есть свой собственный tsc, который имеет свою ценность. Чтобы использовать rdtsc, убедитесь, что вы либо taskset, либо SetThreadAffinityMask (windows) или pthread_setaffinity_np, чтобы убедиться, что ваш процесс остается на одном ядре.

Затем вы делите это на вашей основной тактовой частотой, которая на Linux можно найти в/Proc/CPUInfo или вы можете сделать это во время выполнения

RDTSC
clock_gettime
сон в течение 1 секунды
clock_gettime
rdtsc

затем посмотрите, сколько клещей в секунду, а затем вы можете разделить любую разницу в тиках, чтобы узнать, сколько времени прошло.

5
  1. не используют значение, Avg

    Используйте наименьший один или Avg меньших значений вместо (чтобы получить СРЕДНЕМ из-CACHE-х), так как более крупные было прервано OS многозадачных.

    Вы также могли помнить все значения, а затем нашли границу детализации процесса OS и отфильтровать все значения после этой границы (обычно>1ms, который легко обнаружить)

    enter image description here

  2. нет необходимости измерять накладные расходы RDTSC

    Вы просто измеряете смещение на некоторое время, и такое же смещение присутствует в обоих случаях, а после вычитания оно уходит.

  3. для переменного источника тактовой RDTS (как на ноутбуках)

    Вы должны изменить скорость CPU к его максимальному некоторым установившимся интенсивному циклу вычислений обычно несколько секунд достаточно. Вы должны постоянно измерять частоту CPU и начать измерять свою вещь только в том случае, если она достаточно стабильна.

2

Если код начинается на одном процессоре, то обменивает на другую, разница отметка времени может быть отрицательным из-процессоров спящих и т.д.

Попробуйте установить соответствие процессоров, прежде чем начать измерение.

Я не могу понять, работаете ли вы под управлением Windows или Linux, поэтому я отвечу за них.

Окна:

DWORD affinityMask = 0x00000001L; 
SetProcessAffinityMask(GetCurrentProcessId(), affinityMask); 

Linux:

cpu_set_t cpuset; 
CPU_ZERO(&cpuset); 
CPU_SET(0, &cpuset); 
sched_setaffinity (getpid(), sizeof(cpuset), &cpuset) 
0

Если поток, который работает ваш код движется между ядрами, то вполне возможно, что значение RDTSC возвращается меньше, чем значение, вычисленное на другой ядро. Ядро не все устанавливает счетчик на 0 точно в то же время, когда пакет активируется. Поэтому при запуске теста убедитесь, что вы привязываете нить к определенному ядру.

+0

tsc часто синхронизируются между ядрами одного и того же сокета и обычно могут быть синхронизированы по нескольким сокетам (http://stackoverflow.com/questions/10921210) * На новых процессорах (i7 Nehalem + IIRC) TSC синхронизируется по всем ядрам и работает постоянная скорость. * ... * Intel .. является синхронным между ядрами и пакетами на многоразъемной материнской плате *). Вероятно, это делается ОС для получения глобального источника синхронизации с высоким разрешением. – osgx

0

Я проверил ваш код на своей машине, и я понял, что во время RDTSC fuction только uint32_t является разумным.

я делаю следующее в своем коде, чтобы исправить это:

if(before_t<after_t){ diff_t=before_t + 4294967296 -after_t;} 
Смежные вопросы