union 访问成本与使用基本类型

标签 c performance void-pointers dereference c11

我有一个大数据 block ,如果将该 block 视为 64 位无符号整数数组,则某些操作会最快,而如果将其视为 32 位无符号整数数组,则其他操作会最快。 “最快”是指运行代码的机器平均最快。我的目标是在运行代码的所有环境中都接近最佳,我认为如果我使用 void 指针,将其转换为两种类型之一以进行取消引用,这是可能的。这让我想到了我的问题:

1) 如果我使用 void 指针,将其转换为两种类型之一以进行解引用是否会比直接使用所需类型的指针慢?

2) 我对标准的理解是否正确,即这样做不会违反抗锯齿规则,并且不会产生任何未定义或未指定的行为?我使用的 32 位和 64 位类型存在并且没有填充(这是静态断言)。

3) 我是否正确理解抗锯齿规则基本上服务于两个目的:类型安全和编译器保证启用优化?如果是这样,如果我正在讨论的代码将被执行的所有情况都不会发生其他取消引用,我是否可能会放弃任何重要的编译器优化?

我已将其标记为“c11”,因为我需要根据 c11 标准证明该行为已明确定义。任何对该标准的引用都将不胜感激。

最后,我想解决一个可能会在响应中提出的问题,即“过早优化”。首先,这段代码正在不同的计算集群上运行,如果性能至关重要,而且我知道即使是取消引用中的一条指令减速也会很重要。其次,在所有硬件上测试它需要时间,我不必完成这个项目。有很多不同类型的硬件,我在现场实际使用硬件的时间有限。不过,我相信这个问题的答案无论如何都能让我做出正确的设计选择。

编辑:答案和评论指出这种方法存在别名问题,我直接在 c11 标准中验证了这一点。在 32 位情况下, union 数组需要两次地址计算和取消引用,因此我更喜欢数组 union 。那么问题就变成了:

1) 将 union 成员用作数组而不是指向内存的指针是否存在性能问题?即, union 成员访问是否有成本?请注意,声明两个指向数组的指针违反了抗锯齿规则,因此需要直接通过 union 进行访问。

2) 当通过一个数组然后通过另一个数组访问时,数组的内容是否保证不变?

最佳答案

您的问题有不同的方面。首先,解释不同类型的内存有几个问题:

  • 别名
  • 对齐
  • 填充

别名是一个“本地”问题。在函数内部,您不希望拥有指向具有不同目标类型的同一对象的指针。如果您确实修改了此类指向的对象,编译器可能会假装不知道该对象可能已更改并错误地优化您的程序。如果您不在函数内部执行此操作(例如,在开头进行强制转换并保留该解释),您应该可以使用别名。

现在对齐问题经常被忽视,因为现在许多处理器对对齐问题都相当宽容,但这不是可移植的,而且可能还会影响性能。因此,您必须确保您的数组以适合您访问它的所有类型的方式对齐。这可以通过 C11 中的 _Alignas 来完成,旧版编译器的扩展也允许这样做。 C11 对对齐添加了一些限制,例如,这始终是 2 的幂,这应该使您能够针对此问题编写可移植代码。

整数类型填充现在很少见(唯一的异常(exception)是 _Bool),但要确保您应该使用已知不会有问题的类型。在您的情况下,这些是 [u]int32_t[u]int64_t ,它们已知具有完全请求的位数并且具有符号类型的二进制补码表示。如果平台不支持它们,您的程序将无法编译。

关于 union 访问成本与使用基本类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29760656/

相关文章:

c - 错误 C2036 : 'void *' : unknown size

c - 在两个值之间切换整数变量的最简单方法

php - 从表中选择条件 1 或反转条件 1 的有效方法

c - 在 C 中将 const 赋值给非常量

C函数多个结果多个类型

performance - Golang - go run 需要很长时间才能执行

.net - Linux 用户应该使用什么 IDE/编译器组合来在 Windows 上构建 Qt 应用程序,同时避免使用 MS Visual Studio?

c - C 中与 Windows 10 的虚拟端口通信

c - 函数声明与原型(prototype)的替代 (K&R) C 语法

performance - wglGetCurrentContext 是否同步 GPU 和 CPU?