面向游戏开发人员的 64 位编程

处理器制造商在其台式计算机中专门提供 64 位处理器,甚至大多数笔记本电脑的芯片集都支持 x64 技术。 游戏开发人员必须利用 64 位处理器在其新应用程序中提供的改进,并确保其早期应用程序在新处理器和 64 位版本的 Windows Vista 和 Windows 7 上正确运行。 本文介绍兼容性和移植问题,并帮助开发人员轻松过渡到 64 位平台。

Microsoft 当前具有以下 64 位操作系统:

  • Windows 10
  • Windows 11
  • Windows Server 2019 或更高版本

过去的 64 位操作系统:

  • Windows Server 2003 Service Pack 1
  • WINDOWS XP Professional x64 Edition (通过 MSDN 向 OEM 和开发人员提供)
  • Windows Vista
  • Windows 7
  • Windows 8.0
  • Windows 8.1
  • Windows Server 2008 - 2016

注意

Windows Server 2008 R2 或更高版本仅作为 64 位版本提供。 Windows 11仅以 64 位或 ARM64 版本的形式提供。

 

可寻址内存的差异

大多数开发人员注意到的第一件事是,64 位处理器在物理内存和虚拟内存量方面提供了巨大的飞跃,可以解决。

  • 32 位平台上的 32 位应用程序最多可以处理 2 GB。

  • 在具有特殊 /3gb 启动选项的 32 位 Windows XP 或 Windows Server 2003 上使用 /LARGEADDRESSAWARE:YES 链接器标志构建的 32 位应用程序最多可以处理 3 GB。 这会将内核限制为 1 GB,这可能会导致某些驱动程序和/或服务失败。

  • 在 32 位版本的 Windows Vista、Windows Server 2008 和 Windows 7 上使用 /LARGEADDRESSAWARE:YES 链接器标志构建的 32 位应用程序最多可以寻址内存,最多可以寻址由启动配置数据 (BCD) 元素 IncreaseUserVa 指定的数量。 IncreaseUserVa 的值可以是 2048(默认值)到 3072 (该值与 Windows XP) 上 /3gb 启动选项配置的内存量相匹配。 剩余的 4 GB 将分配给内核,并可能导致驱动程序和服务配置失败。

    有关 BCD 的详细信息,请参阅 启动配置数据

  • 64 位平台上的 32 位应用程序可以使用 /LARGEADDRESSAWARE:YES 链接器标志处理最多 2 GB 或 4 GB。

  • 64 位应用程序使用 43 位进行寻址,这为应用程序提供 8 TB 的虚拟地址,为内核保留 8 TB 的虚拟地址。

除了内存,使用内存映射文件 I/O 的 64 位应用程序还受益于增加的虚拟地址空间。 64 位体系结构还改进了浮点性能和更快的参数传递速度。 64 位处理器的寄存器数是常规用途和流式 SIMD 扩展 (SSE) 类型的两倍,以及对 SSE 和 SSE2 指令集的支持;许多 64 位处理器甚至支持 SSE3 指令集。

在生成时指定大地址感知

生成 32 位应用程序时,使用链接器标志 /LARGEADDRESSAWARE 指定大地址感知是一种很好的做法,即使应用程序不用于 64 位平台,因为免费获得的优势也是如此。 如前所述,为生成启用此标志允许 32 位程序在 32 位 OS 或 64 位操作系统上使用特殊启动选项访问更多内存。 但是,开发人员必须小心不要进行指针假设,例如假设高位永远不会在 32 位指针中设置。 通常,启用 /LARGEADDRESSAWARE 标志是一种很好的做法。

大地址感知的 32 位应用程序可以通过调用 GlobalMemoryStatusEx 在运行时通过当前 OS 配置确定可用的虚拟地址空间总量。 ullTotalVirtual 结果的范围从 2147352576 个字节 (2 GB) 到 4 GB) (4294836224 个字节。 大于 3221094400 (3 GB) 的值只能在 64 位版本的 Windows 上获取。 例如,如果 IncreaseUserVa 的值为 2560,则结果为 ullTotalVirtual,值为 2684223488 字节。

64 位平台上 32 位应用程序的兼容性

64 位 Windows 操作系统与 IA32 体系结构二进制兼容,32 位应用程序使用的大多数 API 都可通过 Windows 64 位模拟器上的 Windows 32 位 WOW64 提供。 WOW64 有助于确保这些 API 按预期工作。

WOW64 具有处理 32 位数据的封送的执行层。 WOW64 重定向 DLL 文件请求,重定向 32 位应用程序的一些注册表分支,并反映 32 位和 64 位应用程序的一些注册表分支。

有关 WOW64 的详细信息,请参阅 WOW64 实现详细信息

潜在的兼容性缺陷

为 32 位平台开发的大多数应用程序将在 64 位平台上运行,不会出现问题。 一些应用程序可能存在问题,其中可能包括:

  • 64 位版本的 Windows 操作系统的所有驱动程序都必须是 64 位版本。 要求新的 64 位驱动程序会对依赖旧驱动程序的复制保护方案产生影响。 请注意,内核模式驱动程序必须经过验证码签名才能由 64 位版本的 Windows 加载。
  • 64 位进程无法加载 32 位 DLL,32 位进程无法加载 64 位 DLL。 在继续开发之前,开发人员必须确保第三方 DLL 的 64 位版本可用。 如果必须在 64 位进程中使用 32 位 DLL,则可以使用 Windows 进程间通信 (IPC) 。 COM 组件还可以利用进程外服务器和封送处理在边界之间进行通信,但这样做可能会造成性能损失。
  • 许多 x64 处理器也是多核处理器,开发人员需要测试这如何影响其旧版应用程序。 有关多核处理器及其对游戏应用程序的影响的详细信息,请参阅 游戏计时和多核处理器
  • 应用程序还应调用 SHGetFolderPath 来发现文件路径,因为某些文件夹名称在某些情况下已更改。 例如,CSIDL_PROGRAM_FILES将为在 64 位平台上运行的 32 位应用程序返回“C:\Program Files (x86) ”,而不是“C:\Program Files”。 开发人员必须注意 WOW64 模拟器的重定向和反射功能的工作原理。

此外,开发人员需要警惕他们可能仍在使用的 16 位程序。 WOW64 无法处理 16 位应用程序;这包括旧的安装程序和所有 MS-DOS 程序。

注意

最常见的兼容性问题是执行 16 位代码的安装程序,并且没有用于复制保护方案的 64 位驱动程序。

 

下一部分讨论与将代码移植到 64 位本机相关的问题,面向希望确保其旧程序在 64 位平台上工作的开发人员。 它还适用于不熟悉 64 位编程的开发人员。

将应用程序移植到 64 位平台

拥有正确的工具和库将有助于简化从 32 位开发到 64 位开发的过渡。 DirectX 9 SDK 具有支持基于 x86 和 x64 的项目的库。 Microsoft Visual Studio 2005 和 Visual Studio 2008 支持 x86 和 x64 的代码生成,它们附带了针对生成 x64 代码进行了优化的库。 但是,开发人员也需要将 Visual C 运行时与其应用程序一起分发。 请注意,Visual Studio 2005 和 Visual Studio 2008 的 Express 版本不包括 x64 编译器,但 Standard、Professional 和 Team System 版本都包含。

面向 32 位平台的开发人员可以准备 64 位开发,以便以后更轻松地过渡。 编译 32 位项目时,开发人员应使用 /Wp64 标志,这将导致生成有关影响可移植性的问题的警告。 切换到 64 位工具和库最初可能会生成大量新的生成错误;因此,建议在切换到 64 位生成之前,切换非特定位工具和库并更正任何警告。

不过,更改工具、更改库和使用某些编译器标志是不够的。 必须重新评估编码标准中的假设,以确保当前编码标准不允许可移植性问题。 可移植性问题可能包括指针截断、数据类型的大小和对齐方式、对 32 位 DLL 的依赖、旧 API 的使用、程序集代码和旧的二进制文件。

注意

Visual C++ 2010 或更高版本包括 stdint.h 和 cstdint C99 标头,它们定义标准可移植性类型int32_t、uint32_t、int64_t、uint64_t、intptr_t和uintptr_t。 将这些类型与标准ptrdiff_t和size_t数据类型一起使用可能比下面用于提高代码可移植性的 Windows 移植类型更可取。

 

主要移植问题包括:

指针截断

指针在 64 位操作系统上为 64 位,因此将指针强制转换为其他数据类型可能会导致截断,而指针算术可能会导致损坏。 使用 /Wp64 标志通常会提供有关此类问题的警告,但在强制转换指针类型时使用多态类型 (INT_PTR、DWORD_PTR、SIZE_T、UINT_PTR等) 是帮助完全避免此问题的好做法。 由于指针在新平台上是 64 位的,因此开发人员应检查指针的顺序以及类和结构中的数据类型,以减少或消除填充。

数据类型和二进制文件

虽然指针在 64 位平台上从 32 位增加到 64 位,但其他数据类型则不会。 固定精度数据类型 (DWORD32、DWORD64、INT32、INT64、LONG32、LONG64、UINT32、UINT64) 可用于数据类型大小必须已知的地方;例如,在二进制文件结构中。 指针大小和数据对齐方式的变化需要特殊处理,以确保 32 位到 64 位的兼容性。 有关详细信息,请参阅 新数据类型

旧版 Win32 API 和数据对齐

一些 Win32 API 已弃用,并替换为更中性的 API 调用,例如 SetWindowLongPtr 代替 SetWindowLong。

在 x64 平台上,不一致访问的性能损失比在 x86 平台上更大。 TYPE_ALIGNMENT (t) 和FIELD_OFFSET (t 成员) 宏可用于确定代码可以直接使用的对齐信息。 正确使用这些上述宏应消除潜在的不一致访问处罚。

有关TYPE_ALIGNMENT宏、FIELD_OFFSET宏和常规 64 位编程信息的详细信息,请参阅 64 位 Windows 编程:迁移提示:使用指针的其他注意事项和规则。

程序集代码

内联程序集代码在 64 位平台上不受支持,需要替换。 体系结构中的更改可能改变了应用程序瓶颈,C/C++ 或内部函数可以使用更易于阅读的代码获得类似的结果。 最建议的做法是将所有程序集代码切换到 C 或 C++。 内部函数可用于代替程序集代码,但应仅在执行完整分析和分析后使用。

x87、MMX 和 3DNow! 指令集在 64 位模式下已弃用。 指令集仍然存在,以便向后兼容 32 位模式;但是,为了避免将来出现兼容性问题,不建议在当前和将来的项目中使用它们。

已弃用的 API

对于 64 位本机应用程序,已删除一些较旧的 DirectX API:DirectPlay 4 及更早版本、DirectDraw 6 及更早版本、Direct3D 8 及更早版本以及 DirectInput 7 及更早版本。 此外,DirectMusic 的核心 API 可用于本机 64 位应用程序,但性能层和 DirectMusic 生成者已弃用。

Visual Studio 发出弃用警告,这些更改对于使用最新 API 的开发人员来说不是问题。

已移植应用程序的分析和优化

所有开发人员都需要重新分析要移植到新体系结构的任何应用程序。 许多移植到 64 位平台的应用程序的性能配置文件与 32 位版本不同。 在评估需要优化的内容之前,开发人员需要运行 64 位性能测试。 好消息是,许多传统优化适用于 64 位平台。 此外,64 位编译器还可以通过正确使用编译器标志和编码提示来执行许多优化。

某些结构可能会对其内部数据类型重新排序,以节省内存空间并改进缓存。 在某些情况下,可以使用数组索引而不是完整的 64 位指针。 /fp:fast 标志可以改进浮点优化和矢量化。 使用 __restrict,declspec (限制) ,而 declspec (noalias) 可以帮助编译器解决别名问题并改进对寄存器文件的使用。

有关 /fp:fast 的详细信息,请参阅 /fp (指定Floating-Point行为)

有关__restrict的详细信息,请参阅 Microsoft 特定修饰符

有关 declspec (限制) 的详细信息,请参阅 优化最佳做法

有关 declspec (noalias) 的详细信息,请参阅 __declspec (noalias)

64 位操作系统上的托管代码

许多游戏开发人员在其工具链中使用托管代码,因此了解它在 64 位操作系统上的行为方式可能会有所帮助。 托管代码是指令集中性的,因此,在 64 位 OS 上运行托管应用程序时,公共语言运行时 (CLR) 可以将其作为 32 位或 64 位进程运行。 默认情况下,CLR 以 64 位方式运行托管应用程序,并且它们应该正常运行,且不会出现问题。 但是,如果应用程序依赖于本机 32 位 DLL,则应用程序在尝试调用此 DLL 时将失败。 64 位进程需要完全 64 位代码,并且无法从 64 位进程调用 32 位 DLL。 最佳长期解决方案也是将本机代码编译为 64 位,但完全合理的短期解决方案是仅使用 /platform:x86 生成标志将托管应用程序标记为适用于 x86。

运行 64 位操作系统的性能影响

由于采用 AMD64 和 Intel 64 体系结构的处理器可以本机执行 32 位指令,因此它们可以全速运行 32 位应用程序,即使在 64 位操作系统上也是如此。 调用操作系统函数时,转换 32 位和 64 位之间的参数的成本适中,但这种成本通常可以忽略不计。 这意味着,在 64 位操作系统上运行 32 位应用程序时,不会减慢速度。

将应用程序编译为 64 位时,计算会变得更加复杂。 64 位程序使用 64 位指针,其指令稍大,因此内存需求略有增加。 这可能会导致性能略有下降。 另一方面,拥有两倍的寄存器,并且能够在单个指令中执行 64 位整数计算,通常会得到补偿。 最终结果是,64 位应用程序的运行速度可能比编译为 32 位的同一应用程序慢一些,但它的运行速度通常略快一些。

总结

64 位体系结构使开发人员能够限制游戏的外观、声音和游戏方式。 但是,从 32 位编程过渡到 64 位编程并非易事。 通过了解两者之间的差异,并使用最新工具,可以更轻松、更快地过渡到 64 位平台。

有关 64 位编程的详细信息,请参阅 Visual C++ 开发人员中心:64 位编程