При просмотре вывода сборки следующего кода (без оптимизаций -O2 и -O3 дают очень похожие результаты):
int main(int argc, char **argv)
{
volatile float f1 = 1.0f;
volatile float f2 = 2.0f;
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
GCC делает то, за чем мне трудно следовать:
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
Почему GCC дважды перемещает значения с плавающей точкой в xmm0 и xmm1, а также дважды запускает ucomiss?
Не будет ли быстрее сделать следующее?
.LC2:
.string "+"
.LC3:
.string "-"
.text
.globl main
.type main, @function
main:
.LFB2:
pushq %rbp
.LCFI0:
movq %rsp, %rbp
.LCFI1:
subq $32, %rsp
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
movl $0x3f800000, %eax
movl %eax, -4(%rbp)
movl $0x40000000, %eax
movl %eax, -8(%rbp)
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jb .L8 # jump if less than
je .L4 # jump if equal
.L7:
movl $.LC2, %edi
call puts
jmp .L4
.L8:
movl $.LC3, %edi
call puts
.L4:
movl $0, %eax
leave
ret
Я вовсе не настоящий программист на ассемблере, но мне показалось странным иметь дублирующиеся инструкции. Есть ли проблема с моей версией кода?
Обновление
Если вы удалите летучее вещество, которое было у меня изначально, и замените его на scanf (), вы получите те же результаты:
int main(int argc, char **argv)
{
float f1;
float f2;
scanf("%f", &f1);
scanf("%f", &f2);
if(f1 > f2)
{
puts("+");
}
else if(f1 < f2)
{
puts("-");
}
return 0;
}
и соответствующий ассемблер:
.LCFI2:
movl %edi, -20(%rbp)
movq %rsi, -32(%rbp)
leaq -4(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
leaq -8(%rbp), %rsi
movl $.LC0, %edi
movl $0, %eax
call scanf
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm0, %xmm1
jbe .L9
.L7:
movl $.LC1, %edi
call puts
jmp .L4
.L9:
movss -4(%rbp), %xmm1
movss -8(%rbp), %xmm0
ucomiss %xmm1, %xmm0
jbe .L4
.L8:
movl $.LC2, %edi
call puts
.L4:
movl $0, %eax
leave
ret
Окончательное обновление
После рассмотрения некоторых последующих комментариев кажется, что Хан (который комментировал пост Джонатана Леффлера) прибил эту проблему. GCC не проводит оптимизацию не потому, что не может, а потому, что я этого не говорил Кажется, все сводится к правилам IEEE с плавающей запятой, и для обработки строгих условий GCC не может просто выполнить переход, если выше, или переход, если ниже, после первого UCOMISS, потому что он должен обрабатывать все специальные условия чисел с плавающей запятой. При использовании рекомендации хана оптимизатора -ffast-math (ни один из флагов -Ox не включает -ffast-math, поскольку это может сломать некоторые программы) GCC делает именно то, что я искал:
Следующая сборка была произведена с использованием GCC 4.3.2 "gcc -S -O3 -ffast-math test.c"
.LC0:
.string "%f"
.LC1:
.string "+"
.LC2:
.string "-"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB25:
subq $24, %rsp
.LCFI0:
movl $.LC0, %edi
xorl %eax, %eax
leaq 20(%rsp), %rsi
call scanf
leaq 16(%rsp), %rsi
xorl %eax, %eax
movl $.LC0, %edi
call scanf
movss 20(%rsp), %xmm0
comiss 16(%rsp), %xmm0
ja .L11
jb .L12
xorl %eax, %eax
addq $24, %rsp
.p2align 4,,1
.p2align 3
ret
.p2align 4,,10
.p2align 3
.L12:
movl $.LC2, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
.p2align 4,,10
.p2align 3
.L11:
movl $.LC1, %edi
call puts
xorl %eax, %eax
addq $24, %rsp
ret
Обратите внимание, что две инструкции UCOMISS теперь заменены одним COMISS, за которым сразу следуют JA (переход вверху, если выше) и JB (переход внизу, если ниже). GCC может прибегнуть к этой оптимизации, если вы позволите ей использовать -ffast-math!
UCOMISS против COMISS (http://www.softeng.rl.ac.uk/st/archive/SoftEng/SESP/html/SoftwareTools/vtune/users_guide/mergedProjects/analyzer_ec/mergedProjects/reference_olh/mergedProjects/instructions/instruct32_hh/vc315.htm): "Инструкция UCOMISS отличается от инструкции COMISS тем, что она сообщает о недопустимом исключении с плавающей запятой SIMD, только если исходный операнд является SNaN. Инструкция COMISS сигнализирует о недопустимости, если исходный операнд либо QNaN или SNaN. "
Еще раз спасибо всем за полезное обсуждение.