performance difference in Clang’s handling of modulo on small signed types
- 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
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