Если у меня есть большой файл данных, содержащий целые числа произвольной длины, которые нужно отсортировать по второму полю:
1 3 4 5
1 4 5 7
-1 34 56 7
124 58394 1384 -1938
1948 3848089 -14850 0
1048 01840 1039 888
//consider this is a LARGE file, the data goes on for quite some time
и я призываю qsort быть моим любимым оружием в моей функции сортировки. Будет ли использование сокращенного ЕСЛИ значительного повышения производительности к общему времени, которое требуется для сортировки данных? Или сокращение IF используется только как удобный инструмент для организации кода?
num2 = atoi(Str);
num1 = atoi(Str2);
LoggNum = (num2 > num1) ? num2 : num1; //faster?
num2 = atoi(Str);
num1 = atoi(Str2);
if(num2 > num1) //or the same?
LoggNum = num2;
else
LoggNum = num1;
Любой современный компилятор будет создавать идентичный код в этих двух случаях, разница только в стиле и удобстве.
На этот вопрос нет ответа … компилятор может генерировать любой код, который он считает подходящим. Тем не менее, только особо глупый компилятор будет производить существенно другой код для этих случаев. Вы должны писать все, что считаете нужным, чтобы выразить, как работает ваша программа … для меня версия троичного оператора более краткая и удобочитаемая, но людям, не привыкшим к C и / или C ++, поначалу может быть проще понять версию if / else.
Если вы заинтересованы в таких вещах и хотите лучше понять процесс генерации кода, вы должны научиться вызывать ваш компилятор, запрашивая у него вывод на ассемблере, показывающий инструкции процессора для программы. Например, компиляторы GNU принимают -S
флаг, который производит .s
файлы на ассемблере.
Единственный способ узнать это профиль. Однако троичный оператор позволяет выполнить инициализацию объекта:
Sometype z = ( x < y) ? something : somethingElse; // copy initialization
С if-else
Вы должны будете использовать дополнительное назначение, чтобы получить эквивалентное поведение.
SomeType z; // default construction
if ( x < y) {
z = something; // assignment
} else {
z = somethingElse; // assignment
}
Это может оказать влияние, если накладные расходы на присвоение SomeType
большой.
Мы проверили эти два утверждения в VC 2010:
Генерируемая сборка для? = Is:
01207AE1 cmp byte ptr [ebp-101h],0
01207AE8 jne CTestSOFDlg::OnBnClickedButton1+47h (1207AF7h)
01207AEA push offset (1207BE5h)
01207AEF call @ILT+840(__RTC_UninitUse) (120134Dh)
01207AF4 add esp,4
01207AF7 cmp byte ptr [ebp-0F5h],0
01207AFE jne CTestSOFDlg::OnBnClickedButton1+5Dh (1207B0Dh)
01207B00 push offset (1207BE0h)
01207B05 call @ILT+840(__RTC_UninitUse) (120134Dh)
01207B0A add esp,4
01207B0D mov eax,dword ptr [num2]
01207B10 cmp eax,dword ptr [num1]
01207B13 jle CTestSOFDlg::OnBnClickedButton1+86h (1207B36h)
01207B15 cmp byte ptr [ebp-101h],0
01207B1C jne CTestSOFDlg::OnBnClickedButton1+7Bh (1207B2Bh)
01207B1E push offset (1207BE5h)
01207B23 call @ILT+840(__RTC_UninitUse) (120134Dh)
01207B28 add esp,4
01207B2B mov ecx,dword ptr [num2]
01207B2E mov dword ptr [ebp-10Ch],ecx
01207B34 jmp CTestSOFDlg::OnBnClickedButton1+0A5h (1207B55h)
01207B36 cmp byte ptr [ebp-0F5h],0
01207B3D jne CTestSOFDlg::OnBnClickedButton1+9Ch (1207B4Ch)
01207B3F push offset (1207BE0h)
01207B44 call @ILT+840(__RTC_UninitUse) (120134Dh)
01207B49 add esp,4
01207B4C mov edx,dword ptr [num1]
01207B4F mov dword ptr [ebp-10Ch],edx
01207B55 mov eax,dword ptr [ebp-10Ch]
01207B5B mov dword ptr [LoggNum],eax
а для оператора if else:
01207B5E cmp byte ptr [ebp-101h],0
01207B65 jne CTestSOFDlg::OnBnClickedButton1+0C4h (1207B74h)
01207B67 push offset (1207BE5h)
01207B6C call @ILT+840(__RTC_UninitUse) (120134Dh)
01207B71 add esp,4
01207B74 cmp byte ptr [ebp-0F5h],0
01207B7B jne CTestSOFDlg::OnBnClickedButton1+0DAh (1207B8Ah)
01207B7D push offset (1207BE0h)
01207B82 call @ILT+840(__RTC_UninitUse) (120134Dh)
01207B87 add esp,4
01207B8A mov eax,dword ptr [num2]
01207B8D cmp eax,dword ptr [num1]
01207B90 jle CTestSOFDlg::OnBnClickedButton1+100h (1207BB0h)
01207B92 cmp byte ptr [ebp-101h],0
01207B99 jne CTestSOFDlg::OnBnClickedButton1+0F8h (1207BA8h)
01207B9B push offset (1207BE5h)
01207BA0 call @ILT+840(__RTC_UninitUse) (120134Dh)
01207BA5 add esp,4
01207BA8 mov eax,dword ptr [num2]
01207BAB mov dword ptr [LoggNum],eax
01207BB0 cmp byte ptr [ebp-0F5h],0
01207BB7 jne CTestSOFDlg::OnBnClickedButton1+116h (1207BC6h)
01207BB9 push offset (1207BE0h)
01207BBE call @ILT+840(__RTC_UninitUse) (120134Dh)
01207BC3 add esp,4
01207BC6 mov eax,dword ptr [num1]
01207BC9 mov dword ptr [LoggNum],eax
как вы можете видеть? = оператор имеет еще две команды asm:
01207B4C mov edx,dword ptr [num1]
01207B4F mov dword ptr [ebp-10Ch],edx
который занимает больше тактов процессора.
для цикла с размером 97000000, если еще быстрее.
Эта конкретная оптимизация укусила меня в реальной базе кода, где переход от одной формы к другой в функции блокировки сэкономил 10% времени выполнения в тесте макроса.
Давайте проверим это с помощью gcc 4.2.1 на MacOS:
int global;
void
foo(int x, int y)
{
if (x < y)
global = x;
else
global = y;
}
void
bar(int x, int y)
{
global = (x < y) ? x : y;
}
Мы получаем (очищены):
_foo:
cmpl %esi, %edi
jge L2
movq _global@GOTPCREL(%rip), %rax
movl %edi, (%rax)
ret
L2:
movq _global@GOTPCREL(%rip), %rax
movl %esi, (%rax)
ret
_bar:
cmpl %edi, %esi
cmovle %esi, %edi
movq _global@GOTPCREL(%rip), %rax
movl %edi, (%rax)
ret
Принимая во внимание, что инструкции cmov были добавлены специально для улучшения производительности операций такого типа (путем исключения остановок конвейера), ясно, что «?:» В этом конкретном компиляторе быстрее.
Должен ли компилятор генерировать один и тот же код в обоих случаях? Да. Будет ли он? Нет, конечно, нет, никакой компилятор не идеален. Если вы действительно заботитесь о производительности (в большинстве случаев это не так), проверьте и посмотрите.