Rasterio: Performance degradation of CRS equality checking

Created on 10 May 2019  Â·  8Comments  Â·  Source: mapbox/rasterio

That improvement https://github.com/mapbox/rasterio/pull/1444 has been lost while switching to WKT representation.

After:

In [1]: import rasterio  

In [2]: rasterio.__version__  
Out[2]: '1.0.14'

In [3]: from rasterio.crs import CRS 

In [4]: c1 = CRS({'init': 'epsg:3857'})

In [5]: c2 = CRS({'init': 'epsg:3857'})

In [6]: %timeit c1 == c2 
26.7 µs ± 370 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Before:

In [1]: import rasterio

In [2]: rasterio.__version__ 
Out[2]: '1.0.13'

In [3]: from rasterio.crs import CRS  

In [4]: c1 = CRS({'init': 'epsg:3857'})  

In [5]: c2 = CRS({'init': 'epsg:3857'})  

In [6]: %timeit c1 == c2  
204 ns ± 2.59 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
bug

Most helpful comment

In 604dca0 I have optimized the EPSG use case and observe sub-µsec equality comparison.

$ python -m timeit -s "from rasterio.crs import CRS" -s "epsg4326 = CRS.from_epsg(4326)" "epsg4326 == epsg4326"
1000000 loops, best of 3: 0.345 usec per loop

All 8 comments

@drnextgis that particular use of the constructor is going to be deprecated in the future, so I haven't been considering performance. Can you try timing CRS.from_epsg(3857) ? If that is very slow, we should do something (such as memoize the EPSG code for rapid comparison).

I took the liberty to test the four combinations of rasterio 1.0.13 and 1.0.14 and pyproj 1.9.6 and 2.x. These are my results:

(_test36) juanlu@infinity ~ $ pip install "rasterio==1.0.13" "pyproj<2" > /dev/null 2>1
(_test36) juanlu@infinity ~ $ GDAL_DATA=$(rio env --gdal-data) ipython --no-banner

In [1]: import rasterio 
   ...: print(rasterio.__version__) 
   ...: import pyproj 
   ...: print(pyproj.__version__) 
   ...: from rasterio.crs import CRS                                                                                                    
   ...:                                               
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: c1 = CRS.from_epsg(3857) 
   ...: c2 = CRS({'init': 'epsg:3857'}) 
   ...: %timeit c1 == c1 
   ...: %timeit c1 == c2 
   ...: %timeit c2 == c2 
   ...:                                                                                                                                 
1.0.13
1.9.6
83.7 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
13.4 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
17.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
14.8 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
262 ns ± 5.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
165 µs ± 12 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
274 ns ± 18.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [2]:                                                                                                                                 
Do you really want to exit ([y]/n)? 
(_test36) juanlu@infinity ~ $ pip install "rasterio==1.0.13" "pyproj>=2" > /dev/null 2>1
(_test36) juanlu@infinity ~ $ GDAL_DATA=$(rio env --gdal-data) ipython --no-banner

In [1]: import rasterio 
   ...: print(rasterio.__version__) 
   ...: import pyproj 
   ...: print(pyproj.__version__) 
   ...: from rasterio.crs import CRS                                                                                                    
   ...:                                               
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: c1 = CRS.from_epsg(3857) 
   ...: c2 = CRS({'init': 'epsg:3857'}) 
   ...: %timeit c1 == c1 
   ...: %timeit c1 == c2 
   ...: %timeit c2 == c2 
   ...:                                                                                                                                 
1.0.13
2.1.3
83.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
13.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
16.4 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
27.2 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
266 ns ± 8.88 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
157 µs ± 2.27 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
268 ns ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [2]:                                                                                                                                 
Do you really want to exit ([y]/n)? 
(_test36) juanlu@infinity ~ $ pip install "rasterio==1.0.14" "pyproj<2" > /dev/null 2>1
(_test36) juanlu@infinity ~ $ GDAL_DATA=$(rio env --gdal-data) ipython --no-banner

In [1]: import rasterio 
   ...: print(rasterio.__version__) 
   ...: import pyproj 
   ...: print(pyproj.__version__) 
   ...: from rasterio.crs import CRS                                                                                                    
   ...:                                               
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: c1 = CRS.from_epsg(3857) 
   ...: c2 = CRS({'init': 'epsg:3857'}) 
   ...: %timeit c1 == c1 
   ...: %timeit c1 == c2 
   ...: %timeit c2 == c2 
   ...:                                                                                                                                 
1.0.14
1.9.6
1.32 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
102 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
78.1 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
74.5 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
96.5 µs ± 3.62 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
98.4 µs ± 5.87 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
95.6 µs ± 2.37 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [2]:                                                                                                                                 
Do you really want to exit ([y]/n)? 
(_test36) juanlu@infinity ~ $ pip install "rasterio==1.0.14" "pyproj>=2" > /dev/null 2>1
(_test36) juanlu@infinity ~ $ GDAL_DATA=$(rio env --gdal-data) ipython --no-banner

In [1]: import rasterio 
   ...: print(rasterio.__version__) 
   ...: import pyproj 
   ...: print(pyproj.__version__) 
   ...: from rasterio.crs import CRS                                                                                                    
   ...:                                               
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'}) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: %timeit -n1 -r1 CRS.from_epsg(3857) 
   ...: c1 = CRS.from_epsg(3857) 
   ...: c2 = CRS({'init': 'epsg:3857'}) 
   ...: %timeit c1 == c1 
   ...: %timeit c1 == c2 
   ...: %timeit c2 == c2 
   ...:                                                                                                                                 
1.0.14
2.1.3
1.33 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
97.1 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
118 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
141 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
98.8 µs ± 4.5 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
99.9 µs ± 3.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
98.2 µs ± 4.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

And this is the program I copy pasted:

import rasterio
print(rasterio.__version__)
import pyproj
print(pyproj.__version__)
from rasterio.crs import CRS                                                                                                                                                 
%timeit -n1 -r1 CRS({'init': 'epsg:3857'})
%timeit -n1 -r1 CRS({'init': 'epsg:3857'})
%timeit -n1 -r1 CRS.from_epsg(3857)
%timeit -n1 -r1 CRS.from_epsg(3857)
c1 = CRS.from_epsg(3857)
c2 = CRS({'init': 'epsg:3857'})
%timeit c1 == c1
%timeit c1 == c2
%timeit c2 == c2

I notice that:

  • rasterio 1.0.14 introduced a regression in both constructors
  • pyproj 2 introduced a regression in CRS.from_epsg(3857)
  • The pyproj version has no effect in __eq__
  • rasterio 1.0.14 introduced regressions in c1 == c1 initialized for both constructors
  • rasterio 1.0.14 introduced an _improvement_ in c1 == c2 when the initialization was different

@Juanlu001 can you explain why you are reporting about pyproj in this issue? Rasterio doesn't use pyproj. Do you suspect some kind of conflict?

Is performance still a problem with Rasterio 1.1?

You're right about pyproj, I repeated my benchmarks:

rasterio 1.0.13

In [2]: import rasterio  
   ...:    ...: print(rasterio.__version__) 
   ...:    ...: from rasterio.crs import CRS                                                                                            
   ...:          
   ...:    ...:                                                
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: c1 = CRS.from_epsg(3857)  
   ...:    ...: c2 = CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit c1 == c1  
   ...:    ...: %timeit c1 == c2  
   ...:    ...: %timeit c2 == c2                                                                                                        
1.0.13
22.3 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
11.3 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
14.7 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
14 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
594 ns ± 5.53 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
158 µs ± 7.11 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
590 ns ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

rasterio 1.0.28

In [1]: import rasterio  
   ...:    ...: print(rasterio.__version__) 
   ...:    ...: from rasterio.crs import CRS                                                                                            
   ...:          
   ...:    ...:                                                
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: c1 = CRS.from_epsg(3857)  
   ...:    ...: c2 = CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit c1 == c1  
   ...:    ...: %timeit c1 == c2  
   ...:    ...: %timeit c2 == c2                                                                                                        
1.0.28
2.43 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
107 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
88.9 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
90.4 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
82.8 µs ± 757 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
86.2 µs ± 6.62 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
83.6 µs ± 1.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

rasterio 1.1.2

In [1]: import rasterio  
   ...:    ...: print(rasterio.__version__) 
   ...:    ...: from rasterio.crs import CRS                                                                                            
   ...:          
   ...:    ...:                                                
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: %timeit -n1 -r1 CRS.from_epsg(3857)  
   ...:    ...: c1 = CRS.from_epsg(3857)  
   ...:    ...: c2 = CRS({'init': 'epsg:3857'})  
   ...:    ...: %timeit c1 == c1  
   ...:    ...: %timeit c1 == c2  
   ...:    ...: %timeit c2 == c2                                                                                                        
1.1.2
2.35 ms ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
108 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
89 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
87.9 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
83.3 µs ± 1.22 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
84.7 µs ± 2.56 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
83.1 µs ± 1.53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

There is a very noticeable slowdown between 1.0.13 and 1.0.28 (which can be traced to 1.0.14 if I recall correctly) and no significant difference between 1.0.28 and 1.1.2.

Also, @sgillies could you please clarify which constructor will be deprecated?

that particular use of the constructor is going to be deprecated in the future, so I haven't been considering performance.

For future reference, according to some comments I've read in the pyproj documentation, I understand that the deprecated constructor is {"init": "epsg:3857"}.

In 604dca0 I have optimized the EPSG use case and observe sub-µsec equality comparison.

$ python -m timeit -s "from rasterio.crs import CRS" -s "epsg4326 = CRS.from_epsg(4326)" "epsg4326 == epsg4326"
1000000 loops, best of 3: 0.345 usec per loop
Was this page helpful?
0 / 5 - 0 ratings

Related issues

mangecoeur picture mangecoeur  Â·  3Comments

valpesendorfer picture valpesendorfer  Â·  3Comments

astrojuanlu picture astrojuanlu  Â·  4Comments

snowman2 picture snowman2  Â·  3Comments

vincentsarago picture vincentsarago  Â·  4Comments