c# - 为什么更新结构数组比更新类数组更快?

标签 c# .net performance optimization jit

关闭。这个问题需要更多 focused .它目前不接受答案。












想改进这个问题?更新问题,使其仅关注一个问题 editing this post .

6年前关闭。




Improve this question




为了准备对现有软件框架的优化,我进行了独立的性能测试,这样我就可以在花费大量时间之前评估潜在的 yield 。

情况

N不同类型的组件,其中一些实现了 IUpdatable界面 - 这些是有趣的。它们被分组在 M对象,每个对象都维护一个组件列表。更新它们的工作方式如下:

foreach (GroupObject obj in objects)
{
    foreach (Component comp in obj.Components)
    {
        IUpdatable updatable = comp as IUpdatable;
        if (updatable != null)
            updatable.Update();
    }
}

优化

我的目标是针对大量分组对象和组件优化这些更新。首先,确保通过将它们缓存在每种类型的一个数组中来连续更新一种类型的所有组件。本质上,这是:
foreach (IUpdatable[] compOfType in typeSortedComponents)
{
    foreach (IUpdatable updatable in compOfType)
    {
        updatable.Update();
    }
}

其背后的想法是 JIT 或 CPU 可能比在 shuffled 版本中更容易一遍又一遍地操作相同的对象类型。

在下一步中,我想通过确保一个 Component 类型的所有数据在内存中对齐来进一步改善这种情况 - 通过将其存储在结构数组中,如下所示:
foreach (ComponentDataStruct[] compDataOfType in typeSortedComponentData)
{
    for (int i = 0; i < compDataOfType.Length; i++)
    {
        compDataOfType[i].Update();
    }
}

问题

在我的独立性能测试中,这些更改中的任何一个都没有显着的性能提升。我不确定为什么。没有显着的性能提升意味着,对于 10000 个组件,每批运行 100 个更新周期,所有主要测试大约需要 85 毫秒 +/- 2 毫秒。

(唯一的区别在于引入 as 类型转换和 if 检查,但这并不是我真正测试的内容。)
  • 所有测试均在 Release模式下执行,没有附加调试器。
  • 使用以下代码减少了外部干扰:
        currentProc.ProcessorAffinity = new IntPtr(2);
        currentProc.PriorityClass = ProcessPriorityClass.High;
        currentThread.Priority = ThreadPriority.Highest;
    
  • 每个测试实际上都做了一些原始的数学工作,所以它不仅仅是测量可能被优化掉的空方法调用。
  • 每次测试之前都会明确执行垃圾收集,以排除干扰。
  • 完整的源代码(VS 解决方案,构建和运行)可用 here

  • 由于内存对齐和更新模式的重复,我预计会发生重大变化。所以,我的核心问题是:为什么我无法衡量显着的改进? 我忽略了一些重要的事情吗?我在测试中遗漏了什么吗?

    最佳答案

    传统上您可能更喜欢后一种实现的主要原因是 Locality of Reference .如果数组的内容适合 CPU 缓存,那么您的代码运行速度会快很多。相反,如果您有很多缓存未命中,那么您的代码运行速度就会慢得多。

    我怀疑您的错误是您的第一个测试中的对象可能已经具有良好的引用位置。 If you allocate a lot of small objects all at once, those objects are likely to be contiguous in memory even though they're on the heap. (我正在为此寻找更好的来源,但我在自己的工作中也经历过同样的事情)即使它们还不是连续的,GC 也可能会移动它们以使其成为。由于现代 CPU 具有大型缓存,因此可能整个数据结构都适合 L2 缓存,因为没有太多其他东西可以与之竞争。即使缓存不大,现代 CPU 已经非常擅长预测使用模式和预取。

    也可能是您的代码必须对您的结构进行装箱/拆箱。但是,如果性能真的如此相似,这似乎不太可能。

    在 C# 中使用此类低级内容的重要之处在于,您确实需要 a) 信任框架来完成其工作,或者 b) 在确定低级性能问题后在实际条件下进行分析。我很欣赏这可能是一个玩具项目,或者您可能只是在玩弄内存优化以获得傻笑,但是您在 OP 中所做的先验优化不太可能在项目规模上产生明显的性能改进。

    我还没有详细阅读你的代码,但我怀疑你的问题是不切实际的条件。随着内存压力的增加,尤其是组件的动态分配,您可能会看到预期的性能差异。再说一次,你可能不会,这就是为什么配置文件如此重要。

    值得注意的是,如果您事先确定严格手动优化内存局部性对于应用程序的正确功能至关重要,您可能需要考虑托管语言是否是该工作的正确工具。

    编辑:是的,问题几乎肯定在这里: -

    public static void PrepareTest()
    {
      data = new Base[Program.ObjCount]; // 10000
      for (int i = 0; i < data.Length; i++)
        data[i] = new Data(); // Data consists of four floats
    }
    
    Data 的那 10,000 个实例在内存中可能是连续的。此外,无论如何,它们可能都适合您的缓存,因此我怀疑您是否会在此测试中看到缓存未命中对性能的任何影响。

    关于c# - 为什么更新结构数组比更新类数组更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31231802/

    相关文章:

    android - 以编程方式创建按钮或在 xml 中创建按钮之间是否存在性能差异?

    performance - 慈善机构如何衡量捐赠的 CPU 使用率?

    c# - 如何覆盖 UWP 中的文件?

    c# - .NET windows 窗体应用程序 self 更新的最佳方式是什么?

    .net - 列表、数组或其他什么?

    c# - 同步等待一个异步操作,Wait()为什么会在这里卡住程序

    c# - Navision 自动化 C# COM DLL

    c# - 函数中数组参数长度的默认参数值

    c# - 快速功能可根据键返回字符串,反之亦然

    mysql - 单表层次结构的性能问题