Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2022-01

Double-checking an optimisation

8 messages · 3 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

J. Gareth Moreton

Hi everyone,

So a merge request of mine was just approved that allows the peephole optimizer access to more registers when it needs one for temporary storage.  It allows it to make an optimisation on x86_64-win64 that wasn't possible before due to the lack of available volatile registers.  In packages\numlib\src\dsl.pas - before:

.Lj184:
    ...
    cmpl    $1,%ecx
    jng    .Lj188
    subl    $1,%ecx
.Lj188:
    ...

After:

.Lj184:
    ...
    cmpl    $1,%ecx
    setg    %bl
    movzbl    %bl,%ebx
    subl    %ebx,%ecx
    ...

%ebx is a non-volatile register, but the current subroutine preserves it and it's not currently in use, so the peephole optimizer can borrow it for a few instructions.

I need to double-check though... is this actually a good optimisation for speed?  It removes a jump and a label, which might permit other long-range optimisations, but it's 3 instructions that are in a dependency chain.

Gareth aka. Kit

This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Martin Frb
to J. Gareth Moreton
J. Gareth Moreton wrote 34 lines
Hi everyone, So a merge request of mine was just approved that allows the peephole optimizer access to more registers when it needs one for temporary storage.  It allows it to make an optimisation on x86_64-win64 that wasn't possible before due to the lack of available volatile registers.  In packages\numlib\src\dsl.pas - before: .Lj184:     ...     cmpl    $1,%ecx     jng    .Lj188     subl    $1,%ecx .Lj188:     ... After: .Lj184:     ...     cmpl    $1,%ecx     setg    %bl     movzbl    %bl,%ebx     subl    %ebx,%ecx     ... %ebx is a non-volatile register, but the current subroutine preserves it and it's not currently in use, so the peephole optimizer can borrow it for a few instructions. I need to double-check though... is this actually a good optimisation for speed?  It removes a jump and a label, which might permit other long-range optimisations, but it's 3 instructions that are in a dependency chain.

I take it, it also is one (or two?) bytes longer? If that is in a loop, which otherwise is exactly within a 32 byte aligned block, then that could cause a slow down too. (If the loop is 16 bytes long, but aligned to a 32byte-bound+16, then it may slow down if the loop code size goes from 16 to 17 bytes, because that is when it goes over the boundary of the 32byte block.
This is a bit hard to predict. But within very small loops (even 2 or maybe 3 blocks of 32 bytes), size may be as important. (Actually a good question, what weighs more....)

Reply by email

J. Gareth Moreton
to Martin Frb
Martin Frb wrote 14 lines
I take it, it also is one (or two?) bytes longer? If that is in a loop, which otherwise is exactly within a 32 byte aligned block, then that could cause a slow down too. (If the loop is 16 bytes long, but aligned to a 32byte-bound+16, then it may slow down if the loop code size goes from 16 to 17 bytes, because that is when it goes over the boundary of the 32byte block. This is a bit hard to predict. But within very small loops (even 2 or maybe 3 blocks of 32 bytes), size may be as important. (Actually a good question, what weighs more....) _______________________________________________ fpc-devel maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

jng .Lj188 would be 2 bytes long here since it's a short jump, while setg %bl and movzbl %bl,%ebx are 3 bytes long apiece, so the code size goes grow by 4 bytes overall (it would grow by 6 bytes if a REX prefix has to be used to encode, say, %r8b etc.)

Some sizes are very hard to predict, like some optimisations are not performed under -Os because it would increase code size, but in some cases it causes a cascade that ends up both shrinking the code size and making the block several cycles faster.

I dare say, if there's one possible place for AI and machine learning, it would be compiler optimisation!

Gareth aka. Kit

This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Florian Klämpfl
to J. Gareth Moreton: So a merge request of mine was just approved that allows the peephole optimizer access to…
J. Gareth Moreton wrote 27 lines
Hi everyone, So a merge request of mine was just approved that allows the peephole optimizer access to more registers when it needs one for temporary storage.  It allows it to make an optimisation on x86_64-win64 that wasn't possible before due to the lack of available volatile registers. In packages\numlib\src\dsl.pas - before: .Lj184:     ...     cmpl    $1,%ecx     jng    .Lj188     subl    $1,%ecx .Lj188:     ... After: .Lj184:     ...     cmpl    $1,%ecx     setg    %bl     movzbl    %bl,%ebx     subl    %ebx,%ecx     ... %ebx is a non-volatile register, but the current subroutine preserves it and it's not currently in use, so the peephole optimizer can borrow it for a few instructions. > I need to double-check though... is this actually a good optimisation for speed?

I think getting rid of jumps is always good as it also reduces branch predictor load. Not too mention that iirc most CPUs can handle correctly only one jump per 16 bytes in the branch predictor.

  It removes a jump and a label, which might permit other long-range optimisations, but it's 3 instructions that are in a dependency chain.

Didn't you implement something which transformed the code above in

xorl %ebx,%ebx
cmpl $1,%ecx
setg %bl
subl %ebx,%ecx

?

Reply by email

J. Gareth Moreton
to Florian Klämpfl
Florian Klämpfl wrote 11 lines
>   It removes a jump and a label, which might permit other long-range > optimisations, but it's 3 instructions that are in a dependency chain. Didn't you implement something which transformed the code above in       xorl    %ebx,%ebx       cmpl    $1,%ecx       setg    %bl       subl    %ebx,%ecx ?

I believe I did, yes.  I'll have to double-check if the "jcc -> setcc" optimisation is actually mine or if I just adapted it to use GetIntRegisterBetween.  That's certainly the better sequence because XOR is a smaller instruction (2 bytes, I believe) and it breaks the dependency chain, since it can execute at the same time as the CMP instruction.

Either way, it's not being performed here, so that's another optimisation to add to my ever-growing list!  Yes, I've got a new optimisation ready to merge request soon - I'm running my local tests as we speak.  I need to discipline myself a bit and try to fix the bug on 3.2.4's i386-linux implementation.

Gareth aka. Kit

This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Florian Klämpfl
to J. Gareth Moreton
Show quoted text 17 lines
Am 09.01.2022 um 15:08 schrieb J. Gareth Moreton via fpc-devel : On 09/01/2022 12:35, Florian Klämpfl via fpc-devel wrote: >> It removes a jump and a label, which might permit other long-range optimisations, but it's 3 instructions that are in a dependency chain. > > Didn't you implement something which transformed the code above in > > xorl %ebx,%ebx > cmpl $1,%ecx > setg %bl > subl %ebx,%ecx > > ? I believe I did, yes. I'll have to double-check if the "jcc -> setcc" optimisation is actually mine or if I just adapted it to use GetIntRegisterBetween. That's certainly the better sequence because XOR is a smaller instruction (2 bytes, I believe) and it breaks the dependency chain, since it can execute at the same time as the CMP instruction. Either way, it's not being performed here, so that's another optimisation to add to my ever-growing list! Yes, I've got a new optimisation ready to merge request soon - I'm running my local tests as we speak. I need to discipline myself a bit and try to fix the bug on 3.2.4's i386-linux implementation.

Yes, this is always why I am behind with the merge request, we have to finish 3.2.4.

Reply by email

Martin Frb
to J. Gareth Moreton: jng .Lj188 would be 2 bytes long here since it's a short jump, while setg %bl and movzbl…
J. Gareth Moreton wrote 25 lines
On 09/01/2022 01:47, Martin Frb via fpc-devel wrote: > > I take it, it also is one (or two?) bytes longer? If that is in a > loop, which otherwise is exactly within a 32 byte aligned block, then > that could cause a slow down too. (If the loop is 16 bytes long, but > aligned to a 32byte-bound+16, then it may slow down if the loop code > size goes from 16 to 17 bytes, because that is when it goes over the > boundary of the 32byte block. > This is a bit hard to predict. But within very small loops (even 2 or > maybe 3 blocks of 32 bytes), size may be as important. (Actually a > good question, what weighs more....) > _______________________________________________ > fpc-devel maillist  -  > https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel > jng .Lj188 would be 2 bytes long here since it's a short jump, while setg %bl and movzbl %bl,%ebx are 3 bytes long apiece, so the code size goes grow by 4 bytes overall (it would grow by 6 bytes if a REX prefix has to be used to encode, say, %r8b etc.) Some sizes are very hard to predict, like some optimisations are not performed under -Os because it would increase code size, but in some cases it causes a cascade that ends up both shrinking the code size and making the block several cycles faster.

Btw, have you seen this?
https://www.agner.org/optimize/optimizing_assembly.pdf

Page 70, it says that under some conditions a branch may be faster than a conditional move.

Reply by email