Почему Fun c <> делегирует намного медленнее - PullRequest
9 голосов
/ 03 августа 2020
• 1000

Почему оценка выражения в два раза медленнее

using System;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<Calculations>();

            var logger = ConsoleLogger.Default;
            MarkdownExporter.Console.ExportToLog(summary, logger);

            Console.WriteLine(summary);
        }
    }

    public class Calculations
    {
        public Random RandomGeneration = new Random();

        [Benchmark]
        public void CalculateNormal()
        {
           var s =  RandomGeneration.Next() * RandomGeneration.Next();
        }

        [Benchmark]
        public void CalculateUsingFunc()
        {
            Calculate(() => RandomGeneration.Next() * RandomGeneration.Next());
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public int Calculate(Func<int> expr)
        {
            return expr();
        }
    }
}

Ниже приведен тест: введите описание изображения здесь

1 Ответ

13 голосов
/ 03 августа 2020

Вы создаете новый объект делегата при каждом вызове. Неудивительно, что у этого есть изрядные накладные расходы.

Если вы используете лямбда-выражение, которое не захватывает this, или какие-либо локальные переменные (в этом случае компилятор может кэшировать его в stati c field) или если вы явно создаете один экземпляр и сохраняете его в поле самостоятельно, большая часть накладных расходов уходит.

Вот измененная версия вашего теста:

public class Calculations
{
    public Random RandomGeneration = new Random();
    private Func<int> exprField;
    
    public Calculations()
    {
        exprField = () => RandomGeneration.Next() * RandomGeneration.Next();
    }
    
    [Benchmark]
    public void CalculateNormal()
    {
       var s =  RandomGeneration.Next() * RandomGeneration.Next();
    }

    [Benchmark]
    public void CalculateUsingFunc()
    {
        Calculate(() => RandomGeneration.Next() * RandomGeneration.Next());
    }
    
    [Benchmark]
    public void CalculateUsingFuncField()
    {
        Calculate(exprField);
    }

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int Calculate(Func<int> expr)
    {
        return expr();
    }
}

И результаты на моей машине:

|                  Method |     Mean |    Error |   StdDev |
|------------------------ |---------:|---------:|---------:|
|         CalculateNormal | 27.61 ns | 0.438 ns | 0.388 ns |
|      CalculateUsingFunc | 48.74 ns | 1.009 ns | 0.894 ns |
| CalculateUsingFuncField | 32.53 ns | 0.698 ns | 0.717 ns |

Итак, есть еще бит накладных расходов, но гораздо меньше, чем раньше.

...