1

我目前正在将 C# 中的光线追踪器作为一个爱好项目。我试图通过从 c++ 实现中实现一些技巧来实现不错的渲染速度,但遇到了麻烦。

光线追踪器渲染的场景中的对象存储在 KdTree 结构中,而树的节点又存储在数组中。我遇到问题的优化是在尝试将尽可能多的树节点放入缓存行时。这样做的一种方法是让节点只包含指向左子节点的指针。然后隐含的是,右孩子紧跟在数组中的左孩子之后。

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

我已经尝试了几种方法来将地址固定在内存中,但它们似乎都不能像我需要的那样持续整个应用程序的生命周期。'fixed' 关键字似乎只在单个方法调用期间有帮助,并且声明 'fixed' 数组只能在节点不是的简单类型上完成。有没有一个好的方法可以做到这一点,或者我只是在 C# 不适合的东西的道路上走得太远了。

顺便说一句,改用 c++ 虽然可能是高性能程序的更好选择,但不是一种选择。

4

5 回答 5

4

首先,如果你正常使用C#,你不会因为垃圾收集器移动东西而突然得到一个空引用,因为垃圾收集器也会更新所有引用,所以你不必担心它会移动东西。

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

我从您的帖子中要说的一件事是,使用结构可能不会像您希望的那样提高性能。C# 无法内联任何涉及结构的方法调用,即使他们在最新的运行时测试版中修复了这个问题,结构也经常不能很好地执行。

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

于 2008-09-01T19:14:11.233 回答
2

您的静态内存管理器实际上在做什么?除非它正在做一些不安全的事情(P/Invoke,不安全的代码),否则您看到的行为是程序中的错误,而不是由于 CLR 的行为。

其次,关于结构之间的链接,您所说的“指针”是什么意思?您的字面意思是不安全的 KdTree* 指针吗?不要那样做。相反,使用数组中的索引。由于我希望单个树的所有节点都存储在同一个数组中,因此您不需要单独引用该数组。只需一个索引即可。

最后,如果您真的必须使用 KdTree* 指针,那么您的静态内存管理器应该使用例如 Marshal.AllocHGlobal 或其他非托管内存源分配一个大块;它应该将这个大块视为一个 KdTree 数组(即索引一个 KdTree* C 样式),并且它应该通过碰撞一个“空闲”指针从这个数组中子分配节点。

如果你不得不调整这个数组的大小,那么你当然需要更新所有的指针。

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

如果你更详细地描述你在做什么,你会得到更明智的答案。

于 2008-09-01T21:16:14.810 回答
1

如果你真的想这样做,你可以使用 GCHandle.Alloc 方法来指定一个指针应该被固定,而不是像固定语句那样在作用域的末尾自动释放。

但是,正如其他人所说,这样做会给垃圾收集器施加过大的压力。仅仅创建一个包含一对节点的结构,然后管理一个 NodePairs 数组而不是一个节点数组呢?

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

于 2008-09-01T23:25:33.670 回答
0

存储数组引用和索引对真的令人望而却步吗?

于 2008-09-01T19:08:46.283 回答
0

您的静态内存管理器实际上在做什么?除非它正在做一些不安全的事情(P/Invoke,不安全的代码),否则您看到的行为是程序中的错误,而不是由于 CLR 的行为。

我实际上是在谈论不安全的指针。我想要的是类似的东西Marshal.AllocHGlobal,尽管生命周期超过了单个方法调用。回想起来,似乎只使用索引是正确的解决方案,因为我可能过于沉迷于模仿 c++ 代码。

我从您的帖子中要说的一件事是,使用结构可能不会像您希望的那样提高性能。C# 无法内联任何涉及结构的方法调用,即使他们在最新的运行时测试版中修复了这个问题,结构也经常不能很好地执行。

我对此进行了一些研究,发现它已在 .NET 3.5SP1 中修复;我认为这就是您所说的运行时测试版。事实上,我现在明白,这种变化使我的渲染速度翻了一番。现在,结构被积极地内联,大大提高了它们在 X86 系统上的性能(X64 提前具有更好的结构性能)。

于 2008-09-01T23:08:27.673 回答