Что происходит в конструкциях "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 в значение || Случай
Ответы
Ответ 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
будет доступно для оценки второго условия.