rdtsc, слишком много циклов
Вопрос
#include <stdio.h>
static inline unsigned long long tick()
{
unsigned long long d;
__asm__ __volatile__ ("rdtsc" : "=A" (d) );
return d;
}
int main()
{
long long res;
res=tick();
res=tick()-res;
printf("%d",res);
return 0;
}
Я скомпилировал этот код с помощью gcc с оптимизацией -O0 -O1 -O2 -O3.И у меня всегда получается 2000-2500 циклов.Может ли кто-нибудь объяснить причину такого вывода?Как провести эти циклы?
Первая функция «галочка» неверна.Это правильно.
Другая версия функции "галочка"
static __inline__ unsigned long long tick()
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
Это ассемблерный код для -O3
.file "rdtsc.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl %ecx, -16(%ebp)
movl %ebx, -12(%ebp)
movl %esi, -8(%ebp)
movl %edi, -4(%ebp)
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %edx, %edi
movl %eax, %esi
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %eax, %ecx
movl %edx, %ebx
subl %esi, %ecx
sbbl %edi, %ebx
movl %ecx, 4(%esp)
movl %ebx, 8(%esp)
movl $.LC0, (%esp)
call printf
movl -16(%ebp), %ecx
xorl %eax, %eax
movl -12(%ebp), %ebx
movl -8(%ebp), %esi
movl -4(%ebp), %edi
movl %ebp, %esp
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
Это процессор
processor : 0
vendor_id : GenuineIntel
cpu family : 15
model : 4
model name : Intel(R) Xeon(TM) CPU 3.00GHz
stepping : 3
cpu MHz : 3000.105
cache size : 2048 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 5
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss constant_tsc up pebs bts pni
bogomips : 6036.62
clflush size : 64
Решение
Я попробовал ваш код на нескольких дистрибуциях Linux, работающих на разных процессорах Intel (по общему признанию все более позднее, чем Pentium 4 HT 630, который вы, похоже, используете). Во всех этих тестах я получил значения от 25 до 50 циклов.
Моя единственная гипотеза, которая согласуется со всеми доказательствами, заключается в том, что вы запускаете свою операционную систему внутри виртуальной машины, а не на обнаженном металле, и TSC виртуализирует.
Другие советы
Есть любое количество причин, чтобы получить большое количество:
- ОС сделала переключатель контекста, и ваш процесс был усыплен.
- Произошла поиск диска, и ваш процесс усыновился.
- … Любая из множества причин относительно того, почему ваш процесс может быть проигнорирован.
Обратите внимание, что rdtsc
не особенно надежно для времени без работы, потому что:
- Скорость процессора может измениться, и, следовательно, длина цикла (при измерении в секундах) изменяется.
- Разные процессоры могут иметь разные значения для TSC для данного момента во времени.
Большинство операционных систем имеют высокий характер или метод синхронизации. clock_gettime
Например, на Linux, особенно монотонные часы. (Также поймите разницу между настенным покрытием и монотонными часами: настенные часы могут двигаться назад-даже в UTC.) В окнах, я думаю, рекомендация QueryHighPerformanceCounter
. Анкет Как правило, эти часы обеспечивают более чем достаточную точность для большинства потребностей.
Кроме того, глядя на сборку, похоже, что вы получаете только 32-битный ответ: я не вижу %edx
спасение после rdtsc
.
Запуск вашего кода, я получаю время от 120-150 нс для clock_gettime
с использованием CLOCK_MONOTONIC
, и 70-90 циклов для RDTSC (~ 20 нс на полной скорости, но я подозреваю, что процессор снижается, и это действительно около 50 нс). (На ноутбукНастольный компьютер (черт возьми, забыл, на какой машине я был!), Который находится примерно на 20% использования ЦП). Убедитесь, что ваша машина не увязана?
Похоже, ваша ОС отключила выполнение RDTSC в пользовательском пространстве.И вашему приложению приходится переключаться на ядро и обратно, что занимает много циклов.
Это из Руководства разработчика программного обеспечения Intel:
В режиме защищенного или виртуального режима 8086, флаг Time Disable (TSD) в реестре CR4 ограничивает использование инструкции RDTSC следующим образом.Когда флаг TSD ясен, инструкция RDTSC может быть выполнена на любом уровне привилегий;Когда флаг установлен, инструкция может быть выполнена только на уровне привилегий 0.(В режиме реального адапта, инструкция RDTSC всегда включена.)
Редактировать:
Отвечая на комментарий aix, поясняю, почему здесь, скорее всего, причина ТСД.
Я знаю только эти возможности программы выполнять одну инструкцию дольше, чем обычно:
- Запускаю под каким-нибудь эмулятором,
- используя самомодифицируемый код,
- переключение контекста,
- переключатель ядра.
Первые две причины обычно не могут задержать выполнение более чем на несколько сотен циклов.2000-2500 циклов более характерны для переключения контекста/ядра.Но поймать переключение контекста несколько раз на одном и том же месте практически невозможно.Итак, это должен быть переключатель ядра.Это означает, что либо программа работает под отладчиком, либо RDTSC не разрешен в пользовательском режиме.
Наиболее вероятной причиной отключения RDTSC в ОС может быть безопасность.Были попытки использовать RDTSC для взлома программ шифрования.
Кэш инструкции пропускает? (это мое предположение)
Также, возможно,,
Переключиться на гипервизор в виртуализированной системе? Остатки программы начальной загрузки (включая сетевую деятельность на том же процессоре)?
To Thanatos: в системах более недавних, чем 2008 год, rdtsc () - это настенные часы и не варьируется в зависимости от шагов частоты.
Вы можете попробовать этот маленький код?
int main()
{
long long res;
fflush(stdout); // chnage the exact timing of stdout, in case there is something to write in a ssh connection, together with its interrupts
for (int pass = 0; pass < 2; pass++)
{
res=tick();
res=tick()-res;
}
printf("%d",res); // ignore result on first pass, display the result on second pass.
return 0;
}
Просто идея - может быть, эти две инструкции RDTSC выполняются на разных ядрах? Значения RDTSC могут немного различаться в зависимости от ядер.