# Faster \`div\`, \`rem\` and \`mod\` for small integers?

**URL:** <https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264>\
**Category:** Performance\
**Created:** [June 27, 2025, 12:02am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264 "2025-06-27T00:02:59Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 27, 2025, 12:03am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264/1 "2025-06-27T00:03:00Z")

</div>

I’ve played around with `div`, `rem` and `mod` for signed bit integers. I first noticed that these functions are faster for `Int32` than for `Int64` on amd64 processors. (This becomes clear if you look at the processor instruction set.) Then I tried smaller integers. On several processors (all amd64), the following definitions are faster than the standard functions in Julia:

```julia
Base.div(x::Int16, y::Int16) = div(x % Int32, y % Int32) % Int16
Base.rem(x::Int16, y::Int16) = rem(x % Int32, y % Int32) % Int16
Base.mod(x::Int8, y::Int8) = (z = rem(x, y); ifelse(iszero(z) || signbit(z) == signbit(y), z, z+y))

```

With the benchmark code

```julia
using Chairmarks
M = 1000
for T in (Int32, Int16, Int8)
    @show T
    p = rand(T, M)
    q = map(_ -> (y = rand(T); y in (0, -1) ? T(9) : y), 1:M)
    display(@b similar(q) map!(div, _, $p, $q), map!(rem, _, $p, $q), map!(mod, _, $p, $q))
end

```

I get for the standard functions the timings

| `T` | `div` | `rem` | `mod` |
| --- | --- | --- | --- |
| `Int32` | 3.639 μs | 3.080 μs | 3.921 μs |
| `Int16` | 3.919 μs | 7.016 μs | 8.913 μs |
| `Int8` | 3.372 μs | 3.090 μs | 9.828 μs |

and for the new ones

| `T` | `div` | `rem` | `mod` |
| --- | --- | --- | --- |
| `Int32` | 3.637 μs | 3.082 μs | 3.921 μs |
| `Int16` | 2.803 μs | 3.081 μs | 4.475 μs |
| `Int8` | 3.361 μs | 3.081 μs | 4.300 μs |

I’d be curious to know what other people observe, both for amd64 processors and for other architectures. Thanks in advance for posting your benchmarks below!

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [June 27, 2025, 12:47am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264/2 "2025-06-27T00:47:24Z")

</div>

I can reproduce this on Zen5. Looking at the code, the difference in assembly is very small. It’s simply the difference between

```julia
	mov	eax, edi
	cwd
	idiv	si
	mov	eax, edx
                                        # kill: def $ax killed $ax killed $eax
	ret

```

for `Base.rem` vs

```julia
	movsx	eax, di
; ││ @ int.jl:296 within `rem`
	cdq
	idiv	esi
	mov	eax, edx
; ││ @ int.jl:544 within `rem`
                                        # kill: def $ax killed $ax killed $eax
	ret

```

for your rem. It is very unclear to me why this makes a difference.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 27, 2025, 1:12am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264/3 "2025-06-27T01:12:51Z")

</div>

EDIT: Forget this, it’s wrong, at least for `Int32`.

On a related note, the standard definition for `rem(x, y)` checks if `y == -1`:

```julia
; Function Signature: rem(Int32, Int32)
; @ int.jl:296 within `rem`
define i32 @julia_rem_1412(i32 signext %"x::Int32", i32 signext %"y::Int32") #0 {
top:
  switch i32 %"y::Int32", label %oksrem [
    i32 0, label %fail
    i32 -1, label %after_srem
  ]

[...]

oksrem: ; preds = %top
  %0 = srem i32 %"x::Int32", %"y::Int32"
  br label %after_srem

after_srem: ; preds = %oksrem, %top
  %1 = phi i32 [%0, %oksrem], [0, %top]
  ret i32 %1
}

```

~~I think this is superfluous because the only bad argument is `y == 0`. Once this case has been ruled out, one can safely call LLVM’s `srem`.~~

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [June 27, 2025, 1:38am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264/4 "2025-06-27T01:38:06Z")

</div>

I believe we do this to avoid issues with `rem(typemin(Int), -1)`

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [June 27, 2025, 1:55am UTC](https://discourse.julialang.org/t/faster-div-rem-and-mod-for-small-integers/130264/5 "2025-06-27T01:55:17Z")

</div>

Sorry, I was wrong – `srem` is translated to `idiv` on amd64, which does not allow the combination of `typemin` and `-1`.
