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

如何从存储器到寄存器3个字节(24位)?

如何解决《如何从存储器到寄存器3个字节(24位)?》经验,为你挑选了1个好方法。

我可以使用MOV指令将存储在内存中的数据项移动到我选择的通用寄存器中.

MOV r8, [m8]
MOV r16, [m16]
MOV r32, [m32]
MOV r64, [m64]

现在,不要拍我,但怎么是下面的实现:MOV r24, [m24]?(我很欣赏后者不合法).

在我的例子中,我想移动字符"Pip",即0x706950h,以注册rax.

section .data           ; Section containing initialized data

14      DogsName: db "PippaChips"
15      DogsNameLen: equ $-DogsName

我首先认为我可以分别移动字节,即首先是一个字节,然后是一个字,或者是它们的某种组合.但是,我不能引用的"顶半" eax,rax,所以这倒下的第一个障碍,因为我最终会在写入任何数据首先被感动.

我的解决方案

26    mov al, byte [DogsName + 2] ; move the character “p” to register al
27    shl rax, 16                 ; shift bits left by 16, clearing ax to receive characters “pi”
28    mov ax, word [DogsName]     ; move the characters “Pi” to register ax

我可以将"Pip"声明为初始化数据项,但示例只是一个例子,我想了解如何在汇编中引用24位,或者40,48 ......就此而言.

是否有更类似的指令MOV r24, [m24]?有没有办法选择一系列内存地址,而不是提供偏移量和指定大小运算符.如何从内存中移动3个字节到ASM x86_64中注册?

NASM版本2.11.08架构x86



1> Peter Cordes..:

通常你会做一个4字节的加载并屏蔽掉你想要的字节带来的高垃圾,或者如果你正在对不关心高位的数据做一些事情,就忽略它. 如果只需要结果的低部分,那么可以使用哪个2的补码整数运算而不将输入中的高位置零?


与商店1 不同,加载您"不应该"的数据绝不是正确性的问题,除非您进入未映射的页面.(例如,如果db "pip"出现在页面的末尾,并且下一页未被映射.)但在这种情况下,您知道它是较长字符串的一部分,因此唯一可能的缺点是如果宽负载扩展到下一个缓存行中的性能(因此负载跨越缓存行边界). 在x86和x64上读取同一页面内的缓冲区末尾是否安全?

对于任何3个字节,如果3个字节本身没有在两个高速缓存行之间分割,则前面的字节或后面的字节总是可以安全访问.在运行时弄清楚它可能不值得,但如果你知道编译时的对齐,你可以做任何一个

mov   eax, [DogsName-1]     ; if previous byte is in the same page/cache line
shr   eax, 8

mov   eax, [DogsName]       ; if following byte is in the same page/cache line
and   eax, 0x00FFFFFF

我假设您想将结果零扩展到eax/rax,如32位操作数大小,而不是与现有的EAX/RAX高字节(如8或16位操作数大小寄存器)合并写道.如果您确实要合并,请屏蔽旧值和OR.或者,如果您加载,[DogsName-1]那么您想要的字节位于EAX的前3个位置,并且您想要合并到ECX中:shr ecx, 24/ shld ecx, eax, 24将旧的顶部字节向下移动到底部,然后在移位3个新字节时将其移回.(shld不幸的是,没有内存源形式.半相关:从两个单独的双字有效地加载到qword中.) shld在Intel CPU上很快(特别是Sandybridge和后来:1 uop),但不在AMD上(http:// agner.org/optimize/).


结合2个独立的负载

有很多方法可以做到这一点,但遗憾的是,所有CPU都没有单一的最快方式. 部分寄存器写入在不同的CPU上表现不同.你的方式(字节加载/移位/字加载ax)在Core2/Nehalem以外的CPU上是相当不错的(当你eax在组装后读取时会停止插入合并的uop ).但首先movzx eax, byte [DogsName + 2]要打破对旧值的依赖rax.

您期望编译器生成的经典"安全无处不在"代码将是:

DEFAULT REL      ; compilers use RIP-relative addressing for static data; you should too.
movzx   eax, byte [DogsName + 2]   ; avoid false dependency on old EAX
movzx   ecx, word [DogsName]
shl     eax, 16
or      eax, ecx

这需要额外的指令,但避免编写任何部分寄存器.但是,在Core2或Nehalem以外的CPU上,2个负载的最佳选择是写入ax.(Core2之前的Intel P6无法运行x86-64代码,而没有部分寄存器重命名的CPU将rax在写入时合并ax).Sandybridge仍然重命名AX,但合并只需1 uop而没有停顿,即与OR相同,但在Core2/Nehalem上,前端在插入合并uop时会停顿约3个周期.

Ivybridge和以后只重命名AH,而不是,AX或者AL在这些CPU上,对AX的负载是微融合负载+合并.Agner Fog没有列出mov r16, mSilvermont或Ryzen(或我看过的电子表格中的任何其他标签)的额外惩罚,因此可能没有部分重命名的其他CPU也会mov ax, [mem]作为加载+合并执行.

movzx   eax, byte [DogsName + 2]
shl     eax, 16
mov      ax, word [DogsName]

; using eax: 
  ; Sandybridge: extra 1 uop inserted to merge
  ; core2 / nehalem: ~3 cycle stall (unless you don't use it until after the load retires)
  ; everything else: no penalty

实际上,可以有效地在运行时测试对齐.给定寄存器中的指针,前一个字节在同一个高速缓存行中,除非该地址的最后几个5或6位都为零.(即地址与高速缓存行的开头对齐).让我们假设缓存行是64字节; 所有当前的CPU都使用它,我不认为任何具有32字节行的x86-64 CPU存在.(我们仍然绝对避免翻页).

    ; pointer to m24 in RSI
    ; result: EAX = zero_extend(m24)

    test   sil, 111111b     ; test all 6 low bits.  There's no TEST r32, imm8, so  REX r8, imm8 is shorter and never slower.
    jz   .aligned_by_64

    mov    eax, [rsi-1]
    shr    eax, 8
.loaded:

    ...
    ret    ; end of whatever large function this is part of

 ; unlikely block placed out-of-line to keep the common case fast
.aligned_by_64:
    mov    eax, [rsi]
    and    eax, 0x00FFFFFF
    jmp   .loaded

所以在通常的情况下,额外的成本只是一个未采取的测试和分支uop.

根据CPU,输入和周围代码的不同,测试低12位(仅避免跨越4k边界)会对页面内的某些高速缓存线分割进行更好的分支预测,但仍然不会进行高速缓存行分割.(在这种情况下test esi, (1<<12)-1.与sil使用a imm8进行测试不同,si使用一个imm16不值得在Intel CPU上的LCP停止来保存1个字节的代码.当然,如果你可以将你的指针放在ra/b/c/dx中,你就不要需要一个REX前缀,甚至还有一个紧凑的2字节编码test al, imm8.)

你甚至可以无分支地做到这一点,但显然不值得,只需要做两次单独的加载!

    ; pointer to m24 in RSI
    ; result: EAX = zero_extend(m24)

    xor    ecx, ecx
    test   sil, 7         ; might as well keep it within a qword if  we're not branching
    setnz  cl             ; ecx = (not_start_of_line) ? : 1 : 0

    sub    rsi, rcx       ; normally rsi-1
    mov    eax, [rsi]

    shl    ecx, 3         ; cl = 8 : 0
    shr    eax, cl        ; eax >>= 8  : eax >>= 0

                          ; with BMI2:  shrx eax, [rsi], ecx  is more efficient

    and    eax, 0x00FFFFFF  ; mask off to handle the case where we didn't shift.

真正的架构24位加载或存储

在架构上,x86没有24位加载或存储,整数寄存器作为目标或源.正如Brandon指出的那样,MMX/SSE屏蔽存储(比如MASKMOVDQU,不要混淆pmovmskb eax, xmm0)可以存储来自MMX或XMM寄存器的24位,给定一个仅设置了低3字节的矢量屏蔽.但它们几乎从来没用过,因为它们很慢而且总是有一个NT提示(因此它们会在缓存周围写入,并强制驱逐movntdq).(AVX dword/qword屏蔽的加载/存储指令并不意味着NT,但不具有字节粒度.)

AVX512BW(Skylake-server)增加vmovdqu8了为加载和存储提供字节屏蔽功能,可以对被屏蔽的字节进行故障抑制.(即如果16字节加载包含未映射页面中的字节,只要没有为该字节设置掩码位,就不会出现段错误.但这确实会导致大幅减速).因此,在体系结构上它仍然是一个16字节的负载,但对架构状态(即除了性能之外的所有内容)的影响正是真正的3字节加载/存储(使用正确的掩码).

您可以在XMM,YMM或ZMM寄存器中使用它.

;; probably slower than the integer way, especially if you don't actually want the result in a vector
mov       eax, 7                  ; low 3 bits set
kmovw     k1, eax                 ; hoist the mask setup out of a loop


; load:  leave out the {z} to merge into the old xmm0 (or ymm0 / zmm0)
vmovdqu8  xmm0{k1}{z}, [rsi]    ; {z}ero-masked 16-byte load into xmm0 (with fault-suppression)
vmovd     eax, xmm0

; store
vmovd     xmm0, eax
vmovdqu8  [rsi]{k1}, xmm0       ; merge-masked 16-byte store (with fault-suppression)

这与NASM 2.13.01组装.IDK,如果你的NASM足够新,可以支持AVX512.您可以使用英特尔软件开发仿真器(SDE)在没有硬件的情况下使用AVX512

这看起来很酷,因为只有2 uop才能得到一个结果eax(一旦设置了掩码).(但是,http://instlatx64.atw.hu/来自IACA for Skylake-X 的数据电子表格不包含vmovdqu8掩码,只包含未屏蔽的表格.这些表明它仍然是单个uop负载,或微- 像常规一样的保险商店vmovdqu/a)

但是要注意,如果16字节的负载出现故障或越过缓存线边界,则会减速.我认为它在内部确实负载,然后丢弃字节,如果需要抑制故障,可能会有一个特别昂贵的特殊情况.

此外,对于商店版本,请注意屏蔽的商店不能有效地转发到加载.(有关详细信息,请参阅英特尔的优化手册).


脚注:

    宽存储是一个问题,因为即使你替换旧值,你也会做一个非原子读 - 修改 - 写,如果你放回的那个字节是一个锁,这可能会破坏事情. 除非你知道接下来会发生什么并且它是安全的,否则不要存放在物体之外,例如你放在那里的填充物.可以 lock cmpxchg修改的4字节的值到位,以确保你不是踩着额外的字节的另一个线程的更新,但显然做2个独立的专卖店是多少性能比原子更cmpxchg重试循环.


推荐阅读
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 本文探讨了如何通过编程手段在Linux系统中禁用硬件预取功能。基于Intel® Core™微架构的应用性能优化需求,文章详细介绍了相关配置方法和代码实现,旨在帮助开发人员有效控制硬件预取行为,提升应用程序的运行效率。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • 浅析python实现布隆过滤器及Redis中的缓存穿透原理_python
    本文带你了解了位图的实现,布隆过滤器的原理及Python中的使用,以及布隆过滤器如何应对Redis中的缓存穿透,相信你对布隆过滤 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 本文总结了在SQL Server数据库中编写和优化存储过程的经验和技巧,旨在帮助数据库开发人员提升存储过程的性能和可维护性。 ... [详细]
  • 在使用 Cacti 进行监控时,发现已运行的转码机未产生流量,导致 Cacti 监控界面显示该转码机处于宕机状态。进一步检查 Cacti 日志,发现数据库中存在 SQL 查询失败的问题,错误代码为 145。此问题可能是由于数据库表损坏或索引失效所致,建议对相关表进行修复操作以恢复监控功能。 ... [详细]
  • 本文详细介绍了在MySQL中如何高效利用EXPLAIN命令进行查询优化。通过实例解析和步骤说明,文章旨在帮助读者深入理解EXPLAIN命令的工作原理及其在性能调优中的应用,内容通俗易懂且结构清晰,适合各水平的数据库管理员和技术人员参考学习。 ... [详细]
  • 本文介绍了如何利用Shell脚本高效地部署MHA(MySQL High Availability)高可用集群。通过详细的脚本编写和配置示例,展示了自动化部署过程中的关键步骤和注意事项。该方法不仅简化了集群的部署流程,还提高了系统的稳定性和可用性。 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 出库管理 | 零件设计中的状态模式学习心得与应用分析
    出库管理 | 零件设计中的状态模式学习心得与应用分析 ... [详细]
  • 本文详细介绍了在 Oracle 数据库中使用 MyBatis 实现增删改查操作的方法。针对查询操作,文章解释了如何通过创建字段映射来处理数据库字段风格与 Java 对象之间的差异,确保查询结果能够正确映射到持久层对象。此外,还探讨了插入、更新和删除操作的具体实现及其最佳实践,帮助开发者高效地管理和操作 Oracle 数据库中的数据。 ... [详细]
  • 深入理解排序算法:集合 1(编程语言中的高效排序工具) ... [详细]
  • 本文深入探讨了 Git 与 SVN 的高效使用技巧,旨在帮助开发者轻松应对版本控制中的各种挑战。通过详细解析两种工具的核心功能与最佳实践,读者将能够更好地掌握版本管理的精髓,提高开发效率。 ... [详细]
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社区 版权所有