ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

linux中的汇编代码产生了多少延迟

2019-09-28 05:01:01  阅读:260  来源: 互联网

标签:x86 linux assembly delay intel


我正在通过这个链接delay in assembly来增加装配延迟.我想通过添加不同的延迟值来执行一些实验.

生成延迟的有用代码

; start delay

mov bp, 43690
mov si, 43690
delay2:
dec bp
nop
jnz delay2
dec si
cmp si,0    
jnz delay2
; end delay

我从代码中理解的是,延迟与执行nop指令所花费的时间成比例(43690×43690).所以在不同系统和不同版本的操作系统中,延迟会有所不同.我对吗?

任何人都可以向我解释如何计算nsec的延迟量,下面的汇编代码正在生成,以便我可以结束我在实验设置中添加的延迟的实验?

这是我用来生成延迟而不理解使用43690值的逻辑的代码(我在原始源代码中只对一个循环使用了一个循环).为了产生不同的延迟(不知道它的值),我只改变了数字43690到403690或其他值.

32位操作系统中的代码

movl  $43690, %esi   ; ---> if I vary this 4003690 then delay value ??
.delay2:
    dec %esi
    nop
    jnz .delay2

这个汇编代码会产生多少延迟?

如果我想在microsec中生成100nsec或1000nsec或任何其他延迟,那么我需要在寄存器中加载什么初始值?

我使用的是ubuntu 16.04(32位和64位),Intel(R)Core(TM)i5-7200U CPU @ 2.50GHz和Core-i3 CPU 3470 @ 3.20GHz处理器.

先感谢您.

解决方法:

从现代x86 PC上的延迟循环的固定计数中获取准确且可预测的时序没有很好的方法,特别是在非实时操作系统(如Linux)下的用户空间中. (但你可以在rdtsc上进行非常短暂的延迟;见下文).你可以使用一个简单的延迟循环,如果你需要睡眠至少足够长,当出​​现问题时可以睡得更久.

通常你想睡觉并让操作系统唤醒你的进程,但这对Linux上的延迟只有几微秒是行不通的. nanosleep可以表达它,但内核没有按照这样精确的时间安排.请参阅How to make a thread sleep/block for nanoseconds (or at least milliseconds)?.在启用了Meltdown Spectre缓解的内核上,内核往返需要的时间超过一微秒.

(或者你在内核中这样做了吗?我认为Linux已经有一个校准的延迟循环.无论如何,它有一个标准的延迟API:https://www.kernel.org/doc/Documentation/timers/timers-howto.txt,包括使用“jiffies”时钟速度的ndelay(unsigned long nsecs)估计睡眠时间至少足够长.IDK有多准确,或者当时钟速度低时有时睡眠时间比需要的时间长,或者当CPU频率发生变化时更新校准.)

在最近的Intel / AMD CPU上,你的(内部)循环在每个核心时钟周期的1次迭代中是完全可预测的,无论是否存在nop.它属于4个融合域uop,因此您在CPU的每时钟1个循环吞吐量上遇到瓶颈. (参见Agner Fog’s x86 microarch guide,或者使用perf stat ./a.out计算大量迭代计数.)除非在同一物理核心上存在来自另一个超线程的竞争……

或者除非内部循环跨越32字节边界,在Skylake或Kaby Lake上(循环缓冲区由微代码更新禁用以解决设计错误).然后你的dec / jnz循环可以每2个循环运行一次,因为它需要从2个不同的uop-cache行获取.

我建议不要让nop更有可能在更多的CPU上每个时钟1个.无论如何,您需要校准它,因此更大的代码占用空间是没有用的(因此也要省略额外的对齐). (如果您需要确保最小延迟时间,请确保在CPU处于最大涡轮增压时进行校准.)

如果您的内部循环不是那么小(例如更多的nops),请参阅Is performance reduced when executing loops whose uop count is not a multiple of processor width?,了解当uop计数不是8的倍数时前端吞吐量的详细信息.带有禁用循环缓冲区的SKL / KBL甚至从uop缓存运行对于微小的循环.

但x86没有固定的时钟频率(和Skylake CPU上的transitions between frequency states stop the clock for ~20k clock cycles (8.5us)).

如果在启用中断的情况下运行此操作,则中断是另一个不可预测的延迟源. (即使在内核模式下,Linux通常也会启用中断.数万个时钟周期的中断禁用延迟循环似乎是一个坏主意.)

如果在用户空间中运行,那么我希望您使用的是使用实时支持编译的内核.但即便如此,Linux还没有完全针对硬实时操作而设计,所以我不确定你能获得多少好处.

系统管理模式中断是内核不知道的另一个延迟源.根据英特尔的PC BIOS测试套件,2013年的PERFORMANCE IMPLICATIONS OF
SYSTEM MANAGEMENT MODE
表示150微秒被认为是SMI的“可接受”延迟.现代电脑充满伏都教.我认为/希望大多数主板上的固件没有太多的SMM开销,并且SMI在正常操作中非常罕见,但我不确定.另见Evaluating SMI (System Management Interrupt) latency on Linux-CentOS/Intel machine

极低功耗的Skylake CPU以一些占空比停止其时钟,而不是降低时钟并持续运行.见this,也见Intel’s IDF2015 presentation about Skylake power management.

旋转RDTSC直到正确的挂钟时间

如果你真的需要忙等待,请旋转rdtsc等待当前时间到达截止日期.您需要知道参考频率,它与核心时钟无关,因此它是固定的和不间断的(在现代CPU上;对于不变和不间断的TSC,有CPUID功能位.Linux检查这个,所以你可以查看/ proc /对于constant_tsc和nonstop_tsc的cpuinfo,但实际上你应该在程序启动时自己检查CPUID并计算出RDTSC频率(不知怎的……)).

我写了这样一个循环,作为一个愚蠢的计算机技巧练习的一部分:a stopwatch in the fewest bytes of x86 machine code.大多数代码大小是字符串操作增加00:00:00显示并打印它.我为我的CPU硬编码了4GHz RDTSC频率.

对于小于2 ^ 32个参考时钟的睡眠,您只需要查看计数器的低32位.如果你正确地进行比较,环绕会照顾好自己.对于1秒秒表,4.3GHz的CPU会出现问题,但是对于nsec / usec睡眠,没有问题.

 ;;; Untested,  NASM syntax

 default rel
 section .data
    ; RDTSC frequency in counts per 2^16 nanoseconds
    ; 3200000000 would be for a 3.2GHz CPU like your i3-3470

    ref_freq_fixedpoint: dd  3200000000 * (1<<16) / 1000000000

    ; The actual integer value is 0x033333
    ; which represents a fixed-point value of 3.1999969482421875 GHz
    ; use a different shift count if you like to get more fractional bits.
    ; I don't think you need 64-bit operand-size


 ; nanodelay(unsigned nanos /*edi*/)
 ; x86-64 System-V calling convention
 ; clobbers EAX, ECX, EDX, and EDI
 global nanodelay
 nanodelay:
      ; take the initial clock sample as early as possible.
      ; ideally even inline rdtsc into the caller so we don't wait for I$miss.
      rdtsc                   ; edx:eax = current timestamp
      mov      ecx, eax       ; ecx = start
      ; lea ecx, [rax-30]    ; optionally bias the start time to account for overhead.  Maybe make this a variable stored with the frequency.

      ; then calculate edi = ref counts = nsec * ref_freq
      imul     edi, [ref_freq_fixedpoint]  ; counts * 2^16
      shr      edi, 16        ; actual counts, rounding down

.spinwait:                     ; do{
    pause         ; optional but recommended.
    rdtsc                      ;   edx:eax = reference cycles since boot
    sub      eax, ecx          ;   delta = now - start.  This may wrap, but the result is always a correct unsigned 0..n
    cmp      eax, edi          ; } while(delta < sleep_counts)
    jb     .spinwait

    ret

为了避免频率计算的浮点,我使用了像uint32_t ref_freq_fixedpoint = 3.2 *(1 << 16);的定点.这意味着我们只需在延迟循环内使用整数乘法和移位.使用C代码在启动期间使用正确的CPU值设置ref_freq_fixedpoint. 如果为每个目标CPU重新编译它,则乘法常量可以是imul的立即操作数,而不是从内存加载. 在Skylake上暂停睡眠约100个时钟,但在以前的英特尔搜索中仅停留约5个时钟.因此它会稍微损害定时精度,当CPU频率降至~1GHz时,可能会在截止时间之前休眠100 ns.或者以正常~3GHz的速度,更像是高达33ns. 连续运行,这个循环将我的Skylake i7-6700k的一个核心在~3.9GHz加热~15摄氏度,没有停顿,但只有~9 C暂停. (使用大型CoolerMaster Gemini II热管冷却器从~30C的基线开始,但是在这种情况下气流低,以保持风扇噪音低.) 将启动时间测量调整为比实际更早,可以让您补偿一些额外的开销,例如离开循环时的分支错误预测,以及第一个rdtsc不会对时钟进行采样的事实.接近执行结束.乱序执行可以让rdtsc提前运行;您可以使用lfence或者考虑使用rdtscp来阻止第一个时钟样本在调用延迟函数之前在指令之前发生乱序. 将偏移保持在变量中也可以校准常数偏移.如果您可以在启动时自动执行此操作,那么处理CPU之间的差异可能会很好.但是你需要一些高精度计时器才能工作,这已经基于rdtsc了. 将第一个RDTSC内联到调用者并将低32位作为另一个函数arg传递将确保“定时器”立即启动,即使在调用延迟函数时存在指令缓存未命中或其他流水线停顿.所以I $miss time将是延迟间隔的一部分,而不是额外的开销. 在rdtsc上旋转的优势: 如果发生延迟执行的任何事情,循环仍然会在截止日期之前退出,除非在截止日期过后执行被阻止(在这种情况下,你被任何方法搞砸了). 因此,不使用n个周期的CPU时间,而是使用CPU时间,直到当前时间比首次检查时晚n * freq纳秒. 使用简单的计数器延迟循环,在4GHz时足够长的延迟会使您在0.8GHz(在最近的Intel CPU上的典型最低频率)下睡眠时间超过4倍. 这确实运行了两次rdtsc,因此它不适合仅几纳秒的延迟. (rdtsc本身约为20 uops,在Skylake / Kaby Lake上每25个时钟的吞吐量为一个.)我认为这可能是数百或数千纳秒的繁忙等待的最不好的解决方案. 缺点:迁移到另一个未通过TSC的核心可能导致在错误的时间内睡眠.但除非您的延迟很长,否则迁移时间将超过预期的延迟.最糟糕的情况是在迁移后再次延迟休眠时间.我做比较的方式:(现在 – 开始)< count,而不是查找某个目标目标计数,意味着当now-start是一个大数字时,无符号环绕将使比较为真.当计数器缠绕时,你不会被困住几乎整整一秒钟. 下行:maybe you want to sleep for a certain number of core cycles,或在CPU处于睡眠状态时暂停计数.

缺点:旧CPU可能没有不间断/不变的TSC.在启动时检查这些CPUID功能位,并可能使用备用延迟循环,或至少在校准时将其考虑在内.另请参阅Get CPU cycle count?,以了解我对RDTSC行为的规范答案的尝试.

未来的CPU:在具有WAITPKG CPUID功能的CPU上使用tpause.

(我不知道将来会有哪些CPU.)

这就像暂停,但让逻辑核心进入睡眠状态,直到TSC =您在EDX中提供的值:EAX.所以你可以rdtsc找出当前时间,将/缩放到TSC滴答的睡眠时间加到EDX:EAX,然后运行tpause.

有趣的是,它需要另一个输入寄存器,您可以将0设置为更深的睡眠(对其他超线程更友好,可能会回退到单线程模式),或者1更快的唤醒和更少的省电.

你不想用它来睡几秒钟;你想把控制权交还给操作系统.但是你可以做一个操作系统睡眠以接近你的目标唤醒,如果它很远,然后移动ecx,1或xor ecx,ecx / tpause ecx,无论剩下什么时间.

半相关(也是WAITPKG扩展的一部分)是更有趣的umonitor / umwait,它(如特权监视器/ mwait)在看到地址范围内的内存更改时可以唤醒核心.对于超时,它在TSC = EDX:EAX作为tpause时具有相同的唤醒.

标签:x86,linux,assembly,delay,intel
来源: https://codeday.me/bug/20190928/1826016.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有