llvm / llvm/llvm-project

performance difference in Clang’s handling of modulo on small signed types

Open
#205,500 2 comments 0 reactions 0 assignees View on GitHub
llvm missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

The C++ Weekly episode about “Why is GCC better than Clang?” demonstrates a large performance difference when compiling a Game of Life implementation with Clang vs GCC. The code uses minimal signed integer types (`int8_t`, `int16_t`) for board indices and a helper `floor_modulo` function implemented as:

```c++
[[nodiscard]] constexpr auto floor_modulo(auto dividend, auto divisor) {
return ((dividend % divisor) + divisor) % divisor;
}
```

When compiled with GCC (e.g. g++ 13), the code runs in ~21 seconds, but when compiled with Clang (e.g. clang++ 17) it runs in ~42 seconds. Changing the board dimensions to size_t or changing the type of the divisor parameter to an unsigned type (e.g. size_t) eliminates the performance gap: Clang then produces code similar in speed to GCC.

The video showing the issue: https://youtu.be/4P32EFClwuo?is=8jbDeCD62ZJYiOms

In the original code the minimal integer types cause % on small signed types, which generates additional sign-extension and conditional correction instructions in the generated code. Changing the divisor to size_t or reworking floor_modulo so that the dividend is first made non‑negative (e.g. dividend += divisor; return dividend % divisor;) avoids these slow paths and greatly improves Clang’s performance.

This appears to be an optimization issue in Clang’s code generation for signed modulo on small integer types. Reproduction:

1. Clone https://github.com/lefticus/cpp_weekly and build parallel_algorithms/game_of_life.cpp with Clang and GCC at -O3.
2. Run the benchmark or run_performance_tests.sh script. GCC completes in ~21 s; Clang takes ~42 s.
3. Modify the floor_modulo function to take size_t divisor or change width/height to size_t. Rebuild with Clang; performance becomes similar to GCC.

Contributor guide

Open the contributing guide

Research direction

Start with parallel_algorithms/game_of_life.cpp and run_performance_tests.sh from the cpp_weekly reproduction, building with Clang and GCC at -O3. Compare the generated behavior for signed int8_t/int16_t modulo with the size_t or unsigned-divisor variants. Done means the small signed-type case no longer has the reported Clang performance gap, with suitable compiler coverage if the fix is isolated.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.