Consider emitting more performant codegen for looped switch statement, such as that found in a simple interpreter

Open
#118,809 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Stale
Tech stack
csharp

Research direction

Start by comparing label_array_interpreter.c and standard_interpreter.c in the linked ExampleInterpreterJumpTablesDifference repository, including its benchmark procedure. Then locate the .NET runtime JIT switch and loop code-generation paths; done means demonstrating the proposed optimized control flow without regressing other switch cases or size-focused modes.

Written by the indexing model from the issue text.

Description

area-CodeGen-coreclr

Basically the request is that when having code like

while (true)
{
    switch (instruction)
    {
        ... handle cases - some end with break (majority), others with goto, others with return, etc.
    }
}

instead of emitting assembly like (in generic format):

a:
branch <jump table>[value]

case 0:
...
goto a;

case 1:
...
goto a;

...

we would emit assembly like

a:
branch <jump table>[value]

case 0:
...
branch <jump table>[value] + (&a - &here);

case 1:
...
branch <jump table>[value] + (&a - &here);

...

GCC supports emitting assembly like this via labels as values, but that feature is far more generic than is needed to achieve the benefits of the above for 99% of cases, and runs into some issues I believe plus would be very difficult to add to IL anyway I suspect.

Here is an example of me implementing an interpreter in C with the exact same logic & code (outside of the goto & switch handling), but using the 2 different approaches. On my machines I measure about a 33% performance improvement on macOS x64 (~0.92s vs ~1.37s) and a 45% performance improvement on macOS arm64 (~0.85s vs ~1.55s). I tested with for i in $(seq 1 10) ; do time sudo standard_interpreter/label_array_interpreter 50000000 ; sleep 1 ; done (note: you have to run in the directory with instructions.txt).

Source code: https://github.com/hamarb123/ExampleInterpreterJumpTablesDifference (the difference is in https://github.com/hamarb123/ExampleInterpreterJumpTablesDifference/blob/main/label_array_interpreter.c vs https://github.com/hamarb123/ExampleInterpreterJumpTablesDifference/blob/main/standard_interpreter.c).

Due to such large potential performance benefits for some domains that rely heavily on this kind of thing (especially stuff like interpreters), I think it would be well worth implementing this in .NET. If additional code size is a concern, it could be saved for only optimised code & disabled on NAOT in optimise for size mode.

I would greatly appreciate this being considered as I plan to write an interpreter at some point (in C# ideally) with a structure along these lines, and it seems I could end up missing up to something like a third to a half of the potential performance I could have.

/cc @jakobbotsch who I was discussing this on the c# discord with.

Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/runtime

All issues in dotnet/runtime

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.