Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/25.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
对C#中的小代码示例进行基准测试,可以改进此实现吗?_C#_.net_Performance_Profiling - Fatal编程技术网

对C#中的小代码示例进行基准测试,可以改进此实现吗?

对C#中的小代码示例进行基准测试,可以改进此实现吗?,c#,.net,performance,profiling,C#,.net,Performance,Profiling,经常如此,我发现自己在对小块代码进行基准测试,以确定哪种实现最快 我经常看到基准代码没有考虑jitting或垃圾收集器的注释 我有以下简单的基准测试功能,我已经慢慢发展: static void Profile(string description, int iterations, Action func) { // warm up func(); // clean up GC.Collect(); var

经常如此,我发现自己在对小块代码进行基准测试,以确定哪种实现最快

我经常看到基准代码没有考虑jitting或垃圾收集器的注释

我有以下简单的基准测试功能,我已经慢慢发展:

  static void Profile(string description, int iterations, Action func) {
        // warm up 
        func();
        // clean up
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }
这个实现有任何缺陷吗?在Z次迭代中,实现X比实现Y快,这足够好吗?你能想出什么办法来改进这一点吗

编辑
很明显,基于时间的方法(与迭代相反)是首选方法,有没有人有时间检查不影响性能的实现

如果您想排除GC交互,您可能希望在GC.对方付费呼叫之后而不是之前运行“预热”呼叫。这样,您就知道.NET已经从操作系统中为您的函数工作集分配了足够的内存

请记住,您正在为每个迭代调用一个非内联方法,因此请确保将您正在测试的内容与一个空体进行比较。您还必须接受,您只能可靠地计算比方法调用长几倍的时间


另外,根据你分析的内容,您可能希望在一定时间内,而不是在一定次数的迭代中,进行基于时间的运行—这样可以得到更容易比较的数字,而不必在很短的时间内实现最好的实现,也不必在很长的时间内实现最坏的实现。

我完全不想通过委托:

  • 委托调用是~virtual方法调用。不便宜:大约占.NET中最小内存分配的25%。如果您对详细信息感兴趣,请参阅
  • 匿名委托可能导致使用闭包,您甚至不会注意到。同样,访问闭包字段要比访问堆栈上的变量明显
  • 导致闭包使用的示例代码:

    public void Test()
    {
      int someNumber = 1;
      Profiler.Profile("Closure access", 1000000, 
        () => someNumber + someNumber);
    }
    

    如果您不知道闭包,请在.NET Reflector中查看此方法。

    您还必须运行“预热”在实际测量之前通过,以排除JIT编译器在JIT代码上花费的时间。

    我认为像这样的基准测试方法最难克服的问题是考虑边缘情况和意外情况。例如,“这两个代码片段在高CPU负载/网络使用率/磁盘抖动等情况下如何工作?”它们非常适合进行基本逻辑检查,以查看某个特定算法是否比另一个算法工作得快得多。但是为了正确地测试大多数代码的性能,您必须创建一个测试来测量特定代码的特定瓶颈


    我仍然认为,测试小块代码通常没有多少投资回报,并且会鼓励使用过于复杂的代码而不是简单的可维护代码。编写其他开发人员或我自己6个月后能够快速理解的清晰代码比高度优化的代码有更多的性能优势

    GC.Collect
    返回之前,不一定要完成定稿。最终结果将排队,然后在单独的线程上运行。此线程可能在测试期间仍处于活动状态,从而影响结果

    如果要确保在开始测试之前完成最终确定,则可能需要调用,该调用将一直阻止,直到清除最终确定队列:

    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    

    这是修改后的功能:根据社区的建议,可以随意修改它,这是一个社区wiki

    static double Profile(string description, int iterations, Action func) {
        //Run at highest priority to minimize fluctuations caused by other processes/threads
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
    
        // warm up 
        func();
    
        var watch = new Stopwatch(); 
    
        // clean up
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
        return watch.Elapsed.TotalMilliseconds;
    }
    
    静态双配置文件(字符串描述、整数迭代、动作函数){
    //以最高优先级运行,以最小化由其他进程/线程引起的波动
    Process.GetCurrentProcess().PriorityClass=ProcessPriorityClass.High;
    Thread.CurrentThread.Priority=ThreadPriority.Highest;
    //热身
    func();
    var watch=新秒表();
    //清理
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();
    watch.Start();
    对于(int i=0;i

    确保在启用优化的版本中编译,并在Visual Studio之外运行测试。最后一部分很重要,因为即使在发布模式下,JIT也会通过附加的调试器进行优化。

    我会多次调用
    func()
    进行预热,而不仅仅是一次。

    根据您进行基准测试的代码及其运行的平台,您可能需要考虑。要做到这一点,可能需要一个外部包装器来多次运行测试(在单独的应用程序域或进程中?),其中一些时候首先调用“填充代码”来强制对其进行JIT编译,以便使被基准测试的代码以不同的方式对齐。完整的测试结果将给出各种代码对齐的最佳情况和最坏情况的时间安排。

    改进建议
  • 检测执行环境是否适合基准测试(例如检测是否附加了调试器,或者是否禁用了jit优化,这将导致不正确的测量)

  • 独立地度量代码的各个部分(以准确地看到瓶颈在哪里)

  • 比较不同版本/组件/代码块(在您的第一句话中,您说“…对小块代码进行基准测试,以查看哪个实现最快”)
  • 关于#1:

    • 要检测是否附加了调试器,请阅读属性
      System.Diagnostics.debugger.IsAttached
      (请记住还要处理调试器最初未附加,但经过一段时间后才附加的情况)

    • 检测是否存在ji
      static double Profile(string description, int iterations, Action func) {
          //Run at highest priority to minimize fluctuations caused by other processes/threads
          Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
          Thread.CurrentThread.Priority = ThreadPriority.Highest;
      
          // warm up 
          func();
      
          var watch = new Stopwatch(); 
      
          // clean up
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
      
          watch.Start();
          for (int i = 0; i < iterations; i++) {
              func();
          }
          watch.Stop();
          Console.Write(description);
          Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
          return watch.Elapsed.TotalMilliseconds;
      }
      
      private bool IsJitOptimizerDisabled(Assembly assembly)
      {
          return assembly.GetCustomAttributes(typeof (DebuggableAttribute), false)
              .Select(customAttribute => (DebuggableAttribute) customAttribute)
              .Any(attribute => attribute.IsJITOptimizerDisabled);
      }
      
      static void Profile(string description, int iterations, Action func) {
          // warm up 
          func();
      
          var watch = new Stopwatch(); 
      
          // clean up
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
      
          watch.Start();
          for (int i = 0; i < iterations; i++) {
              func();
          }
          watch.Stop();
          Console.Write(description);
          Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
      }
      
      static void ProfileGarbageMany(string description, int iterations, Action func) {
          // warm up 
          func();
      
          var watch = new Stopwatch(); 
      
          // clean up
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
      
          watch.Start();
          for (int i = 0; i < iterations; i++) {
              func();
          }
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
      
          watch.Stop();
          Console.Write(description);
          Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
      }
      
      static void ProfileGarbage(string description, int iterations, Action func) {
          // warm up 
          func();
      
          var watch = new Stopwatch(); 
      
          // clean up
          GC.Collect();
          GC.WaitForPendingFinalizers();
          GC.Collect();
      
          watch.Start();
          for (int i = 0; i < iterations; i++) {
              func();
      
              GC.Collect();
              GC.WaitForPendingFinalizers();
              GC.Collect();
          }
          watch.Stop();
          Console.Write(description);
          Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
      }