# Weird ldexp performance

**URL:** <https://discourse.julialang.org/t/weird-ldexp-performance/43416>\
**Category:** Numerics\
**Created:** [July 21, 2020, 7:04am UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416 "2020-07-21T07:04:15Z")\
**Posts on this page:** 5\
**Page:** 1

<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:** [July 21, 2020, 7:04am UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416/1 "2020-07-21T07:04:15Z")

</div>

I’m trying to write a faster implimentation of `exp`, and along the way, I noticed that I have no idea how fast `ldexp` is, and my efforts to benchmark it result in madness.

My code is

```julia
function myexp(x::Float64)
    # Reduction: x = M*ln2 + ln2*(j/1024) + r
    # M, j chosen such that 0<1024, abs(r) < ln2/2048
    
    N = round(Int, x*1477.3197218702985) # 1024/ln2
    r = x - N*0.0006769015435155716 # ln2/1024
    M = N >> 10
    j = N - M<<10
    
    # exp(x) = exp(ln2*M + ln2*j/2^k + r)
    small_part = J_TABLE[j+1] * small_exp(r)
    return ldexp(small_part, M)
end

```

Unfortunately, this is about 2x slower than if the last line is replaced with `return small_part* (1<<M)`. When I saw this, everything made sense. I then tried to benchmark the performance of `ldexp` by itself, and discovered that the function, no matter how I timed it (with BenchmarkTools) only took 15ns. Any ideas what’s happening here?

---

<div class="post-metadata">

**Author:** ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)\
**Post date:** [July 21, 2020, 7:17am UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416/2 "2020-07-21T07:17:26Z")

</div>

Why is 15ns madness? That’s about what I’d expect. Can you give an MWE and show your benchmarking code?

---

<div class="post-metadata">

**Author:** ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)\
**Post date:** [July 21, 2020, 7:18am UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416/3 "2020-07-21T07:18:34Z")

</div>

Actually I get

```julia
julia> x = 1.0; y = 3
3

julia> @btime ldexp($(Ref(x))[], $(Ref(y))[])
  3.272 ns (0 allocations: 0 bytes)
8.0

```

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [July 21, 2020, 12:16pm UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416/4 "2020-07-21T12:16:47Z")

</div>

> [@Oscar\_Smith](#):
>
> `return small_part* (1<<M)` .

I’d stick with this. Compare the native code.  
But it is possible to do even better if you can guarantee that `M` belongs to a narrow range (such as `0 <= M < 64`):

```julia
julia> myldexp(x, y) = x * (1 << y)
myldexp (generic function with 1 method)

julia> @code_native debuginfo=:none myldexp(0.125, 3)
        .text
        movl $1, %eax
        shlxq %rdi, %rax, %rcx
        xorl %edx, %edx
        cmpq $63, %rdi
        cmovaq %rdx, %rcx
        movq %rdi, %rsi
        negq %rsi
        shrxq %rsi, %rax, %rax
        cmpq $63, %rsi
        cmovaq %rdx, %rax
        testq %rdi, %rdi
        cmovnsq %rcx, %rax
        vcvtsi2sd %rax, %xmm1, %xmm1
        vmulsd %xmm0, %xmm1, %xmm0
        retq
        nopl (%rax,%rax)

julia> using SIMDPirates

julia> myldexp(x, y) = (SIMDPirates.assume(0 ≤ y < 64); x * (1 << y))
myldexp (generic function with 1 method)

julia> @code_native debuginfo=:none myldexp(0.125, 3)
        .text
        movl $1, %eax
        shlxq %rdi, %rax, %rax
        vcvtsi2sd %rax, %xmm1, %xmm1
        vmulsd %xmm0, %xmm1, %xmm0
        retq
        nopw %cs:(%rax,%rax)
        nop

```

But I think LLVM is good at figuring out restricted ranges on its own, so odds are `assume` is unnecessary.

---

<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:** [July 21, 2020, 2:12pm UTC](https://discourse.julialang.org/t/weird-ldexp-performance/43416/5 "2020-07-21T14:12:24Z")

</div>

My bench-marking code is as follows:

```julia
x = rand(1000);
@btime myexp.($x)

```

I find that the ldexp version takes 6.1us, vs 3.0us for the bit-shifted version. For the actuall problem, I don’t have any restrictions on the range of M, but if it helps, I know that `small_part` is around `1`.
