热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《linux中的汇编代码产生了多少延迟》经验,为你挑选了1个好方法。

我在装配中经历了这个链接延迟,以增加装配延迟.我想通过添加不同的延迟值来执行一些实验.

生成延迟的有用代码

; start delay

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

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

任何人都可以向我解释如何计算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处理器.

先感谢您.



1> Peter Cordes..:

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

通常你想睡觉并让操作系统唤醒你的进程,但这对Linux上的延迟只有几微秒是行不通的. nanosleep可以表达它,但内核没有按照这样精确的时间安排.请参阅如何使线程休眠/阻塞纳秒(或至少毫秒)?.在启用了Meltdown + Spectre缓解的内核上,无论如何,内核的往返时间都要超过一微秒.

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


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

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

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

如果您的内部循环不是那么小(例如,更多nops),请参阅执行uop计数不是处理器宽度倍数的循环时性能是否降低?有关uop计数不是8的倍数的前端吞吐量的详细信息.带有禁用循环缓冲区的SKL/KBL从uop缓存运行,即使是微循环也是如此.


但是x86没有固定的时钟频率(在Skylake CPU上,频率状态之间的转换会使时钟停止约20k个时钟周期(8.5us)).

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

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

系统管理模式中断是内核不知道的另一个延迟源. 系统管理模式的性能影响从2013年开始表示,根据英特尔的PC BIOS测试套件,150微秒被认为是SMI的"可接受"延迟.现代电脑充满伏都教.我认为/希望大多数主板上的固件没有太多的SMM开销,并且SMI在正常操作中非常罕见,但我不确定.另请参阅评估Linux-CentOS/Intel计算机上的SMI(系统管理中断)延迟

极低功耗的Skylake CPU以一些占空比停止其时钟,而不是降低时钟并持续运行.请参阅此内容,以及英特尔关于Skylake电源管理的IDF2015演示文稿.


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

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

我写了这样一个循环,作为一个愚蠢的计算机技巧练习的一部分:一个秒表在x86机器代码的最少字节.大多数代码大小用于字符串操作以增加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 

为了避免频率计算的浮点数,我使用了定点uint32_t ref_freq_fixedpoint = 3.2 * (1<<16);.这意味着我们只需在延迟循环内使用整数乘法和移位. 使用C代码ref_freq_fixedpoint在启动期间使用正确的CPU值进行设置.

如果为每个目标CPU重新编译它,则乘法常量可以是立即操作数,imul而不是从内存加载.

pause在Skylake上睡觉约100个时钟,但在以前的英特尔搜索中只能用于约5个时钟.因此它会稍微损害定时精度,当CPU频率降至~1GHz时,可能会在截止时间之前休眠100 ns.或者以正常~3GHz的速度,更像是高达+ 33ns.

运行汽车无,这个循环加热我SKYLAKE微架构i7-6700k的一个核心在〜3.9GHz由〜15°C,无pause,但只能通过〜9下用pause.(使用大型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的核心可能导致在错误的时间内睡眠.但除非您的延迟长,否则迁移时间将超过预期的延迟.最糟糕的情况是在迁移后再次延迟休眠时间.我进行比较的方式:(now - start) ,而不是寻找某个目标目标计数,意味着无符号环绕将使得当now-start大数字时比较为真.当计数器缠绕时,你不会被困住几乎整整一秒钟.

缺点:您可能想要睡眠一定数量的核心周期,或者在CPU处于睡眠状态时暂停计数.

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


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

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

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

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

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

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


推荐阅读
  • Linux环境下的PHP7安装与配置指南
    本文详细介绍了如何在Linux操作系统中安装和配置PHP7,包括检查当前PHP版本、升级PHP以及配置MySQL支持等步骤,适合后端开发者参考。 ... [详细]
  • 本文详细介绍了如何在 Ubuntu 14.04 系统上搭建仅使用 CPU 的 Caffe 深度学习框架,包括环境准备、依赖安装及编译过程。 ... [详细]
  • 远程访问用户 Kindle通过电子书实现控制
    介绍自2007年以来,亚马逊已售出数千万台Kindle,令人印象深刻。但这也意味着数以千万计的人可能会因为这些Kindle中的软件漏洞而被黑客入侵。他 ... [详细]
  • 本文概述了在GNU/Linux系统中,动态库在链接和运行阶段的搜索路径及其指定方法,包括通过编译时参数、环境变量及系统配置文件等方式来控制动态库的查找路径。 ... [详细]
  • 本文深入探讨了Linux内核中进程地址空间的设计与实现,包括虚拟地址空间的概念、内存描述符`mm_struct`的作用、内核线程与用户进程的区别、进程地址空间的分配方法、虚拟内存区域(VMA)的结构以及地址空间与页表之间的映射机制。 ... [详细]
  • 当Ubuntu虚拟机的存储空间不足时,可以通过VMware轻松地为其添加新的硬盘。本文详细介绍了从关闭虚拟机、添加新硬盘到分区、格式化及挂载整个过程的操作步骤。 ... [详细]
  • 深入解析C++ Atomic编程中的内存顺序
    在多线程环境中,为了防止多个线程同时修改同一数据导致的竞争条件,通常会使用内核级同步对象,如事件、互斥锁和信号量等。然而,这些方法往往伴随着高昂的上下文切换成本。本文将探讨如何利用C++11中的原子操作和内存顺序来优化多线程编程,减少不必要的开销。 ... [详细]
  • 在Linux系统中使用EncFS实现文件夹加密
    为了保护个人隐私或敏感数据不被未经授权的访问,可以通过加密技术来增强安全性。本文介绍如何在Linux系统上使用EncFS工具创建和管理加密文件夹,以确保即使在系统登录状态下,特定文件夹中的数据也保持加密状态。 ... [详细]
  • 本文详细介绍了如何使用Linux下的mysqlshow命令来查询MySQL数据库的相关信息,包括数据库、表以及字段的详情。通过本文的学习,读者可以掌握mysqlshow命令的基本语法及其常用选项。 ... [详细]
  • 本文探讨了Linux环境下线程私有数据(Thread-Specific Data, TSD)的概念及其重要性,介绍了如何通过TSD技术避免多线程间全局变量冲突的问题,并提供了具体的实现方法和示例代码。 ... [详细]
  • 一文详解Linux
    Linuxnetfilter与VRF实验环境如下图所示:配置如下:#!binbashsudoipnetnsaddns1sudoiplinkaddns1veth1typevethpe ... [详细]
  • Ubuntu系统下的GIF动画录制解决方案
    在撰写文章或教程时,GIF动态图能够有效地传达信息。对于Windows用户而言,ScreenToGif是一款非常实用的工具。而在Ubuntu系统中,用户同样拥有多种选择来创建GIF动画,本文将重点介绍两款录屏工具——Byzanz和Peek。 ... [详细]
  • IhaveWindows8.1prowithanAMDprocessor.IinstalledtheAndroidSDKandEclipse.Itworksbut ... [详细]
  • 如何解决《所有64位intel架构是否都支持SSSE3/SSE4.1/SSE4.2指令?》经验,为你挑选了1个好方法。 ... [详细]
  • github项目地址https:github.comlinux-nvmenvme-cli下载:wgethttps:codeload.github.comlin ... [详细]
author-avatar
陈庭勇筱玲喜芳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有