Любопытная разница в производительности между возвратом значения или возвратом через параметр Action <T> - PullRequest
1 голос
/ 16 сентября 2010

Мне было любопытно посмотреть, в чем разница в производительности между возвратом значения из метода или возвратом через параметр Action.

Есть несколько связанный с этим вопрос Производительность вызова делегатов по сравнению с методами

Но я не могу объяснить, почему возвращение значения будет на ~ 30% медленнее, чем вызов делегата для возврата значения. Является ли .net Jitter (не компилятор ...) встроенным в мой простой делегат (я не думал, что он это сделал)?

class Program
{
    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        A aa = new A();

        long l = 0;
        for( int i = 0; i < 100000000; i++ )
        {
            aa.DoSomething( i - 1, i, r => l += r );
        }

        sw.Stop();
        Trace.WriteLine( sw.ElapsedMilliseconds + " : " + l );

        sw.Reset();
        sw.Start();

        l = 0;
        for( int i = 0; i < 100000000; i++ )
        {
            l += aa.DoSomething2( i - 1, i );
        }

        sw.Stop();
        Trace.WriteLine( sw.ElapsedMilliseconds + " : " + l );
    }
}
class A
{
    private B bb = new B();

    public void DoSomething( int a, int b, Action<long> result )
    {
        bb.Add( a,b, result );
    }

    public long DoSomething2( int a, int b  )
    {
        return bb.Add2( a,b );
    }

}
class B
{
    public void Add( int a, int b, Action<long> result )
    {
        result( a + b );
    }

    public long Add2( int i, int i1 )
    {
        return i + i1;
    }
}

Ответы [ 4 ]

2 голосов
/ 16 сентября 2010

Я сделал пару изменений в вашем коде.

  • Переместился new A() перед временной секцией.
  • Добавлен код прогона перед временной секцией, чтобы получить методы JIT 'ed.
  • Создана ссылка Action<long> перед временной секцией и циклом, чтобы ее не нужно было создавать на каждой итерации.Это, похоже, сильно повлияло на время выполнения.

Вот мои результаты после внесения вышеуказанных изменений.Столбец vshost указывает, выполнялся ли код внутри процесса vshost.exe (при запуске непосредственно из Visual Studio).Я использовал Visual Studio 2008 и предназначался для .NET 3.5 SP1.

vshost?   Debug   Release
-------------------------
 YES       6405     3827
          11059     3092

 NO        4214     1691
           4607      811

Обратите внимание, как вы получаете разные результаты в зависимости от конфигурации сборки и среды выполнения.Результаты интересны, если не сказать больше.Если у меня будет время, я могу отредактировать свой ответ, чтобы представить теорию.

1 голос
/ 16 сентября 2010

Странно, я не вижу поведения, которое вы описываете при запуске сборки Release в VS.Я вижу это при запуске сборки Debug.Единственное, что я могу понять, это то, что при запуске сборки Debug есть дополнительные издержки с подходом, основанным на возврате, хотя я не достаточно умен, чтобы понять, почему.

Вот еще кое-что интересное: это несоответствие исчезает, когдаЯ переключаюсь на сборку x64 (Release или Debug).

Если бы я рискнул догадаться ( полностью необоснованно), это может привести кбудь то, что стоимость передачи 64-битного long в качестве возвращаемого значения в B.Add2 и A.DoSomething2 превышает стоимость передачи Action<long> в 32-битной среде.В 64-битной среде эта экономия исчезнет, ​​поскольку Action<long> также потребует 64 бит.В сборке Release в любой конфигурации стоимость прохождения long, вероятно, исчезает, так как B.Add2 и A.DoSomething2 кажутся главными кандидатами на встраивание.

Кто-то, кто знает об этом больше, чем я: не стесняйтесь полностью опровергать все, что я только что сказал.В конце концов, мы все здесь, чтобы учиться;)

1 голос
/ 16 сентября 2010

Хорошо, для начала ваш звонок на new A() рассчитывается так, как вы сейчас настроили свой код.Вы должны убедиться, что вы работаете в режиме релиза с включенной оптимизацией.Также вам необходимо принять во внимание JIT - процитировать все пути кода, чтобы вы могли гарантировать, что они скомпилированы до того, как вы их успеете (если вы не беспокоитесь о времени запуска).

Я вижу проблему, когдаВы пытаетесь рассчитать огромное количество примитивных операций (простое дополнение).В этом случае вы не можете сделать никаких окончательных выводов, поскольку любые накладные расходы будут полностью доминировать в ваших измерениях.

edit: в режиме выпуска с таргетингом на .NET 3.5 в VS2008 я получаю:

1719 : 9999999800000000
1337 : 9999999800000000

, чтокажется, согласуется со многими другими ответами.Использование ILDasm дает следующий IL для B.Add:

  IL_0000:  ldarg.3
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  add
  IL_0004:  conv.i8
  IL_0005:  callvirt   instance void class [mscorlib]System.Action`1<int64>::Invoke(!0)
  IL_000a:  ret

Где B.Add2:

  IL_0000:  ldarg.1
  IL_0001:  ldarg.2
  IL_0002:  add
  IL_0003:  conv.i8
  IL_0004:  ret

Таким образом, похоже, что вы просто синхронизируете load и callvirt.

0 голосов
/ 16 сентября 2010

Почему бы не использовать отражатель , чтобы узнать?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...