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

group ccall'ed MPZ library functions in a module #21654

Merged
merged 1 commit into from
May 19, 2017
Merged

Conversation

rfourquet
Copy link
Member

The goals where 1) making the code cleaner in the implementation of julia function (so that it's not full of difficult-to-read ccalls), and 2) avoid duplication for for some of the ccall, within base itself, but also for users/packages.

With the help of some functions which create automatically new BigInt objects for output arguments, the simplification in 1) has gone beyond my expectation :) The same could probably be done in mpfr.jl.

base/gmp.jl Outdated
@@ -69,6 +69,93 @@ function __init__()
end
end


module mpz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modules names should be in all caps (if an acronym) or upper camel case

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I'm sensible to that, but I made this choice to be as close as possible to the original function names, of the form "mpz_add", which translates here into "mpz.add". An alternative could be to not have any submodule and to name the julia functions as in GMP, with the "mpz_" prefix. I prefer the module with lower case, and I thought that an exception to the style can be made as it's an internal sub-sub-module of Base. But will change if you or someone insists.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would rather stick with the established naming conventions. I don't think it's especially necessary to have julia wrappers for C functions use exactly the same names as in the C library, when the conventions or meanings for names differ in idiomatic Julia

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, most importantly because the convention can easily lead one to believe when perusing the code that mpz is an object rather than a module, which is quite confusing. So I think that writing MPZ.add would be less likely to be confusing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I got convinced :) thanks for your input.

@ararslan ararslan added the maths Mathematical functions label Apr 30, 2017
@ararslan
Copy link
Member

This is cool!

@nanosoldier runbenchmarks(ALL, vs=":master")

base/gmp.jl Outdated
end

invert(x::BigInt, a::BigInt, b::BigInt) =
ccall((:__gmpz_invert, :libgmp), Cint, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &x, &a, &b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is getting updated might as well update the & syntax , since it is deprecated and should be using the Ref{T} argument type instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it still has a performance cost though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will as well then wait till the performances get on par...

@rfourquet
Copy link
Member Author

@nanosoldier runbenchmarks(("BigInt"||"Complex{BigInt}"), vs=":master")

@rfourquet
Copy link
Member Author

rfourquet commented May 1, 2017

@ararslan thanks, your nanosoldier run didn't work probably because I had a forgotten import in random.jl. Running again with filters now, hope this covers all what is necessary.

@jrevels
Copy link
Member

jrevels commented May 1, 2017

@rfourquet You need commit access to trigger nanosoldier (maybe you already have access, but that's my guess as to why nothing happened here, since everything looks fine server side).

@nanosoldier runbenchmarks("BigInt" || "Complex", vs=":master")

@jrevels
Copy link
Member

jrevels commented May 1, 2017

Weird, nothing's happening here even though the logs server-side seem fine. I kicked Nanosoldier, let's try again:

@nanosoldier runbenchmarks("BigInt" || "Complex", vs=":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed, but no benchmarks were actually executed. Perhaps your tag predicate contains mispelled tags? cc @jrevels

@jrevels
Copy link
Member

jrevels commented May 1, 2017

Benchmarks do exist with those keys, but keys weren't counted as tags in BenchmarkTools until v0.0.8, and Nanosoldier only had v0.0.7. Just did a Pkg.update, let's see how it goes:

@nanosoldier runbenchmarks("BigInt" || "Complex", vs=":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels

@rfourquet
Copy link
Member Author

Thanks @jrevels ! Rerunning with "Complex{BigInt}", as it seems "Complex" is not enough to handle it.

@rfourquet
Copy link
Member Author

@nanosoldier runbenchmarks("Complex{BigInt}", vs=":master")

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - no performance regressions were detected. A full report can be found here. cc @jrevels

base/gmp.jl Outdated

for op in (:add, :sub, :mul, :fdiv_q, :tdiv_q, :fdiv_r, :tdiv_r, :gcd, :lcm, :and, :ior, :xor)
@eval begin
$op(x::BigInt, a::BigInt, b::BigInt) = (ccall($(gmpz(op)), Void, (Ptr{BigInt}, Ptr{BigInt}, Ptr{BigInt}), &x, &a, &b); x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should get a ! since it is mutating the input.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review. I hesitated about that, but prefered to follow as closely as possible the original names (from GMP). Am fine to change though; according to their previous comments on the case of the module name, I guess @tkelman and @StefanKarpinski would agree with you?

Copy link
Contributor

@thofma thofma May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the "problem" that since we are extending Base.gcd, there will also be a method gcd(::BigInt, ::BigInt, ::BigInt) exposed to the user, which does funny things from a user perspective. In particular it is breaking the following behaviour:

julia> gcd(BigInt(3), BigInt(2), BigInt(4))
1

With the proposed change the function mutates the first argument and returns 2.

I don't think this is what we want and gcd is not the only function with this problem.

Edit: Sorry, my mistake. This does not break anything.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right, though it is mitigated by the fact that Base.gcd is not touched here, we introduce only a hard-to-find Base.GMP.MPZ.gcd.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you are right. Overlooked the MPZ module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@StefanKarpinski and @tkelman, could you comment on this? I kept the names as in libgmp without any ! even for mutating functions (and for few functions I added a variant with a ! when updating the input, like add!(x, y) = add(x, x, y), equivalent to a x += y), which seems more predictable for me, but @thofma suggests to add ! whenever an argument is modified.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Julia wrapper functions should try to aim for following idiomatic Julia naming/argument conventions whenever possible, rather than matching C API naming exactly (if you want to be calling the C API directly, you can always use ccall yourself). We have a bit of an unfortunate mix of these styles at the moment, I have a few of the library binding files on my personal to-do list to clean up in this way and be more consistent about. Disparate C libraries aren't going to have any common conventions, but would be nice if our bindings attempted to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again for your extended answer, I will update accordingly.

base/gmp.jl Outdated

for op in (:add_ui, :sub_ui, :mul_ui, :mul_2exp, :fdiv_q_2exp, :pow_ui, :bin_ui)
@eval begin
$op(x::BigInt, a::BigInt, b) = (ccall($(gmpz(op)), Void, (Ptr{BigInt}, Ptr{BigInt}, Culong), &x, &a, b); x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

base/gmp.jl Outdated
end
end

ui_sub(x::BigInt, a, b::BigInt) = (ccall((:__gmpz_ui_sub, :libgmp), Void, (Ptr{BigInt}, Culong, Ptr{BigInt}), &x, a, &b); x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

base/gmp.jl Outdated

for op in (:neg, :com, :sqrt, :set)
@eval begin
$op(x::BigInt, a::BigInt) = (ccall($(gmpz(op)), Void, (Ptr{BigInt}, Ptr{BigInt}), &x, &a); x)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

base/gmp.jl Outdated

popcount(a::BigInt) = Int(ccall((:__gmpz_popcount, :libgmp), Culong, (Ptr{BigInt},), &a))

function tdiv_qr(x::BigInt, y::BigInt, a::BigInt, b::BigInt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!

@thofma
Copy link
Contributor

thofma commented May 2, 2017

There are more functions missing a !.

@rfourquet rfourquet force-pushed the rf/big-ccall branch 5 times, most recently from 44c6719 to 662439a Compare May 12, 2017 10:12
@rfourquet
Copy link
Member Author

I updated by renaming functions with a ! when mutating as requested. I'm not sure for the function MPZ.get_str which writes to a passed char* without modifying the pointer itself. Still, as a String can be passed in instead (via a unsafe_convert) and be modified by this function, I added also a !.

@tkelman
Copy link
Contributor

tkelman commented May 13, 2017

This looks good to me (I made one trivial edit to a comment) - these look like they would be handy for writing in-place BigInt code. Let's run @nanosoldier runbenchmarks(ALL, vs=":master") again, why not

@nanosoldier
Copy link
Collaborator

Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels

@rfourquet
Copy link
Member Author

Thanks. Indeed working on in-place BigInt code in Primes.jl pulled me to make this PR.
The regressions reported by nanosoldier seem unrelated to this patch.
I forgot to discuss: we use (only) one GMP function prefixed with "mpn_" instead of "mpz_" (i.e. "mpn_popcount"), so I put it in this MPZ module for simplicity, but it could be cleaner to create a separate MPN module. It may depend on whether or not this module has vocation to become exhaustive (implementing ccall's which are not used by base itself, to serve package authors). What do you think?

@tkelman
Copy link
Contributor

tkelman commented May 14, 2017

What's the difference between mpz and mpn in libgmp? Hard to keep track of their naming convention if you don't use the low-level library calls that often. There's no problem with packages writing extended library bindings to libraries that are for now shipped by default with Julia (see for example BandedMatrices.jl which has some lapack bindings that aren't all that useful within base), but the eventual plan is probably to separate out the library-specific bindings from the bare-minimum BigInt type definition, and make the former optional or swappable (if we ever care more about MSVC we may want to try out MPIR instead of libgmp for example).

@rfourquet
Copy link
Member Author

rfourquet commented May 14, 2017

The MPN functions are lower level than MPZ: MPZ tries to provide a convenient application-level user interface, while MPN provides low-level routines taking raw arrays as input instead of mpz_t objects (i.e. BigInt), which can be used by library implementors to roll-up their own bigint objects. For example, I started some time ago to experiment with a different layout for BigInt, which would require using only MPN functions, but migrating away from MPZ to MPN-only is already a big work.
According to your answer, it seems fine for now to keep this mpn_popcount function in the MPZ namespace for simplicity.

@rfourquet
Copy link
Member Author

I added a forgotten binding used in the BigInt constructor. If this receives no more comments, I think this should be safe to merge within few days.

@rfourquet rfourquet merged commit 9a477f2 into master May 19, 2017
@rfourquet rfourquet deleted the rf/big-ccall branch May 19, 2017 11:00
@rfourquet rfourquet added the bignums BigInt and BigFloat label May 23, 2017
@Jutho
Copy link
Contributor

Jutho commented Oct 13, 2017

Thanks for this nice PR. I use the inplace version of BigInt (i.e. the new MPZ module) in https://github.com/Jutho/WignerSymbols.jl . How difficult would it be to backport this to Julia v0.6, by which I not mean officially, but just within my package.

Would it be sufficient to copy all the code between module MPZ ... end into my package, sandwiched between a version check? Is that allowed (in terms of license restrictions)?

@rfourquet
Copy link
Member Author

Thanks! I had not realized this change was not in 0.6. It should be pretty trivial to backport to 0.6, indeed by copying all the code in the MPZ module (maybe changing a few things, like const mpz_t = Ref{BigInt} back to const mpz_t = Ptr{BigInt}). Let me know if you have any trouble. Licence-wise, I have unfortunately no idea, but if "MIT-expat" (your package) is compatible with Julia's "MIT licence" it should be fine?

@Jutho
Copy link
Contributor

Jutho commented Oct 14, 2017

Thanks for your help. Everything seems to work indeed by just copying the MPZ module, even without any change. I would indeed think that licenses are compatible, so I'll take my chance and register the package including a copy of your MPZ module, if that's fine by you. When Julia 0.7 or 1.0 is released, I'll probably drop 0.6 support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bignums BigInt and BigFloat maths Mathematical functions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants