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

ENH: Add a fast-path for inplace transforms #1202

Closed
wants to merge 1 commit into from

Conversation

greglucas
Copy link
Contributor

Rather than opening an issue, I figured it would be easier to push up a PR with code for discussion purposes. This is looking at micro-optimizations when repeatedly calling Transformer.transform() on a single point at a time (yes, it is way better to make a large array first, but unfortunately that isn't always feasible). When transforming small arrays, there is a very large overhead (~40%) associated with calling into _copytobuffer/_convertback on every input/output array.

I'm wondering if anyone has any thoughts on making a fast-path for inplace arrays to bypass the utils functions? Right now the code is very friendly and if a user requests inplace, but the array doesn't work as input array it will gracefully transform it to an array that works, but this adds quite a few checks along the way that are inconsequential for large arrays, but make a difference for small arrays.

Essentially what I want is access to the private Cython inplace _transform() method, so perhaps another way to handle this would be to expose that in a public way somehow?

Benchmarking code:

import array

import numpy as np
from pyproj import Transformer

transformer = Transformer.from_crs(2263, 4326)
x_coords = np.random.randint(80000, 120000)
y_coords = np.random.randint(200000, 250000)

x_np = x_coords * np.ones(1, dtype=np.float64)
y_np = y_coords * np.ones(1, dtype=np.float64)

x_list = x_np.tolist()
y_list = y_np.tolist()

x_arr = array.array('d', x_list)
y_arr = array.array('d', y_list)

# Repeat the calculation a bunch of times
for i in range(200000):
    # transformer.transform(x_np, x_np, inplace=False)
    # transformer.transform(x_np, x_np, inplace=True)
    # transformer.transform(x_list, y_list)
    # transformer.transform(x_arr, y_arr, inplace=False)
    transformer.transform(x_arr, y_arr, inplace=True)

Then running:

python -m cProfile -o prof-arr test_script.py
snakeviz prof-arr
  • Closes #xxxx
  • Tests added
  • Fully documented, including history.rst for all changes and api/*.rst for new API

@codecov
Copy link

codecov bot commented Dec 16, 2022

Codecov Report

Merging #1202 (59e8ad6) into main (e345729) will increase coverage by 0.02%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##             main    #1202      +/-   ##
==========================================
+ Coverage   96.25%   96.28%   +0.02%     
==========================================
  Files          20       20              
  Lines        1791     1805      +14     
==========================================
+ Hits         1724     1738      +14     
  Misses         67       67              
Impacted Files Coverage Δ
pyproj/geod.py 97.48% <100.00%> (+0.09%) ⬆️
pyproj/transformer.py 95.13% <100.00%> (+0.17%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@@ -789,6 +789,22 @@ def transform( # pylint: disable=invalid-name
'33 98'

"""
if inplace and getattr(xx, "typecode", "") == "d":
Copy link
Member

Choose a reason for hiding this comment

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

Thoughts about moving this to _copytobuffer instead?

Copy link
Member

Choose a reason for hiding this comment

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

You could move it to the top of the function so it would skip everything else if true.

Copy link
Member

Choose a reason for hiding this comment

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

You could also make DataType.ARRAY the first type checked for in _convertback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I tried that too and it unfortunately still adds quite a bit of overhead with both of those. 1.5 seconds vs 1 second on my laptop here.

Although, both of those would be "easy" immediate gains.

@snowman2
Copy link
Member

Another alternative to consider is to add a method called transform_point that is optimized for transforming scalars.

@greglucas
Copy link
Contributor Author

Another alternative to consider is to add a method called transform_point that is optimized for transforming scalars.

I like this idea! It could be a bit confusing that you'd need a buffer array to pass to it for the speed though? (i.e. _copytobuffer_returnscalar wouldn't be good enough I don't think) I also want the fast-path for Geod fwd/inv too...

@snowman2
Copy link
Member

snowman2 commented Dec 16, 2022

I like this idea! It could be a bit confusing that you'd need a buffer array to pass to it for the speed though? (i.e. _copytobuffer_returnscalar wouldn't be good enough I don't think)

I was thinking of adding _transform_point to the cython transformer as well. Under the hood it could use proj_trans instead of proj_trans_generic which _transform is using for arrays. This should add significant speedups as you wouldn't require any transformations between types or type checking. You could only pass in floats for x,y,x,t and get floats back.

I also want the fast-path for Geod fwd/inv too...

That is a good goal. I suggest addressing that issue separately.

@greglucas
Copy link
Contributor Author

OK, I did some benchmarking on the single-point versions, and casting immediately to a single-value float array and using those in the generics is still a massive improvement. I'm sure more can be done by calling into a different Cython function too...

see here: #1203

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.

None yet

2 participants