Skip to content

Performance Improvement - Ternary operator with constants #61480

@hopperpl

Description

@hopperpl

Description

Using the ternary operator with constants creates a lot of unnecessary branches and jumps. It can be factored into a common base and based on the boolean result, the difference can be multiplied with the condition state and then added -- or a "-1" mask (negated state) on the difference can be used.

clang, gcc, msvc all do that.

public int Value(bool cond) => cond ? c_ConstA : c_ConstB;

It's a common code pattern to return 2 different constants based on a flag, on a condition, etc. Like makeUpper ? 'A' : 'a'

public int Value(bool cond) => c_ConstA + cond * (c_ConstB - c_ConstA);
public int Value(bool cond) => c_ConstA + (-cond & (c_ConstB - c_ConstA));

Note: C# cannot easily convert a boolean into an integer (0/1) but the JIT will have no issue with that.

Configuration

.NET 6.0.0 (6.0.21.52210)

Regression?

No

Data

Analysis

    [Benchmark]
    public int A() => Cond ? 171 : 239;

    [Benchmark]
    public int B() => 171 + CondInt * (239 - 171);

    volatile bool Cond;
    volatile int  CondInt; // bool as int as there is no friction-free C# conversion

.NET 6.0.0 (6.0.21.52210), X64 RyuJIT

; Benchmark+Bench.A()
       7FFB738AD610 cmp       byte ptr [rcx+18],0
       7FFB738AD614 jne       short M00_L00
       7FFB738AD616 mov       eax,0EF
       7FFB738AD61B ret
M00_L00:
       7FFB738AD61C mov       eax,0AB
       7FFB738AD621 ret
; Total bytes of code 18

.NET 6.0.0 (6.0.21.52210), X64 RyuJIT

; Benchmark+Bench.B()
       7FFB7389C4E0 imul      eax,[rcx+14],44
       7FFB7389C4E4 add       eax,0AB
       7FFB7389C4E9 ret
; Total bytes of code 10

I did some benchmarking, but added a 1000-loop to reduce the call-frame jitter, and the difference is significant. And that doesn't even include branch misprediction which real-world code will have at around 50%. Here, misprediction is 0% as the code is too artificial and predictable. Nonetheless, it's twice as fast and with bad branch prediction it will be an order of magnitude when the 18-cycle penalty has an effect.

    [Benchmark]
    public bool A()
    {
      for (var nn = 0 ; nn < 1000 ; ++nn) Value = Cond ? 171 : 239;
      return true;
    }

    [Benchmark]
    public bool B()
    {
      for (var nn = 0 ; nn < 1000 ; ++nn) Value = 171 + CondI * (239 - 171);
      return true;
    }

    volatile int  Value;
    volatile bool Cond;
    volatile int  CondI;
| Method |     Mean |   Error |  StdDev | Code Size |
|------- |---------:|--------:|--------:|----------:|
|      A | 756.3 ns | 2.37 ns | 2.22 ns |      41 B |
|      B | 434.1 ns | 0.26 ns | 0.24 ns |      30 B |

Metadata

Metadata

Assignees

No one assigned

    Labels

    tenet-performancePerformance related issueuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions