【问题标题】:Pinning pointer arrays in memory将指针数组固定在内存中
【发布时间】:2010-09-07 11:42:28
【问题描述】:

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

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

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

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

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

【问题讨论】:

    标签: c# optimization unsafe raytracing


    【解决方案1】:

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

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

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

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

    【讨论】:

      【解决方案2】:

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

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

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

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

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

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

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

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

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2019-05-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-12-10
              • 2011-12-04
              • 1970-01-01
              相关资源
              最近更新 更多