Подтвердить что ты не робот

Что происходит в конструкциях "If (.. ||..)" и "If (... &&...)" внутри?

Мне просто интересно, что происходит внутри ", если OR" и ", если AND". У меня есть ощущение, что это просто синтаксический сахар для использования && и ||, и внутренне все случаи строятся как одиночные операторы if.

Компактная форма ||:

if(a || b || c)
{
    DoSomething();
}

Потенциальная внутренняя форма:

if(a)
{
    DoSomething();
}    
else if(b)
{
    DoSomething();
}    
else if(c)
{
    DoSomething();
}

Компактная форма & &:

if(a && b && c)
{
    DoSomething();
}

Потенциальная внутренняя форма:

if(a)
{
    if(b)
    {
        if(c)
        {
            DoSomething();
        }
    }
}

Есть ли разница в производительности этих двух примеров?

* Edit: добавлено else в значение || Случай

4b9b3361

Ответ 1

Прежде всего, || и && short-circuit. Это означает, что в:

if(a || b || c)
    DoSomething();

если a истинно, b и c не будут оцениваться.

Во-вторых, ваша реализация || неверна:

if(a)
    DoSomething();
if(b)
    DoSomething();
if(c)
    DoSomething();

DoSomething() будет называться до 3 раз.

Это должно быть:

if(a)
    DoSomething();
else if(b)
    DoSomething();
else if(c)
    DoSomething();

Чтобы закончить, если вы хотите, чтобы производительность предпочитала более короткий вызов сначала в ваших условиях:

if(aShortFunctionToExecute() || aVeryVeryLongFunctionToExecute())
     DoSomething();

Будет быстрее, чем

if(aVeryVeryLongFunctionToExecute() || aShortFunctionToExecute())
     DoSomething();

Из-за ленивая оценка


Если вы разобрали код:

private static void Main()
{
    if (a() && b() && c())
    {
        Console.WriteLine("DoSomething");
    }
}
bool a(){
    return true;
}
bool b(){
    return 3 % 2 == 1;
}
bool c(){
    return (3 % 2) / 1 == 1;
}

Вы получите:

    if (a() && b() && c())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       eax,byte ptr [rbp+20h] 
0000002e  test        eax,eax 
00000030  je          000000000000005A 
00000032  call        FFFFFFFFFFEE8D98 
00000037  mov         byte ptr [rbp+21h],al 
0000003a  movzx       eax,byte ptr [rbp+21h] 
0000003e  test        eax,eax 
00000040  je          000000000000005A 
00000042  call        FFFFFFFFFFEE8DA0 
00000047  mov         byte ptr [rbp+22h],al 
0000004a  movzx       ecx,byte ptr [rbp+22h] 
0000004e  xor         eax,eax 
00000050  test        ecx,ecx 
00000052  sete        al 
00000055  mov         dword ptr [rbp+24h],eax 
00000058  jmp         0000000000000062 
0000005a  nop 
0000005b  mov         dword ptr [rbp+24h],1 
00000062  nop 
00000063  movzx       eax,byte ptr [rbp+24h] 
00000067  mov         byte ptr [rbp+2Fh],al 
0000006a  movzx       eax,byte ptr [rbp+2Fh] 
0000006e  test        eax,eax 
00000070  jne         0000000000000087 
        {
00000072  nop 
            Console.WriteLine("DoSomething");
00000073  mov         rcx,12603398h 
0000007d  mov         rcx,qword ptr [rcx] 
00000080  call        00000000577A82A0 
00000085  nop 
        }

и для кода:

private static void Main()
{
    if (a())
        if(b())
            if(c())
                Console.WriteLine("DoSomething");
}
static bool a(){
    return true;
}
static bool b(){
    return 3 % 2 == 1;
}
static bool c(){
    return (3 % 2) / 1 == 1;
}

Вы получите:

if (a())
00000022  call        FFFFFFFFFFEE8D90 
00000027  mov         byte ptr [rbp+20h],al 
0000002a  movzx       ecx,byte ptr [rbp+20h] 
0000002e  xor         eax,eax 
00000030  test        ecx,ecx 
00000032  sete        al 
00000035  mov         dword ptr [rbp+24h],eax 
00000038  movzx       eax,byte ptr [rbp+24h] 
0000003c  mov         byte ptr [rbp+3Fh],al 
0000003f  movzx       eax,byte ptr [rbp+3Fh] 
00000043  test        eax,eax 
00000045  jne         00000000000000A4 
            if(b())
00000047  call        FFFFFFFFFFEE8D98 
0000004c  mov         byte ptr [rbp+28h],al 
0000004f  movzx       ecx,byte ptr [rbp+28h] 
00000053  xor         eax,eax 
00000055  test        ecx,ecx 
00000057  sete        al 
0000005a  mov         dword ptr [rbp+2Ch],eax 
0000005d  movzx       eax,byte ptr [rbp+2Ch] 
00000061  mov         byte ptr [rbp+3Fh],al 
00000064  movzx       eax,byte ptr [rbp+3Fh] 
00000068  test        eax,eax 
0000006a  jne         00000000000000A4 
                if(c())
0000006c  call        FFFFFFFFFFEE8DA0 
00000071  mov         byte ptr [rbp+30h],al 
00000074  movzx       ecx,byte ptr [rbp+30h] 
00000078  xor         eax,eax 
0000007a  test        ecx,ecx 
0000007c  sete        al 
0000007f  mov         dword ptr [rbp+34h],eax 
00000082  movzx       eax,byte ptr [rbp+34h] 
00000086  mov         byte ptr [rbp+3Fh],al 
00000089  movzx       eax,byte ptr [rbp+3Fh] 
0000008d  test        eax,eax 
0000008f  jne         00000000000000A4 
                    Console.WriteLine("DoSomething");
00000091  mov         rcx,125D3398h 
0000009b  mov         rcx,qword ptr [rcx] 
0000009e  call        00000000577B82A0 
000000a3  nop 

Это немного дольше: вместо 31 требуется 40 инструкций.


Как указано thanosqr, производительность также зависит от вероятности того, что ваше условие будет истинным. Чтобы взять его пример:

Если a не работает 99% времени и занимает 1 секунду для запуска, и если b  преуспеть в 99% случаев и занять 10 секунд, более 100 попыток вы быстрее ставите b:

if(b || a) => 10s 99% ==> 100 runs will take 99*10+11 = 1001s
if(b || a) => 11s 1%

if(a || b) => 11s 99% ==> 100 runs will take 99*11+1 = 1090s
if(a || b) => 1s 1%

Кроме того, я предлагаю вам это чтение Почему быстрее обрабатывать отсортированный массив, чем несортированный массив?, что довольно интересно!

Ответ 2

Используя компактную форму, IL, испускаемый компилятором С#, будет менее подробным, что приведет к меньшему количеству команд, которые будут обрабатываться во время выполнения. Испускаемые утверждения IL и их логика на самом деле одинаковы, поэтому нет никакой фантастической встроенной поддержки для обработки этого случая или какой-либо специальной инструкции (помните, что вы можете поместить любое выражение с логическим результатом в if).

Для компактной формы с помощью оператора || (сборка отладки):

.method private hidebysig static void  One() cil managed
{
  // Code size       38 (0x26)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  brtrue.s   IL_0019
  IL_0008:  ldsfld     bool ConsoleApplication4.Program::b
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldsfld     bool ConsoleApplication4.Program::c
  IL_0014:  ldc.i4.0
  IL_0015:  ceq
  IL_0017:  br.s       IL_001a
  IL_0019:  ldc.i4.0
  IL_001a:  nop
  IL_001b:  stloc.0
  IL_001c:  ldloc.0
  IL_001d:  brtrue.s   IL_0025
  IL_001f:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0024:  nop
  IL_0025:  ret
} // end of method Program::One

С вашей внутренней формой (учитывая, что вы используете else if вместо if):

.method private hidebysig static void  Two() cil managed
{
  // Code size       60 (0x3c)
  .maxstack  2
  .locals init ([0] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  ldsfld     bool ConsoleApplication4.Program::a
  IL_0006:  ldc.i4.0
  IL_0007:  ceq
  IL_0009:  stloc.0
  IL_000a:  ldloc.0
  IL_000b:  brtrue.s   IL_0015
  IL_000d:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0012:  nop
  IL_0013:  br.s       IL_003b
  IL_0015:  ldsfld     bool ConsoleApplication4.Program::b
  IL_001a:  ldc.i4.0
  IL_001b:  ceq
  IL_001d:  stloc.0
  IL_001e:  ldloc.0
  IL_001f:  brtrue.s   IL_0029
  IL_0021:  call       void ConsoleApplication4.Program::DoSomething()
  IL_0026:  nop
  IL_0027:  br.s       IL_003b
  IL_0029:  ldsfld     bool ConsoleApplication4.Program::c
  IL_002e:  ldc.i4.0
  IL_002f:  ceq
  IL_0031:  stloc.0
  IL_0032:  ldloc.0
  IL_0033:  brtrue.s   IL_003b
  IL_0035:  call       void ConsoleApplication4.Program::DoSomething()
  IL_003a:  nop
  IL_003b:  ret
} // end of method Program::Two

Таким образом, есть еще несколько инструкций для обработки всех прыжков, необходимых дополнительным операторам if. Поэтому первая форма более эффективна (и на самом деле более читаема:)).

С точки зрения производительности (каждый метод измеряется 10 раз с 10.000.000 итераций и удаляет самые высокие и самые низкие значения, выпускает сборку):

Компактная форма: 55 мс в среднем

Подробная форма: 56 мс в среднем

Таким образом, нет большой разницы.

Ответ 3

Для тех, кто читает С# лучше, чем сборки, реальные внутренние формы ближе к:

if(a) goto yes;
if(b) goto yes;
if(c) goto yes;
goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

для

if(a || b || c)
  DoSomething();

и

if(!a) goto no;
if(!b) goto no;
if(!c) goto no;
yes:  DoSomething();
goto done;
no:   /* if there were an else it would go here */;
done: ;

для

if(a && b && c)
  DoSomething();

Это потому, что фактические инструкции являются условными ветвями - во внутренней форме невозможно связать if с блоком, вложенным if или фактически чем-либо, кроме goto.

Ответ 4

Код:

if(a)
    if(b)
        if(c)
            DoSomething();

является логическим (но не "практическим" ) эквивалентом для:

if(a && b && c)
    DoSomething();

Что касается оператора OR, у вас это немного неверно. Логический (но, опять же, не "практический" ) эквивалент для:

if(a || b || c)
    DoSomething();

:

if(a)
    DoSomething();
else if(b)
    DoSomething();
else if(c)
    DoSomething();

По практическим соображениям я понимаю любые возникающие различия в коде, введенные компилятором (подробнее см. другие ответы).

Ответ 5

|| и && являются условными -операторов. Они также являются операторами, как и другие операторы, которых вы, возможно, знаете. (например, +, *,...)

Их поведение аналогично логическим операторам, | и &. Они получают две переменные типа bool и возвращают значение bool следующим образом:

// If one of them is true, the return value is true. Otherwise, it false.
true  | true  == true
true  | false == true
false | true  == true
false | false == false
// If both of them are true, the return value is true. Otherwise, it false.
true  & true  == true
true  & false == false
false & true  == false
false & false == false

Однако, как и для условных операторов, существует разность бит: короткое замыкание.

Предположим, что этот код:

bool func1() { .. }
bool func2() { .. }

bool b1 = func1() || func2();
bool b2 = func1() && func2();

Если func1() возвращает true, b1 становится true независимо от того, что возвращает func2(). Поэтому нам не нужно вызывать func2() и на самом деле этого не делать. Если func1() возвращает false, то же самое относится к b2. Такое поведение называется короткозамкнутым.


Теперь подумайте о своем примере.

if (a || b || c)
    DoSomething();

Он равен

bool value = a || b || c;
if (value)
    DoSomething();

Так как порядок оценки условных операторов слева направо, он равен

bool value = (a || b) || c;
if (value)
    DoSomething();

Ответ 6

Их эквиваленты VB могут быть более подробными. || - OrElse, а && - AndAlso в VB.
Это условные операторы; что означает, что в вашем случае задание контроля - if - оценивайте условия по мере необходимости, а не все из них всегда.

Например, в if ( a || b ), если a истинно, не имеет значения, что b; результат верен, и поэтому b не будет оцениваться, и это приведет к более быстрому выполнению.

Эта функция также может использоваться в качестве механизма проверки нулей. if ( a != null && a.prop == somevalue ) предотвратит исключение ссылочной ссылки, если a имеет значение null, и если оно не равно null, его свойство prop будет доступно для оценки второго условия.