Накладные расходы попробовать /наконец в C #?

Мы видели много вопросов о том, когда и зачем использовать try /catch и try /catch /finally. И я знаю, что определенно есть вариант использования для try /finally (тем более что это способ реализации оператора using).

Мы также видели вопросы о накладных расходах из try /catch и исключений .

Вопрос, на который я ссылался, однако, не говорит об издержках, связанных с JUST try-finally.

Если предположить, что в блоке try нет исключений из всего, что связано с тем, что finally выполняются при выходе из блока try (иногда возвращаясь из функции)?

Опять же, я спрашиваю ТОЛЬКО о try /finally, нет catch, нет исключений.

Спасибо!

РЕДАКТИРОВАТЬ: Хорошо, я попытаюсь показать мой вариант использования немного лучше.

Что я должен использовать, DoWithTryFinally или DoWithoutTryFinally

public bool DoWithTryFinally()
{
  this.IsBusy = true;

  try
  {
    if (DoLongCheckThatWillNotThrowException())
    {
      this.DebugLogSuccess();
      return true;
    }
    else
    {
      this.ErrorLogFailure();
      return false;
    }
  }
  finally
  {
    this.IsBusy = false;
  }
}

public bool DoWithoutTryFinally()
{
  this.IsBusy = true;

  if (DoLongCheckThatWillNotThrowException())
  {
    this.DebugLogSuccess();

    this.IsBusy = false;
    return true;
  }
  else
  {
    this.ErrorLogFailure();

    this.IsBusy = false;
    return false;
  }
}

Этот случай слишком упрощен, потому что есть только две точки возврата, но представьте, было ли четыре ... или десять ... или сто.

В какой-то момент я хотел бы использовать try /finally по следующим причинам:

  • Соблюдайте принципы СУХОЙ (особенно в связи с увеличением количества точек выхода)
  • Если окажется, что я ошибаюсь из-за того, что моя внутренняя функция не выдает исключение, я хочу убедиться, что this.Working установите значение false.

Таким образом, гипотетически, учитывая производительность, принципы обслуживания и принципы DRY, для какого количества точек выхода (особенно, если я могу предположить, что все внутренние исключения перехвачены) делают Я хочу понести любые потери производительности, связанные с try /finally

РЕДАКТИРОВАТЬ # 2: я изменил имя this.Working на this.IsBusy. Извините, забыл упомянуть, что это многопоточный (хотя на самом деле метод вызовет только один поток); другие потоки будут опрашивать, чтобы увидеть, выполняет ли объект свою работу. Возвращаемое значение - просто успех или неудача, если работа прошла так, как ожидалось.

66 голосов | спросил Platinum Azure 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 17:38:09 +0300 2010, 17:38:09

6 ответов


0

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

Вот простой кусок кода на C #:

    static void Main(string[] args)
    {
        int i = 0;
        try
        {
            i = 1;
            Console.WriteLine(i);
            return;
        }
        finally
        {
            Console.WriteLine("finally.");
        }
    }

А вот итоговый IL в отладочной сборке:

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init ([0] int32 i)
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: nop 
    L_0004: ldc.i4.1 
    L_0005: stloc.0 
    L_0006: ldloc.0 // here's the WriteLine of i 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32)
    L_000c: nop 
    L_000d: leave.s L_001d // this is the flavor of branch that triggers finally
    L_000f: nop 
    L_0010: ldstr "finally."
    L_0015: call void [mscorlib]System.Console::WriteLine(string)
    L_001a: nop 
    L_001b: nop 
    L_001c: endfinally 
    L_001d: nop 
    L_001e: ret 
    .try L_0003 to L_000f finally handler L_000f to L_001d
}

и вот сборка, сгенерированная JIT при запуске в отладке:

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00288D34h],0 
00000028  je          0000002F 
0000002a  call        59439E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        try
        {
0000003a  nop 
            i = 1;
0000003b  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000042  mov         ecx,dword ptr [ebp-40h] 
00000045  call        58DB2EA0 
0000004a  nop 
            return;
0000004b  nop 
0000004c  mov         dword ptr [ebp-20h],0 
00000053  mov         dword ptr [ebp-1Ch],0FCh 
0000005a  push        4E1584h 
0000005f  jmp         00000061 
        }
        finally
        {
00000061  nop 
            Console.WriteLine("finally.");
00000062  mov         ecx,dword ptr ds:[036E2088h] 
00000068  call        58DB2DB4 
0000006d  nop 
        }
0000006e  nop 
0000006f  pop         eax 
00000070  jmp         eax 
00000072  nop 
    }
00000073  nop 
00000074  lea         esp,[ebp-0Ch] 
00000077  pop         ebx 
00000078  pop         esi 
00000079  pop         edi 
0000007a  pop         ebp 
0000007b  ret 
0000007c  mov         dword ptr [ebp-1Ch],0 
00000083  jmp         00000072 

Теперь, если я закомментирую попытку и наконец и возвращение, я получу почти идентичную сборку из JIT. Различия, которые вы увидите, это переход в блок finally и некоторый код, чтобы выяснить, куда идти после выполнения finally. Итак, вы говорите о крошечных различиях. В выпуске переход в finally будет оптимизирован - фигурные скобки - это не nop-инструкции, так что это станет переходом к следующей инструкции, которая также является nop - это простая оптимизация глазка. Pop Eax, а затем JMP Eax также дешево.

    {
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 
00000006  sub         esp,34h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[00198D34h],0 
00000028  je          0000002F 
0000002a  call        59549E21 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  nop 
        int i = 0;
00000035  xor         edx,edx 
00000037  mov         dword ptr [ebp-40h],edx 
        //try
        //{
            i = 1;
0000003a  mov         dword ptr [ebp-40h],1 
            Console.WriteLine(i);
00000041  mov         ecx,dword ptr [ebp-40h] 
00000044  call        58EC2EA0 
00000049  nop 
        //    return;
        //}
        //finally
        //{
            Console.WriteLine("finally.");
0000004a  mov         ecx,dword ptr ds:[034C2088h] 
00000050  call        58EC2DB4 
00000055  nop 
        //}
    }
00000056  nop 
00000057  lea         esp,[ebp-0Ch] 
0000005a  pop         ebx 
0000005b  pop         esi 
0000005c  pop         edi 
0000005d  pop         ebp 
0000005e  ret 

То есть, вы говорите очень, очень маленькие затраты на попытку /наконец. Есть очень мало проблемных областей, где это имеет значение. Если вы делаете что-то вроде memcpy и помещаете попытку /окончание вокруг каждого копируемого байта, а затем приступаете к копированию сотен МБ данных, я вижу, что это проблема, но в большинстве случаев? Незначительно.

ответил plinth 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 18:23:31 +0300 2010, 18:23:31
0

Итак, давайте предположим, что есть накладные расходы. Вы собираетесь прекратить использование finally тогда? Надеюсь, что нет.

Показатели производительности IMO актуальны только в том случае, если вы можете выбирать между различными вариантами. Я не вижу, как можно получить семантику finally без использования finally.

ответил Brian Rasmussen 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 17:42:13 +0300 2010, 17:42:13
0

try/finally очень легкий. На самом деле, так и try/catch/finally, если не выдается исключение.

У меня было приложение для быстрого профиля, которое я сделал недавно, чтобы проверить его; в тесном цикле это действительно ничего не добавляло ко времени выполнения.

Я бы опубликовал это снова, но это было действительно просто; просто запустите тугой цикл, делая что-то с try/catch/finally, который не генерирует никаких исключений внутри цикла, и синхронизируйте результаты с версией try/catch/finally.

ответил Andrew Barber 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 17:42:48 +0300 2010, 17:42:48
0

Давайте добавим к этому некоторые контрольные цифры. Этот тест показывает, что на самом деле время выполнения try /finally примерно такое же, как и издержки на вызов пустой функции (вероятно, лучше сказать: «переход к следующей инструкции», как об этом заявил эксперт по IL). выше).

            static void RunTryFinallyTest()
            {
                int cnt = 10000000;

                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));
                Console.WriteLine(TryFinallyBenchmarker(cnt, false));

                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));
                Console.WriteLine(TryFinallyBenchmarker(cnt, true));

                Console.ReadKey();
            }

            static double TryFinallyBenchmarker(int count, bool useTryFinally)
            {
                int over1 = count + 1;
                int over2 = count + 2;

                if (!useTryFinally)
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do something so optimization doesn't ignore whole loop. 
                        if (i == over1) throw new Exception();
                        if (i == over2) throw new Exception();
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
                else
                {
                    var sw = Stopwatch.StartNew();
                    for (int i = 0; i < count; i++)
                    {
                        // do same things, just second in the finally, make sure finally is 
                        // actually doing something and not optimized out
                        try
                        {
                            if (i == over1) throw new Exception();
                        } finally
                        {
                            if (i == over2) throw new Exception();
                        }
                    }
                    return sw.Elapsed.TotalMilliseconds;
                }
            }

Результат: 33,33,32,35,32 63,64,69,66,66 (миллисекунды, убедитесь, что у вас включена оптимизация кода)

Таким образом, около 33 миллисекунд накладных расходов для циклов try /finally в 10 миллионов циклов.

За попытку /наконец, мы говорим 0.033 /10000000 =

3,3 наносекунды или 3,3 миллиарда секундных издержек на попытку /окончание.

ответил Nicholas Petersen 6 AMpFri, 06 Apr 2012 03:08:38 +040008Friday 2012, 03:08:38
0

Что сказал Эндрю Барбер Фактические операторы TRY /CATCH не добавляют /пренебрежимо малые накладные расходы, если не выдается исключение. В конце концов, в этом нет ничего особенного. Ваш код просто всегда переходит к окончанию после выполнения кода в инструкциях try + catch

ответил Bengie 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 18:17:59 +0300 2010, 18:17:59
0

На более низких уровнях finally так же дорого, как и else, если условие не выполнено. Это на самом деле прыжок на ассемблере (IL).

ответил Liviu M. 5 52010vEurope/Moscow11bEurope/MoscowFri, 05 Nov 2010 18:23:38 +0300 2010, 18:23:38

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132