Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option for unchecked divison methods #9

Closed
BioTurboNick opened this issue May 10, 2024 · 9 comments
Closed

Option for unchecked divison methods #9

BioTurboNick opened this issue May 10, 2024 · 9 comments

Comments

@BioTurboNick
Copy link
Collaborator

Julia's default integer division is checked. This occurs with division by 0, and typemin(T) ÷ -one(T).

LLVM deems either of these cases undefined behavior, which is very very bad because the compiler is allowed to do whatever it wants in that case. Also on Windows it may throw a Win32 exception that crashes Julia.

This is in contrast to +/-/*/^/abs where overflow has a defined result of wrapping around. And for example, C# doesn't stop checking division in an unchecked context. So it makes sense to not automatically follow this.

However, we could consider an option passed to the unchecked macros that allows people to opt in to unchecked division.

@kimikage
Copy link

kimikage commented May 11, 2024

As suggested in #7 (comment), we would be able to explicitly use @wrapping or @saturating instead of @unchecked for divisions (without the errors).

Fixed-point numbers have characteristics intermediate between integers and floating-point numbers, i.e. they can easily overflow as well as divide-by-zero. So, there can be more disagreement as to whether the default division should be checked.
cf. https://discourse.julialang.org/t/rfc-what-should-the-arithmetic-within-the-fixedpointnumbers-be/46697

In other words, we have at least the possibility of drawing boundaries in terms of two aspects: operation (operator) and type.

operation \ type <:Signed <:Integer package-defined <:Any
div/cld/fld
plus rem/mod
anything

@BioTurboNick
Copy link
Collaborator Author

Might be easiest for users for the default to emulate how Julia treats its integer types. That way they don't have to think about the peculiarities of which possibly-overflowing number type is being used.

Should we consider rem/mod separately from div/cld/fld? LLVM for example links their behavior together so that the compiler can be free to use an instruction that returns both the remainder and quotient simultaneously: https://llvm.org/docs/LangRef.html#srem-instruction

Maybe the API could be something like this? Since it can potentially crash Julia, marking it as unsafe seems prudent.

@unchecked :unsafe_div begin

end

And then a new @unchecked without the symbol would reset back to default.

@kimikage
Copy link

Should we consider rem/mod separately from div/cld/fld?

Personally, I don't think that is necessary.
However, there is a possible option to distinguish between division-by-zero and narrowly-defined overflow.

@unchecked :unsafe_div begin

Do users really want "unsafe"? I believe there is little speed benefit.

As noted above, we should be able to use @saturating for error-free divs. In other words, there seems to be more demand for a flag for checked divs. Of course, it would be better (for the users) to have both of the two opposing flags.

@BioTurboNick
Copy link
Collaborator Author

julia> Base.infer_effects(Base.sdiv_int, (Int8, Int8))
(+c,+e,+n,+t,+s,+m,+u)

julia> Base.infer_effects(Base.checked_sdiv_int, (Int8, Int8))
(+c,+e,!n,+t,+s,+m,+u)

I don't know if there are immediate performance differences between the two in normal usage, but the unchecked div is marked no-throw. I assume there are some benefits to being able to compile sections of code guaranteed to never lead to an exception (assuming one is careful about the UB cases).

@kimikage
Copy link

(assuming one is careful about the UB cases).

So, I think we need a guard for UB in the "unchecked" context as well.

@BioTurboNick
Copy link
Collaborator Author

(assuming one is careful about the UB cases).

So, I think we need a guard for UB in the "unchecked" context as well.

Can you say more? Adding a branch would make it slower. I was thinking that if someone really wanted this, they'd be taking the responsibility of that check.

@BioTurboNick
Copy link
Collaborator Author

Tracking what may happen here wrt promotion: JuliaLang/julia#54485

@kimikage
Copy link

I am not opposed to adding an unsafe-specific option like :unsafe_div itself.
However, I believe that the users of this package and downstream packages rarely need it.
At least I expect default "unchecked" to be just "nothrow", not "unsafe".

Also, speculative execution is a common technique for speeding up. While it is generally good practice to perform checks first, there might be cases where checks are performed later.

@BioTurboNick
Copy link
Collaborator Author

BioTurboNick commented May 18, 2024

Fair enough, I'm testing some things out and it doesn't seem like there's much, if any, penalty to doing so...

function unchecked_div(x::T, y::T) where T <: SignedBitInteger
    return (y == zero(T)) ?
        zero(T) :
        (y == -one(T)) ?
            -x :
            Base.sdiv_int(x, y)
end

julia> @btime ccccc .= unchecked_div.(aaaaa, bbbbb);
  141.100 μs (0 allocations: 0 bytes)

julia> @btime ccccc .= Base.sdiv_int.(aaaaa, bbbbb);
  141.100 μs (0 allocations: 0 bytes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants