将指针数组固定在内存中

时间:2020-03-05 18:46:05  来源:igfitidea点击:

我目前正在Cas的一个业余项目中研究射线追踪器。我正在尝试通过实现c ++实现中的一些技巧来实现不错的渲染速度,并且遇到了麻烦。

光线跟踪器渲染的场景中的对象存储在KdTree结构中,而树的节点又存储在数组中。我遇到的优化是在尝试将尽可能多的树节点放入高速缓存行时。一种方法是让节点仅包含指向左子节点的指针。然后隐式地认为,右边的子项紧跟在数组中左边的子项之后。

节点是结构,在树的构造过程中,它们被静态内存管理器类成功地放入数组。当我开始遍历树时,一开始它似乎工作得很好。然后,在渲染的早期(每次大约在同一位置),根节点的左子指针突然指向空指针。我得出的结论是,当数组位于堆上时,垃圾收集器已经移动了结构。

我已经尝试了几种方法来将地址固定在内存中,但是似乎没有一种方法可以在我需要的整个应用程序生命周期中持续存在。 'fixed'关键字似乎仅在单方法调用期间有所帮助,声明'fixed'数组只能在节点没有的简单类型上完成。有没有很好的方法可以做到这一点,或者我只是在Cn所不打算做的事情上走得太远。

顺便说一句,改用c ++也许不是一个高性能程序的更好选择,但是不是一个选择。

解决方案

回答

存储一对数组引用和索引真的禁止吗?

回答

首先,如果我们使用的是Cnormal,由于垃圾收集器移动了东西,我们不会突然得到null引用,因为垃圾收集器还会更新所有引用,因此我们不必担心它会四处移动。

我们可以将东西固定在内存中,但这可能会导致更多的问题而不是解决的。一方面,它阻止垃圾收集器正确压缩内存,并可能以这种方式影响性能。

我从帖子中说的一件事是,使用结构可能无法如我们所愿地帮助提高性能。 Cfa不能内联涉及结构的任何方法调用,即使他们已在最新的运行时Beta中修复了此问题,结构也常常无法很好地执行此操作。

就个人而言,我会说像这样的C ++技巧通常不会很好地延续到C#中。我们可能需要学会放手。可能还有其他更微妙的方法来提高性能;)

回答

静态内存管理器实际上在做什么?除非它执行了不安全的操作(P / Invoke,不安全的代码),否则我们看到的行为是程序中的错误,而不是由于CLR的行为所致。

其次,关于结构之间的链接,"指针"是什么意思?字面意思是不安全的KdTree *指针吗?不要那样做而是使用数组索引。因为我希望一棵树的所有节点都存储在同一数组中,所以我们不需要对数组的单独引用。只需一个索引即可。

最后,如果我们确实必须使用KdTree *指针,则静态内存管理器应使用例如Marshal.AllocHGlobal或者其他不受管的内存源;它应该将这个大块都视为KdTree数组(即为KdTree * C样式编制索引),并且应该通过碰到"空闲"指针从该数组中分配节点。

如果我们必须调整此数组的大小,那么我们当然需要更新所有指针。

这里的基本教训是,不安全的指针和托管内存不会在``固定''的块之外混合使用,而固定的块当然具有堆栈帧亲和力(即,当函数返回时,固定的行为就会消失)。有一种方法可以使用GCHandle.Alloc(yourArray,GCHandleType.Pinned)来固定任意对象,例如数组,但是我们几乎肯定不想走这条路。

如果我们更详细地描述自己在做什么,将会得到更明智的答案。

回答

What is your static memory manager actually doing? Unless it is doing something unsafe (P/Invoke, unsafe code), the behaviour you are seeing is a bug in your program, and not due to the behaviour of the CLR.

我实际上是在谈论不安全的指针。我想要的是类似" Marshal.AllocHGlobal"的东西,尽管生命周期超过了单个方法调用的时间。反思一下,似乎只使用索引是正确的解决方案,因为我可能太想模仿C ++代码了。

One thing I would say from your post is that using structs may not help performance as you hope. C# fails to inline any method calls involving structs, and even though they've fixed this in their latest run-time beta, structs frequently don't perform that well.

我仔细研究了一下,发现它已在.NET 3.5SP1中修复。我假设这就是我们所说的运行时Beta。实际上,我现在知道,此更改使我的渲染速度提高了一倍。现在,结构被积极地内联,大大提高了它们在X86系统上的性能(X64预先具有更好的结构性能)。

回答

如果确实要执行此操作,则可以使用GCHandle.Alloc方法来指定应该固定指针,而不必像fixed语句那样在范围的末尾自动释放指针。

但是,正如其他人所说的那样,这样做给垃圾收集器施加了不适当的压力。只创建一个可以容纳一对节点的结构,然后管理一个NodePairs数组而不是一个节点数组,该怎么办?

如果我们确实确实希望完全不受托管地访问一块内存,那么最好直接从非托管堆中分配内存,而不是永久固定一部分托管堆(这会阻止堆正常运行)。压缩自身)。一种快速而简单的方法是使用Marshal.AllocHGlobal方法。