AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79091005
Accepted
Juliean
Juliean
Asked: 2024-10-16 01:00:06 +0800 CST2024-10-16 01:00:06 +0800 CST 2024-10-16 01:00:06 +0800 CST

函数调用的隐式空检查

  • 772

隐式空值检查是一种技术,用于在高级语言本机表示中移除对空指针/引用的显式检查,而是依靠处理器发出访问冲突,然后对其进行处理(即 SEH),并将其转换为托管异常。它主要用于异常处理开销次要的情况,例如,如果我们知道空值异常很少见。

在我发现的所有例子中,这些检查都是针对访问相关 ptr 的语句进行的:

int m(Foo foo) {
  return foo.x;
}

这里,我们可以简单地发出 asm 代码:

mov rax,[rcx]

并让本机异常处理机制处理生成 NullReferenceException,而不是崩溃。

但是,函数调用又如何呢?

int m(Foo foo) {
  return foo.MemberFunction();
}

是否也可以在那里使用隐式空检查?我对 x64-asm 特别感兴趣。那里似乎更难。让我们看一个 asm 中的非虚拟函数调用示例(代码与函数 1:1 不匹配,它包含一个“mov”,只是为了显示一个对象被设置到用于 Windows 上的成员函数调用的寄存器中):

mov     rcx,[rsp+20h]           // load target-object from stack-local (Foo*)
call    Foo::MemberFunction     // call Foo::MemberFunction, can be represented with an address w/o fixups of the ptr 

在这里,我们无法访问“rcx”指向的内存。因此,如果根据语言的定义,这样的调用必须在调用点抛出 NullReferenceException,我们需要使用显式检查:

mov     rcx,[rsp+32h]           // load target-object from stack-local (Foo*)
test    rcx,rcx
je      .L0                     // exception-handler already moved out of hot-path
call    Foo::MemberFunction     // call Foo::MemberFunction, can be represented with an address w/o fixups of the ptr 

...
.L0:
call throwNullReferenceException();

或者有没有更有效的方法用一条产生访问冲突的指令替换 test+je 对?我想我可以这样做

mov     rcx,[rsp+32h]           // load target-object from stack-local (Foo*)
mov     rax,[rcx]               // mov into unused reg, to trigger access-violation
call    Foo::MemberFunction     // call Foo::MemberFunction, can be represented with an address w/o fixups of the ptr 

这不会使用分支,也不需要额外调用异常调用。但是,它可能需要读取 [rcx] 的内存,而另一种方法不需要。与分支相比,它的性能如何?如果更差,有没有更好的方法?请参阅下文以进一步解释完整的用例。

背景

我有一种自定义的高级语言,它被编译为字节码,然后被编译为本机 ASM。该语言使用 NullReference 异常优雅地处理空检查。异常仍然始终是需要解决的错误,而不是正常发生的事件。因此,处理异常的代码可能效率低下。重要的是,在通常没有异常(因此没有空引用)的情况下,代码运行得尽可能快。这就是隐式空检查看起来很有吸引力的原因。删除处理调用异常所需的所有分支和额外代码可能会有益。不过,即使是现有的检查也应该已经很快了。分支应该可以很好地预测为始终为假,并且我已经这样做了,所以这种情况根本不需要 jmp,而是让代码线性执行(我读过这是更优化的)。

那么考虑到这一点,我在上述情况下试图摆脱这些检查是否愚蠢,或者是否有某种方法可以最佳地实现它?

windows
  • 1 1 个回答
  • 45 Views

1 个回答

  • Voted
  1. Best Answer
    Peter Cordes
    2024-10-16T02:37:43+08:002024-10-16T02:37:43+08:00

    然而,它可能需要读取[rcx]

    除非缓存未命中,否则成本很低。而且被调用者无论如何都会在稍后支付该成本,除非它从不触碰其*this。

    如果确实如此,那么之前的加载基本上就是预取……除非第一个“实际”访问是写入,在这种情况下,如果多个线程访问此对象,我们可以直接进入 MESI 独占/已修改状态,而无需先从只读访问中获取仅处于共享状态的副本。如果之前没有其他核心拥有缓存行,则简单的加载通常会使缓存行进入独占状态,无需另一个核外事务(读取所有权 = RFO)即可转换为已修改状态。

    如果被调用者在第一次访问时也将进行读取,那么这里的读取没有任何缺点。

    对于大对象,如果成员函数仅触及位于后续缓存行中的成员,则触及第一个缓存行将会污染缓存。


    让无序执行处理负载是件好事,可能比假设它实际上从未出错的测试/分支更便宜。测试/分支可能会出现分支错误预测。就像每条指令一样,管道强烈假设负载不会出错,只有当出错指令达到退出状态(变为非推测性)时才会真正执行任何操作。

    但是分支总是以这种或那种方式进行预测,并且占用分支预测资源,并且可以与其他分支混淆,因此即使它们总是被强烈拒绝,也会被错误预测。

    • 当 Skylake CPU 错误预测分支时究竟会发生什么?
    • 推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?(我的回答主要谈论的是需要存储缓冲区才能实现存储的推测执行,因为与加载不同,它们会修改缓存。)
    • 无序执行与推测执行——所有指令在退出之前都被视为推测指令。

    现代 x86 CPU 具有非常好的加载端口吞吐量,例如每个时钟周期 2 或 3 个(对于自然对齐的加载,L1d 缓存命中),并且有相当多的加载缓冲区来跟踪未完成的加载。例如,10 多年前的 Haswell(https://www.realworldtech.com/haswell-cpu/5/)有 72 个加载缓冲区条目,而 ROB(重新排序缓冲区)有 192 个条目。

    对于小于 4 个字节的结构,使用mov eax, [rcx](2 个字节的机器代码) 或(3 个字节)加载到 32 位寄存器可能是最好的选择,甚至比 @user555045 建议的立即数更便宜。 从 8 字节存储到前 8 个字节的 4 字节加载的存储转发在 x86 CPU 上非常有效,甚至在很多年前也是如此,并且 32 位操作数大小避免了 REX 前缀。movzx eax, byte ptr [rcx]test0

    test [rcx], cl可以节省代码大小,并且不会产生错误依赖,mov还可以避免后端执行单元的 ALU uop。对于执行任何类型的微融合的任何 CPU,前端和发布/重命名阶段应该只有 1 个微操作 (uop)。(或者 AMD 首先简单地将其解码为 1 个 uop)。

    两个主要的 x86-64 调用约定都至少有一个纯粹的调用破坏寄存器,在调用之前加载它总是安全的(例如,Win x64 的 EAX、AMD64 SysV 的 R11D:可变参数函数使用 AL 传递 XMM 参数的数量。尽管您可以在 EAX 之后将其设置为常量mov,除非这是一个将可变参数传递给另一个函数的垫片/蹦床。)

    就寄存器文件限制而言,写入 GPR 和/或 FLAGS 与无序执行可以预见的范围是等效的:物理寄存器文件条目有空间容纳一个 64 位整数加上一个 FLAGS 结果,因此类似这样的指令add rax, rcx可以被无序执行机制视为仅写入一个结果。

    test cl, [rcx]或test eax, [rcx]仅比稍差一些mov eax, [rcx],因此如果由于某种原因您不能轻松地选择一个寄存器来写入,那么不要太担心使用它们。

    • 2

相关问题

  • C++ 完全读取文件,其中大小>2GB (Win64)

  • Windows Palo Alto Cortex XDR BSOD,带错误检查 0x139

  • 我无法在我的 go 项目中导入本地包

  • KERNEL32.DLL 总是加载到 Windows 进程中的第三个模块吗?

  • 这个不安全的 Rust 代码有什么问题,所以它可以在 Windows 上运行,但不能在 Ubuntu 上运行?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行?

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    何时应使用 std::inplace_vector 而不是 std::vector?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Marko Smith

    我正在尝试仅使用海龟随机和数学模块来制作吃豆人游戏

    • 1 个回答
  • Martin Hope
    Aleksandr Dubinsky 为什么 InetAddress 上的 switch 模式匹配会失败,并出现“未涵盖所有可能的输入值”? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge 为什么这个简单而小的 Java 代码在所有 Graal JVM 上的运行速度都快 30 倍,但在任何 Oracle JVM 上却不行? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini 具有指定基础类型但没有枚举器的“枚举类”的用途是什么? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer 何时应使用 std::inplace_vector 而不是 std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB 为什么 GCC 生成有条件执行 SIMD 实现的代码? 2024-02-17 06:17:14 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve