JuliaMath / FixedPointNumbers.jl

fixed point types for julia

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optimizing `Normed` -> `Normed` conversions

kimikage opened this issue · comments

As I suggested here, the current Normed -> Normed conversions are inefficient in some cases.

function Normed{T,f}(x::Normed{T2}) where {T <: Unsigned,T2 <: Unsigned,f}
U = Normed{T,f}
y = round((rawone(U)/rawone(x))*reinterpret(x))
(0 <= y) & (y <= typemax(T)) || throw_converterror(U, x)
reinterpret(U, _unsafe_trunc(T, y))
end

The current conversion method has two problems:

  1. It always checks the input range even if there is no need, and may throw the exception.
  2. It always uses floating-point operations even if there is no need.

The former means that the method is not SIMD-suitable.
Regarding the latter, the conversion between types with the same f is already specialized.

Normed{T1,f}(x::Normed{T2,f}) where {T1 <: Unsigned,T2 <: Unsigned,f} = Normed{T1,f}(convert(T1, x.i), 0)

There is also the N0f8->N0f16 specialization. (I wonder why.)
N0f16(x::N0f8) = reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x)))

I do not think these are urgent problems. However, the optimization may be useful in the future to speed up the accumulation (reduce). And I just found a (ugly) workaround for the constant division problem, so I am writing this issue as a memorandum or reminder.

The figures below visualize the cases where the optimization is available.

  • The positive (greenish) area means that the conversion is overflow-safe (i.e. there is no need for the range checking).
  • The negative (reddish) area means that the conversion is unsafe (i.e. it may throw the exception).
  • The deep-colored cells means that the conversion does not need floating-point operations.
    • As mentioned above, the f1 == f2 lines are already supported.

n8_n16
n8_n32
n16_n32

You can get the result of other cases with the following script:

using Gadfly, Colors

set_default_plot_size(15cm, 8cm)

function mat(dest, src)
    b1, b2 = 8*sizeof(dest), 8*sizeof(src)
    if b1 > b2 # widening
        safe = [b1-f1 > b2-f2 || b2 == f2 ? 1 : -1 for f2=1:b2, f1=1:b1]
    else
        safe = [b1-f1 >= b2-f2 ? 1 : -1 for f2=1:b2, f1=1:b1]
    end
    safe .* [isinteger(f1/f2) ? f2/f1 : 1/36 for f2=1:b2, f1=1:b1]
end;

function plot_mat(dest, src)
    s = Scale.color_continuous(
        colormap=Scale.lab_gradient(LCHab(0, 100, 20), "white", LCHab(30, 100, 200)),
        minvalue=-1, maxvalue=1)
    m = mat(dest, src)
    spy(m, 
        Guide.title("Normed{$src,f2} -> Normed{$dest,f1}"),
        Guide.xlabel("f1"), Guide.xticks(ticks=axes(m, 2)),
        Guide.ylabel("f2"), Guide.yticks(ticks=axes(m, 1)),
        Guide.colorkey(title="scale"), s,
        Theme(plot_padding=[0mm,2mm,5mm,0mm]))
end;

plot_mat(UInt16, UInt8)
plot_mat(UInt32, UInt8)
plot_mat(UInt32, UInt16)

Edit: The safe areas are wrong. I forgot to take account of the "carry" or "overlapping". I will soon fix the figures and the script above. Updated.

There is also the N0f8->N0f16 specialization. (I wonder why.)

I can't remember for sure, it seemed important at the time 😄. (Probably, performance.) But it's certainly more specific than needed. Julia's compiler was not what it is now, so the implementations in #138 probably would have resulted in a big branch.

And I agree, Normed->Normed conversions may be a good way to improve reduce. Kudos as always for looking into this!

See also: #97 (It seems to mainly focus on Fixed.)