为什么写入24位结构不是primefaces的(当写入32位结构时)?

我是一个修补匠 – 毫无疑问。 出于这个原因(并且除此之外),我最近做了一个小实验来证实我怀疑写入struct不是primefaces操作,这意味着所谓的“不可变”值类型试图强制执行某些约束可能会假设失败了。

我使用以下类型作为插图写了一篇关于此的博客文章 :

 struct SolidStruct { public SolidStruct(int value) { X = Y = Z = value; } public readonly int X; public readonly int Y; public readonly int Z; } 

虽然上面看起来X != YY != Z永远不会是真的类型,但事实上,如果值是“中间分配”,同时它被复制到另一个位置, 则会发生这种情况。一个单独的线程。

好的,很重要。 好奇心和更多。 但后来我有这种预感:我的64位CPU实际上应该能够primefaces地复制64位,对吧? 那么如果我摆脱了Z而只是坚持使用XY呢? 那只是64位; 应该可以一步覆盖它们。

果然,它奏效了。 (我意识到你们中的一些人现在可能正在皱起眉头,想着, 是啊呃。这怎么有趣?幽默我。)当然,我不知道这是否得到保证或者不给我的系统。 我对寄存器,缓存未命中等几乎一无所知(我实际上只是在不理解其含义的情况下反驳了我听过的术语); 所以现在这对我来说都是一个黑盒子。

接下来我再次尝试,只是在预感 – 是一个由32位使用2个short字段组成的结构。 这似乎也表现出“primefaces可转让性”。 但后来我尝试使用3 byte字段的24位结构: 不行

突然,结构似乎再次受到“中期任务”副本的影响。

低至16位,2 byte字段:primefaces再次!

有人可以向我解释为什么会这样吗? 我听说过“bit packing”,“cache line straddling”,“alignment”等等 – 但是我再也不知道这意味着什么,也不知道它是否与此相关。 但我觉得我看到了一种模式,却无法确切地说出它是什么; 清晰度将不胜感激。

您正在寻找的模式是CPU的本机字大小。

从历史上看,x86系列本身使用16位值(之前是8位值)。 因此,您的CPU可以primefaces地处理这些:它是设置这些值的单个指令。

随着时间的推移,本机元素大小增加到32位,后来增加到64位。 在每种情况下,都添加了一条指令来处理这个特定的位数。 但是,为了向后兼容,旧的指令仍然保留,因此您的64位处理器可以使用所有以前的原生大小。

由于您的struct元素存储在连续的内存中(没有填充,即空的空间),运行时可以利用这些知识仅为这些大小的元素执行该单个指令。 简而言之,这会产生您所看到的效果,因为CPU一次只能执行一条指令(尽管我不确定在多核系统上是否可以保证真正的primefaces性)。

但是,本机元素大小从不是24位。 因此,没有单个指令可以写入24位,因此需要多个指令,并且会失去primefaces性。

C#标准( ISO 23270:2006 , ECMA-334 )对primefaces性有这样的说法:

12.5变量引用的primefaces性以下数据类型的读写应该是primefaces的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference类型。 此外,在前一个列表中具有基础类型的枚举类型的读取和写入也应该是primefaces的。 其他类型的读写,包括long,ulong,double和decimal,以及用户定义的类型,不必是primefaces的。强调我的 )除了为此目的设计的库函数之外,不保证primefaces读 – 修改 – 写,例如在递增或递减的情况下。

您的示例X = Y = Z = value是3个单独的赋值操作的简写,每个操作都被12.5定义为primefaces。 3个操作的顺序(将value赋值给Z ,将Z赋值给Y ,将Y赋值给X并不保证是primefaces的。

由于语言规范不要求primefaces性,而X = Y = Z = value; 可能是一个primefaces操作,无论它是否依赖于一大堆因素:

  • 编译器编写者的一时兴起
  • 在构建时选择了哪些代码生成优化选项(如果有)
  • JIT编译器的细节,负责将程序集的IL转换为机器语言。 比如,在Mono下运行的IL可能表现出与在.Net 4.0下运行时不同的行为(甚至可能与早期版本的.Net不同)。
  • 运行程序集的特定CPU。

人们可能还会注意到,即使单个机器指令也不一定是primefaces操作 – 许多都是可中断的。

此外,访问CLI标准( ISO 23217:2006 ),我们发现第12.6.6节:

12.6.6primefaces读取和写入符合要求的CLI应保证对所有写入的读取和写入访问正确对齐的内存位置不大于本机字大小( native int类型的大小)是primefaces的(参见§12.6.2)对某个位置的访问大小相同。 primefaces写入除了写入之外不得改变任何位。 除非使用显式布局控件(请参阅分区II(控制实例布局))来更改默认行为,否则应正确对齐不大于自然字大小(本native int的大小)的数据元素。 对象引用应被视为存储在本机字大小中。

[ 注意:不保证内存的primefaces更新(读 – 修改 – 写),除了为此目的提供的方法作为类库的一部分(参见Partition IV)。强调我的 )在不支持直接写入小数据项的硬件上进行primefaces读取/修改/写入时,需要对“小数据项”(不大于本机字大小的项)进行primefaces写入。 结束说明 ]

[ 注意:当native int的大小为32位时,没有保证对8字节数据的primefaces访问,即使某些实现可能在数据在8字节边界上对齐时执行primefaces操作。 结束说明 ]

x86 CPU操作以8位,16位,32位或64位进行; 操纵其他尺寸需要多次操作。

编译器和x86 CPU将要小心移动与结构定义的字节数完全相同的字节数。 没有x86指令可以在一次操作中移动24位,但是有8,16,32和64位数据的单指令移动。

如果向24位结构添加另一个字节字段(使其成为32位结构),您应该看到primefaces性返回。

有些编译器允许您在结构上定义填充,使它们的行为类似于本机寄存器大小的数据。 如果填充24位结构,编译器将添加另一个字节,将大小“舍入”为32位,以便整个结构可以在一条primefaces指令中移动。 缺点是你的结构总是占用内存空间的30%。

请注意,内存中结构的对齐对primefaces性也很重要。 如果多字节结构不是在对齐的地址处开始,则它可能跨越CPU高速缓存中的多个高速缓存行。 即使操作码是单个移动指令,读取或写入该数据也需要多个时钟周期和多个读/写。 因此,如果数据未对齐,即使单个指令移动也可能不是primefaces的。 即使在多核系统中,x86也能确保对齐边界上的原生大小读/写的primefaces性。

使用x86 LOCK前缀可以通过多步移动实现内存primefaces性。 但是应该避免这种情况,因为它在多核系统中可能非常昂贵(LOCK不仅阻止其他内核访问内存,它还会在操作期间锁定系统总线,这会影响磁盘I / O和video操作.LOCK可能也迫使其他核心清除他们的本地缓存)