How GCC Eliminates Unnecessary Integer Division

· LeetArxiv ·

3 min read Original article ↗

Here are free GPU credits. First come first serve :)

We code the paper on Fast Unsigned Division by Constants (Ammon, 2011)1 for our number field sieve.

Abstract for Labor of Division (Episode 3): Fast Unsigned Division by Constants (Amon, 2011)

The number field sieve demands us perform repetitive divisibility tests. Observe that GCC runs faster when provided a constant divisor, not a variable, as shown below.

Turns out, GCC finds two magic numbers and replaces division with one multiplication and a comparison. This optimization is Part 8 in our Practical Number Field Sieve for Programmers series:

Part 1: Discrete Logarithms and the Index Calculus Solution.

Part 2: Solving Index Calculus Equations over Integers and Finite Fields.

Part 3: Computation of Discrete Logarithms in Algebraic Number Fields.

Part 4: 2 Dimensional Lattice Basis Reduction.

Part 5: Individual Reduction Phase and Logarithm Collection.

Part 6: Lattice Sieving and Special Q Descent.

Part 7: Continued Fractions and Fast Lattice Sieving.

Part 8: Optimizing Prime Division using Magic Numbers.

First we write this code for comparison:

Next, we observe the corresponding Assembly:

The variable modulo function explicitly calls div while constant modulo for 17 does x%17==0; // x*0xF0F0F0F1 <= 0x0F0F0F0F.

Our techbro ascendants documented this behavior on StackOverflow (Technosaurus, 2018)2:

Even more fascinating, GCC performs the same optimization for regular integer division, as demonstrated in (Qiubit, 2016)3. An interesting question arises: how do we find these magic numbers?

Reciprocal multiplication is the succint term for ‘replacing division with magic number multiplications and comparisons’. Our secondary sources for the remainder of this section are (Jones, 1999)4 and (Ammon, 2010)5.

These magic numbers are precomputed quotients of a rounded up power of 2. At runtime, one multiplies by the magic number, and then divides by the power of 2 to round down. Mathematically, it resembles:

Reciprocal multiplication. Taken from page 1 of (Ammon, 2011)

We follow (Ammon, 2010) and call our magic number, mexact and define it below:

mexact is our magic number

mexact is always a fraction. So we must round it up to an integer, and this rounded up integer becomes our magic number. This is called the round-up algorithm.

(Ammon, 2010) proves why magic numbers work and provides the round-up algorithm alongside division below:

Finding a magic number and using it in division. Round-up algorithm

(Ammon, 2011) introduces the round-down algorithm to address the primary shortcoming of the round-up algorithm from Section 1.1: some magic numbers are 33 bits so don’t fit in typical 32-bit hardware register.

The round-down algorithm is guaranteed to find a 32-bit magic number when the round-up algorithm fails. It is provided below:

Round-down algorithm as introduced in (Amon, 2011)

We follow the LibDivide reference6 and write this code to find magic numbers and further test if an integer n modulo a divisor d is 0:

An example with 7 is:

Try it yourself!