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

Faster pycall. Adds pycall! #492

Merged
merged 22 commits into from
Jul 11, 2018
Merged

Faster pycall. Adds pycall! #492

merged 22 commits into from
Jul 11, 2018

Conversation

JobJob
Copy link
Contributor

@JobJob JobJob commented Apr 13, 2018

Speeds up pycalls. For functions with 1-7 arguments it reduces the overhead of calling the function from Julia by about 10-40x

Adds pycall!(ret, o, ...) to avoid allocating a PyObject for the return value each time the function is called.

One query: I'm not 100% about the const pyarg_tuples = PyPtr[], and growing it when functions have more arguments. Maybe we should just initialise it to a certain size (32 arguments?), check for isassigned(pyarg_tuples, nargs+1), and if not assigned, create the tuple, and grow the pyarg_tuples vector if someone requires more than the 32 arg default.

For reasons that I'll need to track down over the weekend, one of the tuples that we use for passing args, is ending up with a refcount of 2, which is not allowed when setting an item, since tuples are supposed to be immutable (but when there's only one ref, setting an item is allowed):
https://github.com/python/cpython/blob/23ab5ee667a9b29014f6f7f01797c611f63ff743/Objects/tupleobject.c#L166
so this is WIP until I solve that.

Happens after the @pydef type Doubler, see the @test_broken in runtests. Any ideas appreciated.

Creating new builtin exceptions increment the refcount of the arguments tuple passed to it. The Doubler class in that test was subclassing AssertionError and instantiating triggered this. Handled this situation (callees incrementing the refcount of args tuple) now.

to-do:

  • fix a bug in the wrap and possibly some others about. (there was another minor bug in the test for having the only reference to the (empty) arguments tuple)
  • maybe remove the wrap, since maybe it's not faster after all... it's not, always. Let's remove it for now.
  • remove pycall_legacy when everyone's happy with the PR (was just left in for benchmarking comparison purposes)

Benchmark results (Mean times for different arities)

pycall_legacy ()                 	 TrialEstimate(946.804 ns) (on master when this PR was opened)
pycall ()                        	 TrialEstimate(725.172 ns) (this PR)
pycall! ()                       	 TrialEstimate(566.083 ns) (this PR, pycall!) 

pycall_legacy (1,)               	 TrialEstimate(8.582 μs)
pycall (1,)                      	 TrialEstimate(1.454 μs)
pycall! (1,)                     	 TrialEstimate(1.262 μs)

pycall_legacy (1, 1)             	 TrialEstimate(12.365 μs)
pycall (1, 1)                    	 TrialEstimate(1.657 μs)
pycall! (1, 1)                   	 TrialEstimate(1.684 μs)

pycall_legacy (1, 1, 1)          	 TrialEstimate(16.136 μs)
pycall (1, 1, 1)                 	 TrialEstimate(1.694 μs)
pycall! (1, 1, 1)                	 TrialEstimate(1.764 μs)

pycall_legacy 7*(1,1,...)        	 TrialEstimate(29.335 μs)
pycall 7*(1,1,...)               	 TrialEstimate(2.881 μs)
pycall! 7*(1,1,...)              	 TrialEstimate(2.794 μs)

@JobJob JobJob mentioned this pull request Apr 13, 2018
src/PyCall.jl Outdated

"""
Low-level version of `pycall(o, ...)` that always returns `PyObject`.
"""
function _pycall(o::Union{PyObject,PyPtr}, args...; kwargs...)
function _pycall_legacy(o::Union{PyObject,PyPtr}, args...; kwargs...)
Copy link
Contributor Author

@JobJob JobJob Apr 13, 2018

Choose a reason for hiding this comment

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

This is just here for the benchmark comparison, the whole function, and the pycall_legacy methods below, need to be removed before merging.

@JobJob JobJob changed the title WIP: Faster pycall. Adds pycall!, pywrapfn Faster pycall. Adds pycall!, pywrapfn Apr 15, 2018
@JobJob JobJob changed the title Faster pycall. Adds pycall!, pywrapfn WIP: Faster pycall. Adds pycall!, pywrapfn Apr 16, 2018
@JobJob JobJob changed the title WIP: Faster pycall. Adds pycall!, pywrapfn Faster pycall. Adds pycall! Apr 18, 2018
@JobJob JobJob force-pushed the pycall-bang branch 2 times, most recently from 6122bcd to b6db3b1 Compare April 30, 2018 07:21
@JobJob
Copy link
Contributor Author

JobJob commented May 25, 2018

Anything else you need doing for this (and other open PRs)? Test failures on Windows here to do with importing NumPy, not sure what to do for those? 0.7 failures seem unrelated.

@JobJob
Copy link
Contributor Author

JobJob commented Jun 28, 2018

@stevengj any news here?

Should I bother trying to rebase #487 (again) yet? Or wait a bit for more 0.7 related changes? Seems this and the others don't have merge conflicts (so I rebased them).

Apologies if the file rearranging made this or the others harder to review. I can revert if needed I suppose.

@stevengj
Copy link
Member

Looks like this is breaking nightly?

@JobJob
Copy link
Contributor Author

JobJob commented Jul 2, 2018

Hmm, I can't replicate the nightly errors locally, both w julia on the commit that travis used 8e7e6fc00a, and master from yesterday, tests pass with juliadev -e 'Pkg.test("PyCall"; coverage=true)'

edit: with python3 on mac
edit 2: Duh. I was doing it wrong with Pkg3 - replicated locally now

Attempt to fix 0.7 issue
@JobJob
Copy link
Contributor Author

JobJob commented Jul 2, 2018

Ok, pushed a fix. There's a seemingly unrelated error in the Conda Mac build on 0.6, but the rest of the travis builds are good.

Most of the Windows builds are complaining about numpy not being installed - anything I can do about those?

@stevengj
Copy link
Member

stevengj commented Jul 2, 2018

Don't rely on numpy features in the tests, or only test the numpy aspects if PyCall.npy_initialized is true.

@JobJob
Copy link
Contributor Author

JobJob commented Jul 3, 2018

I've changed the tests to not use NumPy anymore.

However, for #487's most of the tests need numpy, so I'll just not run those if pyimport("numpy") fails. Btw PyCall.npy_initialized is always false while running the tests on my machine - so I'll just use try pyimport("numpy") catch.

@stevengj
Copy link
Member

stevengj commented Jul 3, 2018

Btw PyCall.npy_initialized is always false while running the tests

It's false in the beginning because numpy is loaded lazily, but it should be true after other array tests execute.

However, you can also do ispynull(pyimport_e("numpy")) to avoid the try/catch

@codecov-io
Copy link

codecov-io commented Jul 3, 2018

Codecov Report

Merging #492 into master will decrease coverage by 0.32%.
The diff coverage is 91.48%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #492      +/-   ##
==========================================
- Coverage    65.5%   65.17%   -0.33%     
==========================================
  Files          18       19       +1     
  Lines        1487     1522      +35     
==========================================
+ Hits          974      992      +18     
- Misses        513      530      +17
Impacted Files Coverage Δ
src/PyCall.jl 67.04% <40%> (-6.78%) ⬇️
src/pyfncall.jl 97.61% <97.61%> (ø)
src/pyeval.jl 69.79% <0%> (+0.31%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8e89bc4...8ed2888. Read the comment docs.

src/pyfncall.jl Outdated
(`pyargsptr`). Sets `ret.o` to the result of the call, and returns `ret::PyObject`.
"""
function _pycall!(ret::PyObject, o::Union{PyObject,PyPtr}, args,
nargs::Int=length(args), kw::Union{Ptr{Nothing}, PyObject}=C_NULL)
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be a PyPtr rather than Ptr{Nothing}?

Copy link
Contributor Author

@JobJob JobJob Jul 3, 2018

Choose a reason for hiding this comment

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

No that's for the C_NULL when there aren't any kwargs. But good - that nudged me to add some tests for the kwargs case.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ptr{Nothing} is an implementation detail; it's best to use Ptr{Cvoid} (Base in 0.7, available from Compat in 0.6)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ta

@JobJob
Copy link
Contributor Author

JobJob commented Jul 4, 2018

"Everyone" happy with this? Should I remove the pycall_legacy stuff?

@JobJob
Copy link
Contributor Author

JobJob commented Jul 4, 2018

Oh and btw re

It's false in the beginning because numpy is loaded lazily, but it should be true after other array tests execute.

I thought I was checking late in the tests, but perhaps not, it's fine now.

@stevengj
Copy link
Member

Yes, go ahead and remove pycall_legacy

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

Successfully merging this pull request may close these issues.

4 participants