我猜这个问题的答案是“这是不可能的,切换到C ++”。但我还以为我会把它扔出去。
我正在处理一个巨大的二叉树。我有一个结构数组来表示我在迭代树时用来帮助处理内存的分支节点。
为了节省一点内存,从而改善缓存局部性,我正在考虑重叠叶节点的对象引用。该对象引用将指向所有叶数据。基本上,这样的事情:
[StructLayout(LayoutKind.Explicit)]
struct BranchData
{
[FieldOffset(0)] // 1 byte
internal byte SplitIndex;
[FieldOffset(1)] // 4 bytes
internal float SplitValue;
[FieldOffset(5)] // 4 bytes
internal int LowIndex;
[FieldOffset(9)] // 4 bytes
internal int HighIndex;
[FieldOffset(0)] // 8 bytes (We're working with x64 here)
internal LeafData Node;
}
上面给出了以下运行时错误
无法从程序集中加载“BranchData”类型 'WindowsFormsApplication1,Version = 1.0.0.0,Culture = neutral, PublicKeyToken = null'因为它包含偏移0处的对象字段 由非对象字段错误对齐或重叠。
我可以使用一个单独的数组来存储叶子数据,并使用索引指向该数组,但后来我有2次内存查找(对于当前远距离的内存区域)。一个用于叶子数组中的位置以获取引用,另一个用于获取叶子数据。如果我可以实现这种重叠,我就可以摆脱其中一种查找。
我能够锁定对象并使用不安全的代码来解决这个问题。速度是这里的关键因素。
答案 0 :(得分:16)
此限制在托管代码中非常重要。问题是您的 Node 成员是一个对象引用。运行时的指针。它与其他字段重叠。
垃圾收集器需要能够找回该指针。必须要知道堆上的LeafData对象有实时引用。并在压缩堆时移动LeafData对象时更新该指针。
问题是:收集器无法判断你的联盟是否存储了指针。如果没有,则存在其他成员的值看起来的风险,就像对GC的有效对象引用一样。这非常非常糟糕。
在技术上可以存储不安全的LeafData *,但这需要固定LeafData对象。当树很大时,这就无法工作了,当没有任何东西可以移动时,GC就会崩溃。将LeafData数据存储在非托管内存中是在兔子洞的下方,你将开始编写C ++代码。你可以做的唯一其他事情是将LeafData存储在节点本身中,作为一个结构,你很可能不满意。
请注意,您应该避免这些未对齐的字段,当字段跨越L1缓存行边界时,您会受到猛烈的攻击。 HighIndex之后放置SplitIndex ,这样就不会发生这种情况。
答案 1 :(得分:1)
我不知道这在实践中是否更快,但它在托管代码中的内存查找次数更少。
(我不知道CLR本身可能有更多的查找。)
也就是说,您可以使用GCHandle
来覆盖具有非托管数据的托管引用:
[StructLayout(LayoutKind.Explicit)]
public struct Data
{
[FieldOffset(0)]
public IntPtr NativeData;
[FieldOffset(0)]
public GCHandle Handle;
}
Data data = ...;
((YourClass)data.Handle.Target).Blah();