【问题标题】:How to offload memory offset calculation from runtime in C/C++?如何在 C/C++ 中从运行时卸载内存偏移计算?
【发布时间】:2012-07-15 18:10:54
【问题描述】:

我正在实现一个简单的虚拟机,目前我正在使用运行时算法来计算单个程序对象地址作为基指针的偏移量。

我今天就这个主题问了几个问题,但我似乎无处可去。

我从第一个问题中学到了一些东西- Object and struct member access and address offset calculation - 我了解到现代处理器具有虚拟寻址功能,无需任何额外的算术周期即可计算内存偏移量。

从问题二 - Are address offsets resolved during compile time in C/C++? - 我了解到手动执行偏移时无法保证会发生这种情况。

现在应该很清楚,我想要实现的是利用硬件的虚拟内存寻址功能并从运行时卸载这些功能。

我正在使用 GCC,至于平台 - 我正在 windows 中的 x86 上进行开发,但由于它是一个 VM,我希望它能够在 GCC 支持的所有平台上高效运行。

因此,欢迎提供有关该主题的任何信息,我们将不胜感激。

提前致谢!

编辑:关于我的程序代码生成的一些概述 - 在设计阶段,程序被构建为一个树层次结构,然后递归序列化为一个连续的内存块,以及索引对象并计算它们从开头的偏移量程序内存块。

编辑 2:这是虚拟机的一些伪代码:

switch *instruction
   case 1: call_fn1(*(instruction+1)); instruction += (1+sizeof(parameter1)); break;
   case 2: call_fn2(*(instruction+1), *(instruction+1+sizeof(parameter1));
           instruction += (1+sizeof(parameter1)+sizeof(parameter2); break;
   case 3: instruction += *(instruction+1); break;  

案例 1 是一个带有一个参数的函数,该参数在指令之后立即找到,因此它作为指令的 1 个字节的偏移量传递。指令指针递增 1 + 第一个参数的大小以查找下一条指令。

案例 2 是一个接受两个参数的函数,和之前一样,第一个参数作为 1 字节偏移量传递,第二个参数作为 1 字节偏移量加上第一个参数的大小传递。然后指令指针会增加指令的大小加上两个参数的大小。

情况 3 是 goto 语句,指令指针增加一个偏移量,该偏移量紧跟 goto 指令。

编辑 3:据我了解,操作系统将为每个进程提供自己专用的虚拟内存寻址空间。如果是这样,这是否意味着第一个地址总是......以及零,所以从内存块的第一个字节的偏移实际上就是这个元素的地址?如果内存地址专用于每个进程,并且我知道我的程序内存块的偏移量以及每个程序对象相对于内存块第一个字节的偏移量,那么对象地址是否在编译时解析?

问题是那些偏移量在编译 C 代码期间不可用,它们在“编译”阶段和转换为字节码时被知道。这是否意味着没有办法为“free”做对象内存地址计算?

这是如何在 Java 中完成的,例如,只有虚拟机被编译为机器代码,这是否意味着对象地址的计算会因为运行时算法而降低性能?

【问题讨论】:

  • 这不是一个真正的问题吗?或者也许“这不是一个真正的问题吗?”也不是一个真正的问题?
  • 一方面,您混合了一堆不同的问题。 CPU 能够非常有效地计算地址偏移量。但这与虚拟寻址无关,它处理的是完全不同的问题(主要是为每个进程提供自己的地址空间,并与内存碎片作斗争)。你关于地址偏移是否在编译时解决的问题是完全不同的事情再次。同样,处理器可以非常有效地计算地址,但这发生在运行时,而不是编译时。这可能就是为什么有人投票关闭
  • 只是不清楚您要做什么,您对 CPU 和编译器的期望,以及您知道什么和假设什么。但是,我认为您正在寻找的代码生成方式是lea 指令,它可以计算诸如x[y].z 之类的地址(即偏移一个操作数的某个倍数,加上一个恒定的偏移量)在单个周期(IIRC)中。如果您自己生成代码,请使用它来计算地址偏移量。如果你依赖于一些现有的编译器,你只需要相信它会正确地完成它的工作。 :)
  • 这还不是很清楚。虚拟地址映射是由硬件(通常)完成的,与计算结构的偏移量等无关。您能否添加一个您希望优化的具体示例?
  • 但要回答标题中的问题:“你不能,除非你有时光机”。如何将常数添加到未知值?您知道编译时的偏移量,但您不知道应该添加它的指针值。那么如何将两者相加呢?您必须等到您真正知道要加在一起的两个值。

标签: c++ c offset virtual-memory addressing


【解决方案1】:

这里试图阐明链接的问题和答案如何适用于这种情况。

第一个问题的答案混合了两个不同的东西,第一个是 X86 指令中的寻址模式,第二个是虚拟到物理地址的映射。第一个是由编译器完成的,第二个是(通常)由操作系统设置的。在你的情况下,你应该只担心第一个。

X86 汇编中的指令在访问内存地址方面具有很大的灵活性。读取或写入内存的指令的地址按以下公式计算:

segment + base + index * size + offset

地址的段部分几乎总是默认的DS 段,通常可以忽略。 base 部分由通用寄存器之一或堆栈指针给出。 index 部分由通用寄存器之一给出,大小为 1、2、4 或 8。最后,偏移量是嵌入在指令中的常量值。这些组件中的每一个都是可选的,但显然必须至少给出一个。

这种寻址能力通常是指在没有明确的算术指令的情况下计算地址。其中一位评论者提到了一条特殊指令:LEA,它执行地址计算,但不是读取或写入内存,而是将计算的地址存储在寄存器中。

对于您在问题中包含的代码,编译器很可能会使用这些寻址模式来避免显式算术指令。

例如,instruction 变量的当前值可以保存在ESI 寄存器中。此外,sizeof(parameter1)sizeof(parameter2) 中的每一个都是编译时常量。在标准的 X86 调用约定中,函数参数以相反的顺序推送(因此第一个参数位于堆栈的顶部),因此汇编代码可能看起来像

case1: 
  PUSH [ESI+1]
  CALL fn1
  ADD ESP,4 ; drop arguments from stack
  ADD ESI,5
  JMP end_switch
case2: 
  PUSH [ESI+5]
  PUSH [ESI+1]
  CALL fn2
  ADD ESP,8 ; drop arguments from stack
  ADD ESI,9
  JMP end_swtich
case3:
  MOV ESI,[ESI+1]
  JMP end_switch
end_switch:

这是假设两个参数的大小都是 4 字节。当然,实际代码取决于编译器,只要您要求进行某种级别的优化,编译器就会输出相当高效的代码是合理的。

【讨论】:

    【解决方案2】:

    您在 VM 中有一个数据项 X,位于相对地址 A,还有一条指令(例如)push X,对吗?并且您希望能够执行该指令,而无需将A 添加到 VM 数据区的基地址。

    我已经编写了一个虚拟机,通过将虚拟机的数据区域映射到一个固定的虚拟地址来解决这个问题。编译器知道这个虚拟地址,因此可以在编译时调整A。这个解决方案对你有用吗?可以自己改编译器吗?

    我的虚拟机在智能卡上运行,我可以完全控制操作系统,因此它与您的环境完全不同。但是 Windows 确实有一些用于在固定地址分配内存的工具——例如 VirtualAlloc 函数。你可能想试试这个。如果您尝试一下,您可能会发现 Windows 正在分配与您的固定地址数据区域冲突的区域,因此您可能必须手动加载您使用的任何 DLL,之后分配 VM 的数据区域。

    但可能会有无法预料的问题需要克服,而且可能不值得这么麻烦。

    【讨论】:

      【解决方案3】:

      使用虚拟地址转换、页表或 TLB 只能在操作系统内核级别完成,并且在平台和处理器系列之间不可移植。此外,大多数 CPU ISA 上的硬件地址转换通常仅在某些页面大小的级别上得到支持。

      【讨论】:

      • 很难确定,但我认为这与 OP 想知道的内容无关。尽管提到了虚拟地址,但它似乎与虚拟内存、TLB 和所有这些东西无关。但是......很难确定...... :)
      【解决方案4】:

      根据我得到的许多回答来回答我自己的问题。

      事实证明,在我的情况下,我想要实现的目标实际上是不可能的,只有在满足特定要求并且需要编译为机器特定指令时才能免费获得内存地址计算。

      我正在为教育目的开发一个视觉元素、乐高风格的拖放编程环境,它依赖于一个简单的 VM 来执行程序代码。我希望最大限度地提高性能,但在我的场景中这是不可能的。这没什么大不了的,因为程序元素也可以生成它们的 C 代码等价物,然后可以按照常规方式对其进行编译以最大限度地提高性能。

      感谢所有回复并澄清了我不太清楚的问题的人!

      【讨论】:

        猜你喜欢
        • 2017-08-02
        • 2017-03-10
        • 1970-01-01
        • 2011-06-16
        • 2016-10-18
        • 2023-04-03
        • 2021-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多