我有一大块数据,如果将块视为64位无符号整数数组,则其中某些操作最快,如果将其视为32位无符号整数数组,则其他操作最快。 “最快”,我的意思是运行代码的机器的平均速度最快。我的目标是在运行代码的所有环境中接近最优,我认为如果我使用void指针将其转换为两种类型之一以进行解除引用,这是可能的。这让我想到了我的问题:
1)如果我使用void指针,是否会将其转换为两种类型之一以进行解除引用,而不是直接使用所需类型的指针?
2)我对标准的理解是否正确,这样做不会违反抗锯齿规则,并且不会产生任何未定义或未指定的行为?我使用的32位和64位类型存在且没有填充(这是一个静态断言)。
3)我是否正确理解抗锯齿规则基本上有两个目的:类型安全和编译器保证能够实现优化?如果是这样,如果我正在讨论的代码将被执行的所有情况都没有发生其他解引用,那么我是否可能会对任何重要的编译器优化失去兴趣?
我用'c11'标记了这个,因为我需要从c11标准证明行为是明确定义的。任何对标准的引用都将不胜感激。
最后,我想谈谈在回答中提出的关于“过早优化”的可能问题。首先,这个代码是在一个多样化的计算集群上运行的,性能是至关重要的,而且我知道即使一个指令在解除引用时减速也会很重要。其次,在所有硬件上进行测试需要时间,我不需要完成项目。有许多不同类型的硬件,我现场有限的时间来实际使用硬件。但是,我相信,这个问题的答案将使我能够做出正确的设计选择。
编辑:答案和评论指出这种方法存在别名问题,我直接在c11标准中验证。一组联合需要在32位情况下进行两次地址计算和解除引用,所以我更喜欢数组联合。然后问题变成:
1)使用union成员作为数组而不是指向内存的指针是否存在性能问题?即,工会会员访问是否有成本?请注意,声明指向数组的两个指针违反了抗锯齿规则,因此需要直接通过联合进行访问。
2)当通过一个数组然后通过另一个数组访问时,数组的内容是否保证不变?
答案 0 :(得分:1)
我会避免使用void指针。两个数组或一个联合数组的联合会做得更好。
在整个类型上使用正确的对齐方式。 C11提供alignas()作为关键字。 GCC具有非标准的对齐属性(并且也按照每11个标准工作)。其他编译器可能根本没有。 根据您的体系结构,不应对性能产生影响。但这不能保证(但我没有看到她的问题)。您甚至可以将类型与64位以上的更大类型对齐,以完美填充缓存行。这可能会加速预取和回写。
别名是指一个对象同时由多个指针引用的事实。这意味着可以使用两个不同的“源”来寻址相同的存储器地址。问题是编译器可能不知道这个,因此在某些计算过程中可能会将变量值保存在CPU寄存器中,而不会立即将其写回内存。 如果相同的变量随后由另一个“源”(即指针)引用,则编译器可以从存储器位置读取无效数据。 如果内部有两个指针,则Imo只是在函数内相关的别名。因此,如果您不打算将两个指针传递给同一个对象(或其中的一部分),那么应该没有任何问题。否则,您应该熟悉(编译器)障碍。 编辑: C标准似乎有点严格,因为它只需要访问对象的左值来满足某些标准(C11 6.5 / 7(n1570) - 马特麦克纳布)。
哦,不要使用int / long / etc。如果你真的需要合适大小的类型,你真的应该使用stdint.h类型。
答案 1 :(得分:1)
您的问题有不同的方面。首先,解释不同类型的内存有几个问题:
别名是一个"本地"问题。在函数内部,您不希望指向具有不同目标类型的同一对象。如果您确实修改了这样的指向对象,编译器可能会假装不知道该对象可能已经更改并错误地优化您的程序。如果你不是在一个函数中做到这一点(例如在开头做一个演员并保持这种解释)你应该没有别名。
现在,对齐问题经常被忽视,因为许多处理器现在对对齐问题非常宽容,但这不是便携式的,也可能会对性能产生影响。因此,您必须确保您的阵列以适合您访问它的所有类型的方式对齐。这可以在C11中使用_Alignas
来完成,较旧的编译器也具有允许这样做的扩展。 C11对aligment添加了一些限制,例如,这总是2的幂,这使你能够编写关于这个问题的可移植代码。
目前,整数类型填充是很少见的(只有例外_Bool
)但是要确保你应该使用已知不会出现问题的类型。在您的情况下,这些是[u]int32_t
和[u]int64_t
,已知它们具有所请求的位数,并且具有已签名类型的两个补码表示。如果某个平台不支持它们,那么您的程序将无法编译。