【问题标题】:What optimization hints can I give to the compiler/JIT?我可以向编译器/JIT 提供哪些优化提示?
【发布时间】:2016-05-31 02:11:41
【问题描述】:

我已经进行了概要分析,现在正寻求从我的热点中挤出所有可能的性能。

我知道[MethodImplOptions.AggressiveInlining]ProfileOptimization class。还有其他的吗?


[编辑] 我刚刚也发现了[TargetedPatchingOptOut]没关系,显然是that one is not needed

【问题讨论】:

  • 你考虑过ngen吗?
  • @downvoters/close-voters:需要解释一下吗?这是一个有效的问题,a definite answerbased on a problem I am actually facing。我可以做些什么来改善这个问题吗?
  • 你已经标记了你的问题 c# 但提到热点是一个 java jit 编译器?
  • @dr.mo 我认为“热点”是指对性能影响最大的代码部分。
  • @PieterGeerkens:这将是一个完全不同(并且可能过于本地化)的问题 - 我只是在寻找不同的方法来实现优化我的热点的这一方面。

标签: c# .net vb.net optimization


【解决方案1】:

是的,还有更多技巧:-)

我实际上已经对优化 C# 代码进行了大量研究。到目前为止,这些是最重要的结果:

  1. 直接传递的 Func 和 Action 通常由 JIT'ter 内联。请注意,您不应将它们存储为变量,因为它们随后被称为委托。另请参阅this post 了解更多详情。
  2. 小心过载。在不使用IEquatable<T> 的情况下调用 Equals 通常是一个糟糕的计划 - 所以如果你使用 f.ex。哈希,请务必实现正确的重载和接口,因为它会为您提供大量性能。
  3. 从其他类调用的泛型从不内联。原因是here 概述的“魔法”。
  4. 如果您使用数据结构,请务必尝试使用数组来代替 :-) 确实,与...相比,这些东西快得不得了……嗯,几乎是我想的任何东西。通过使用我自己的哈希表和使用数组而不是列表,我已经优化了很多东西。
  5. 在很多情况下,表查找比计算或使用 vtable 查找、开关、多个 if 语句甚至计算等结构更快。如果您有分支,这也是一个好技巧;失败的分支预测通常会成为一个很大的痛苦。另请参阅this post - 这是我在 C# 中经常使用的一个技巧,它在很多情况下都非常有效。哦,查找表当然是数组。
  6. 尝试制作(小)类结构。由于值类型的性质,结构与类的某些优化不同。例如,方法调用更简单,因为编译器确切地知道要调用什么方法。此外,结构数组通常比类数组更快,因为它们每次数组操作所需的内存操作少。
  7. 不要使用多维数组。虽然我更喜欢Foo[],但即使Foo[][] 通常也比Foo[,] 快。
  8. 如果您要复制数据,请选择 Buffer.BlockCopy 而不是 Array.Copy 一周中的任何一天。还要小心字符串:字符串操作可能会消耗性能。

过去还有一个名为“英特尔奔腾处理器优化”的指南,其中包含大量技巧(例如移位或乘法而不是除法)。虽然现在编译器做得很好,但这有时也会有所帮助。

当然,这些只是优化;最大的性能提升通常是更改算法和/或数据结构的结果。请务必检查您可以使用哪些选项,并且不要过多地受 .NET 框架的限制……而且在我自己检查了反编译的代码之前,我有一种不信任 .NET 实现的自然倾向。 .. 有很多东西可以更快地实施(大多数时候是有充分理由的)。

HTH


Alex 向我指出,根据某些人的说法,Array.Copy 实际上更快。由于我真的不知道这些年来发生了什么变化,我决定唯一正确的做法是创建一个全新的基准并对其进行测试。

如果您只对结果感兴趣,请往下看。在大多数情况下,对Buffer.BlockCopy 的调用明显优于Array.Copy。在 .NET 4.5.2 上在具有 16 GB 内存(>10 GB 可用内存)的 Intel Skylake 上进行测试。

代码:

static void TestNonOverlapped1(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K];
    byte[] tmp2 = new byte[K];
    for (long i = 0; i < iter; ++i)
    {
        Array.Copy(tmp, tmp2, K);
    }
}

static void TestNonOverlapped2(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K];
    byte[] tmp2 = new byte[K];
    for (long i = 0; i < iter; ++i)
    {
        Buffer.BlockCopy(tmp, 0, tmp2, 0, K);
    }
}

static void TestOverlapped1(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K + 16];
    for (long i = 0; i < iter; ++i)
    {
        Array.Copy(tmp, 0, tmp, 16, K);
    }
}

static void TestOverlapped2(int K)
{
    long total = 1000000000;
    long iter = total / K;
    byte[] tmp = new byte[K + 16];
    for (long i = 0; i < iter; ++i)
    {
        Buffer.BlockCopy(tmp, 0, tmp, 16, K);
    }
}

static void Main(string[] args)
{
    for (int i = 0; i < 10; ++i)
    {
        int N = 16 << i;

        Console.WriteLine("Block size: {0} bytes", N);

        Stopwatch sw = Stopwatch.StartNew();

        {
            sw.Restart();
            TestNonOverlapped1(N);

            Console.WriteLine("Non-overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestNonOverlapped2(N);

            Console.WriteLine("Non-overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestOverlapped1(N);

            Console.WriteLine("Overlapped Array.Copy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        {
            sw.Restart();
            TestOverlapped2(N);

            Console.WriteLine("Overlapped Buffer.BlockCopy: {0:0.00} ms", sw.Elapsed.TotalMilliseconds);
            GC.Collect(GC.MaxGeneration);
            GC.WaitForFullGCComplete();
        }

        Console.WriteLine("-------------------------");
    }

    Console.ReadLine();
}

x86 JIT 上的结果:

Block size: 16 bytes
Non-overlapped Array.Copy: 4267.52 ms
Non-overlapped Buffer.BlockCopy: 2887.05 ms
Overlapped Array.Copy: 3305.01 ms
Overlapped Buffer.BlockCopy: 2670.18 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 1327.55 ms
Non-overlapped Buffer.BlockCopy: 763.89 ms
Overlapped Array.Copy: 2334.91 ms
Overlapped Buffer.BlockCopy: 2158.49 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 705.76 ms
Non-overlapped Buffer.BlockCopy: 390.63 ms
Overlapped Array.Copy: 1303.00 ms
Overlapped Buffer.BlockCopy: 1103.89 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 361.18 ms
Non-overlapped Buffer.BlockCopy: 219.77 ms
Overlapped Array.Copy: 620.21 ms
Overlapped Buffer.BlockCopy: 577.20 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 192.92 ms
Non-overlapped Buffer.BlockCopy: 108.71 ms
Overlapped Array.Copy: 347.63 ms
Overlapped Buffer.BlockCopy: 353.40 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 104.69 ms
Non-overlapped Buffer.BlockCopy: 65.65 ms
Overlapped Array.Copy: 211.77 ms
Overlapped Buffer.BlockCopy: 202.94 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 52.93 ms
Non-overlapped Buffer.BlockCopy: 38.84 ms
Overlapped Array.Copy: 144.39 ms
Overlapped Buffer.BlockCopy: 154.09 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 45.64 ms
Non-overlapped Buffer.BlockCopy: 30.11 ms
Overlapped Array.Copy: 118.33 ms
Overlapped Buffer.BlockCopy: 109.16 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 30.93 ms
Non-overlapped Buffer.BlockCopy: 30.72 ms
Overlapped Array.Copy: 119.73 ms
Overlapped Buffer.BlockCopy: 104.66 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 30.37 ms
Non-overlapped Buffer.BlockCopy: 26.63 ms
Overlapped Array.Copy: 90.46 ms
Overlapped Buffer.BlockCopy: 87.40 ms
-------------------------

x64 JIT 上的结果:

Block size: 16 bytes
Non-overlapped Array.Copy: 1252.71 ms
Non-overlapped Buffer.BlockCopy: 694.34 ms
Overlapped Array.Copy: 701.27 ms
Overlapped Buffer.BlockCopy: 573.34 ms
-------------------------
Block size: 32 bytes
Non-overlapped Array.Copy: 995.47 ms
Non-overlapped Buffer.BlockCopy: 654.70 ms
Overlapped Array.Copy: 398.48 ms
Overlapped Buffer.BlockCopy: 336.86 ms
-------------------------
Block size: 64 bytes
Non-overlapped Array.Copy: 498.86 ms
Non-overlapped Buffer.BlockCopy: 329.15 ms
Overlapped Array.Copy: 218.43 ms
Overlapped Buffer.BlockCopy: 179.95 ms
-------------------------
Block size: 128 bytes
Non-overlapped Array.Copy: 263.00 ms
Non-overlapped Buffer.BlockCopy: 196.71 ms
Overlapped Array.Copy: 137.21 ms
Overlapped Buffer.BlockCopy: 107.02 ms
-------------------------
Block size: 256 bytes
Non-overlapped Array.Copy: 144.31 ms
Non-overlapped Buffer.BlockCopy: 101.23 ms
Overlapped Array.Copy: 85.49 ms
Overlapped Buffer.BlockCopy: 69.30 ms
-------------------------
Block size: 512 bytes
Non-overlapped Array.Copy: 76.76 ms
Non-overlapped Buffer.BlockCopy: 55.31 ms
Overlapped Array.Copy: 61.99 ms
Overlapped Buffer.BlockCopy: 54.06 ms
-------------------------
Block size: 1024 bytes
Non-overlapped Array.Copy: 44.01 ms
Non-overlapped Buffer.BlockCopy: 33.30 ms
Overlapped Array.Copy: 53.13 ms
Overlapped Buffer.BlockCopy: 51.36 ms
-------------------------
Block size: 2048 bytes
Non-overlapped Array.Copy: 27.05 ms
Non-overlapped Buffer.BlockCopy: 25.57 ms
Overlapped Array.Copy: 46.86 ms
Overlapped Buffer.BlockCopy: 47.83 ms
-------------------------
Block size: 4096 bytes
Non-overlapped Array.Copy: 29.11 ms
Non-overlapped Buffer.BlockCopy: 25.12 ms
Overlapped Array.Copy: 45.05 ms
Overlapped Buffer.BlockCopy: 47.84 ms
-------------------------
Block size: 8192 bytes
Non-overlapped Array.Copy: 24.95 ms
Non-overlapped Buffer.BlockCopy: 21.52 ms
Overlapped Array.Copy: 43.81 ms
Overlapped Buffer.BlockCopy: 43.22 ms
-------------------------

【讨论】:

  • 您至少在第 8 段中是不对的,因为它们是相同的。因此,由于类型安全,我更喜欢Array.Copy。如果您不相信,您可以自己在此站点上找到多个 easy-to-google 基准测试。
  • @AlexZhukovskiy 关于#8:我最近没有测试过这些,2013 年就是这种情况。多年来结果发生了一些变化,x86/x64 JIT 之间存在差异。现在我相信它们在性能上是相当的,这意味着我现在更喜欢Array.Copy
  • AFAIR,这些基准也在 13 年或近乎发布。而且我确信它的代码从 .Net 4.0 开始并可能从 2.0 开始完全没有改变。
  • @AlexZhukovskiy 我真的无法告诉你:iirc 它们都是内部调用,由于我不在微软工作,我真的不知道发生了什么变化。我只记得运行基准测试。如果您真的想知道,我会简单地运行基准测试;如果他们讲述不同的故事,我很乐意更新帖子。
  • Here 你可以找到一些基准测试,日期为 8 月 11 日。这些方法是内部方法,已经足够调整,我不希望针对不同的目标框架有不同的结果。如果您的意见不同,请提供一些长凳。链接基准测试不够好(没有统计数据/手册Stopwatch 检查代码等),但我希望在适当的基准测试中得到类似的结果。
【解决方案2】:

您已经用尽了 .NET 4.5 中添加的选项来直接影响 jitted 代码。下一步是查看生成的机器代码以发现任何明显的低效率。使用调试器这样做,首先要防止它禁用优化器。工具 + 选项,调试,常规,取消勾选“在模块加载时抑制 JIT 优化”选项。在热代码上设置断点,Debug + Disassembly 看看。

没有太多需要考虑的,抖动优化器总体上做得很好。要寻找的一件事是尝试消除数组边界检查失败,fixed 关键字是一种不安全的解决方法。一个极端情况是内联方法的尝试失败,并且抖动没有有效地使用 cpu 寄存器,这是 x86 抖动的一个问题,并通过 MethodImplOptions.NoInlining 修复。优化器在从循环中提升不变代码方面效率不高,但这是您在寻找优化方法时盯着 C# 代码时几乎总是首先考虑的问题。

要知道的最重要的事情是您何时完成并且不能希望让它更快。您只能通过比较苹果和橘子并使用 C++/CLI 在本机代码中编写热门代码才能真正实现目标。确保这段代码是用#pragma unmanaged 编译的,所以它得到了优化器的全部喜爱。从托管代码切换到本机代码执行会产生相关成本,因此请确保本机代码的执行时间足够长。否则,这不一定容易做到,而且您肯定无法保证成功。尽管知道你已经完成了可以为你节省很多陷入死胡同的时间。

【讨论】:

  • 谢谢汉斯 - 不是我想要的,但非常有帮助:) 如果我发现 JIT-er 使用寄存器效率低下,我真的可以做些什么吗?
  • @BlueRaja-DannyPflughoeft 如果您还没有这样做,我会尝试将变量设置为本地变量而不是类变量。对于后者,编译器无法确定它不会被其他线程访问,而前者可以安全地放入寄存器中。此外,您声明局部变量的顺序似乎也很重要 - 所以这是要考虑的两件事。
  • @atlaste 绝对正确。我开发了实时交易应用程序,并且刚刚完成了一次优化扫描,我将在一个方法中多次访问的所有成员变量提升为局部变量。这允许 JIT 编译器注册它们并导致约 10% 的性能提升
猜你喜欢
  • 2023-04-10
  • 2010-10-30
  • 1970-01-01
  • 2015-09-03
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2011-02-21
  • 1970-01-01
相关资源
最近更新 更多