Skip to content

Remove _getattr_ method in RangeIndex class - #10538

Merged
rapids-bot[bot] merged 65 commits into
NVIDIA:branch-22.08from
skirui-source:RangeIndex_
Jul 15, 2022
Merged

Remove _getattr_ method in RangeIndex class#10538
rapids-bot[bot] merged 65 commits into
NVIDIA:branch-22.08from
skirui-source:RangeIndex_

Conversation

@skirui-source

@skirui-source skirui-source commented Mar 29, 2022

Copy link
Copy Markdown
Contributor

This PR helps reduce implicit conversions by minimizing unnecessary Int64Index column materialization by the RangeIndex class( PR #9593). Replaces #10388.

The following methods have been explicitly implemented for RangeIndex in this PR : _column, _columns, where, isna, argsort, max, min, nunique, values_host, to_numpy, to_arrow, __array__

As demonstrated by the results posted in this comment, on average:

  • There's an evident performance gain with the new implementations of values_host, to_numpy, nunique, min and max

  • isna and argsort demonstrate inconsistent measurements of performance perhaps due to memory allocation discrepancies

  • Whereas to_arrow and where still materialize an Int64Index

@skirui-source skirui-source added Python Affects Python cuDF API. Performance Performance related issue breaking Breaking change labels Mar 29, 2022
@codecov

codecov Bot commented Mar 30, 2022

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (branch-22.08@833cadd). Click here to learn what that means.
The diff coverage is n/a.

@@               Coverage Diff               @@
##             branch-22.08   #10538   +/-   ##
===============================================
  Coverage                ?   86.33%           
===============================================
  Files                   ?      144           
  Lines                   ?    22796           
  Branches                ?        0           
===============================================
  Hits                    ?    19681           
  Misses                  ?     3115           
  Partials                ?        0           

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 833cadd...622145b. Read the comment docs.

@skirui-source skirui-source added non-breaking Non-breaking change and removed breaking Breaking change labels Mar 31, 2022
@skirui-source
skirui-source marked this pull request as ready for review March 31, 2022 05:14
@skirui-source
skirui-source requested a review from a team as a code owner March 31, 2022 05:14
@skirui-source skirui-source added breaking Breaking change and removed non-breaking Non-breaking change labels Mar 31, 2022
@skirui-source skirui-source added non-breaking Non-breaking change improvement Improvement / enhancement to an existing function and removed breaking Breaking change labels Apr 5, 2022
@skirui-source
skirui-source marked this pull request as draft April 8, 2022 21:20

@vyasr vyasr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's something odd with isna and argsort. On my current branch, I don't get very consistent measurements of the performance of cupy.zeros or cupy.arange. I suspect that it has to do with either underlying memory allocation characteristics or some initialization logic rather than the performance of the API itself, but I'm not 100% sure. I don't quite trust those numbers, though.

The to_arrow measurement, definitely looks like it's problematic. I think that's basically indicating that populating a PyArrow array sequentially on the host is just a lot slower than populating it in parallel on device. My guess is that this is an API where we are probably willing to eat the GPU memory hit of a conversion to an Int64Index rather than the performance hit. Unless @shwina or @galipremsagar disagree (maybe there are Dask impacts that I am unaware of) I would recommend reverting that implementation to just the naive self._as_int64().to_arrow().

Comment thread python/cudf/cudf/core/index.py Outdated
Comment thread python/cudf/cudf/core/index.py Outdated
@vyasr

vyasr commented Jul 7, 2022

Copy link
Copy Markdown
Contributor

It looks like there's still a pandas-incompatible benchmark in there. To find these, take your benchmarks and run them like this:

CUDF_BENCHMARKS_USE_PANDAS=ON CUDF_BENCHMARKS_DEBUG_ONLY=ON pytest...

That will tell you if any of your benchmarks will fail when run with pandas. See #11122 for more information (hopefully that gets merged soon, then it'll be available on our main docs page).

@skirui-source

Copy link
Copy Markdown
Contributor Author

It looks like there's still a pandas-incompatible benchmark in there. To find these, take your benchmarks and run them like this:

CUDF_BENCHMARKS_USE_PANDAS=ON CUDF_BENCHMARKS_DEBUG_ONLY=ON pytest...

That will tell you if any of your benchmarks will fail when run with pandas. See #11122 for more information (hopefully that gets merged soon, then it'll be available on our main docs page).

In response to this, to_arrow() seems to be the only pandas- incompatible benchmarks, how should i resolve?

FAILED bench_rangeindex.py::bench_to_arrow[20] - AttributeError: 'RangeIndex' object has no attribute 'to_arrow'
FAILED bench_rangeindex.py::bench_to_arrow[10] - AttributeError: 'RangeIndex' object has no attribute 'to_arrow'

@vyasr

vyasr commented Jul 13, 2022

Copy link
Copy Markdown
Contributor

Any method that doesn't have a pandas equivalent can't be run with pandas. I added a mark to solve this case. You should decorate those benchmarks with that mark pandas_incompatible. See this example.

@skirui-source
skirui-source requested a review from vyasr July 13, 2022 18:50
@skirui-source

Copy link
Copy Markdown
Contributor Author

@vyasr I have resolved the to_arrow() benchmark, any feedback on the other implem. before i proceed with benchmarking?

@skirui-source

Copy link
Copy Markdown
Contributor Author

@vyasr The updated before/after comparison benchmark results:

Benchmarks -- values_host()


--------------------------------- benchmark '100': 2 tests ---------------------------------
Name (time in us)                      Min                 Max                Mean          
--------------------------------------------------------------------------------------------
bench_values_host[100] (0001)     130.7169 (8.15)     210.0670 (6.23)     151.7630 (9.21)   
bench_values_host[100] (0010)      16.0381 (1.0)       33.7029 (1.0)       16.4822 (1.0)    
--------------------------------------------------------------------------------------------


---------------------------------- benchmark '10000': 2 tests ----------------------------------
Name (time in us)                        Min                   Max                Mean          
------------------------------------------------------------------------------------------------
bench_values_host[10000] (0001)     145.7820 (7.51)     2,037.2421 (54.40)    153.6504 (7.48)   
bench_values_host[10000] (0010)      19.4218 (1.0)         37.4499 (1.0)       20.5327 (1.0)    
------------------------------------------------------------------------------------------------


------------------------------ benchmark '1000000': 2 tests ------------------------------
Name (time in ms)                        Min               Max              Mean          
------------------------------------------------------------------------------------------
bench_values_host[1000000] (0001)     1.8607 (5.07)     1.9168 (3.33)     1.8733 (4.84)   
bench_values_host[1000000] (0010)     0.3673 (1.0)      0.5760 (1.0)      0.3869 (1.0)    
------------------------------------------------------------------------------------------

Benchmarks -- to_numpy()


--------------------------------- benchmark '100': 2 tests --------------------------------
Name (time in us)                   Min                   Max                Mean          
-------------------------------------------------------------------------------------------
bench_to_numpy[100] (0002)     132.7849 (8.26)     1,384.7651 (41.82)    138.6965 (8.34)   
bench_to_numpy[100] (0011)      16.0839 (1.0)         33.1162 (1.0)       16.6237 (1.0)    
-------------------------------------------------------------------------------------------


-------------------------------- benchmark '10000': 2 tests -------------------------------
Name (time in us)                     Min                 Max                Mean          
-------------------------------------------------------------------------------------------
bench_to_numpy[10000] (0002)     166.8851 (8.61)     223.9509 (4.20)     172.1689 (8.68)   
bench_to_numpy[10000] (0011)      19.3850 (1.0)       53.2640 (1.0)       19.8280 (1.0)    
-------------------------------------------------------------------------------------------


----------------------------- benchmark '1000000': 2 tests ----------------------------
Name (time in ms)                     Min               Max              Mean          
---------------------------------------------------------------------------------------
bench_to_numpy[1000000] (0002)     4.3659 (11.89)    4.5955 (8.19)     4.4503 (11.88)  
bench_to_numpy[1000000] (0011)     0.3671 (1.0)      0.5614 (1.0)      0.3745 (1.0)    
---------------------------------------------------------------------------------------

Benchmarks -- argsort()


-------------------------------- benchmark '100': 2 tests --------------------------------
Name (time in us)                  Min                   Max                Mean          
------------------------------------------------------------------------------------------
bench_argsort[100] (0004)     301.8749 (1.0)      1,885.9080 (3.53)     313.4095 (1.0)    
bench_argsort[100] (0013)     318.6350 (1.06)       533.6509 (1.0)      371.2334 (1.18)   
------------------------------------------------------------------------------------------


-------------------------------- benchmark '10000': 2 tests --------------------------------
Name (time in us)                    Min                   Max                Mean          
--------------------------------------------------------------------------------------------
bench_argsort[10000] (0004)     359.0430 (1.18)     4,981.6479 (2.75)     383.8440 (1.23)   
bench_argsort[10000] (0013)     303.5921 (1.0)      1,808.2329 (1.0)      313.1777 (1.0)    
--------------------------------------------------------------------------------------------


---------------------------- benchmark '1000000': 2 tests ----------------------------
Name (time in ms)                    Min               Max              Mean          
--------------------------------------------------------------------------------------
bench_argsort[1000000] (0004)     2.2911 (6.56)     2.9083 (1.0)      2.4202 (5.84)   
bench_argsort[1000000] (0013)     0.3492 (1.0)      4.2373 (1.46)     0.4146 (1.0)    
--------------------------------------------------------------------------------------

Benchmarks -- nunique()


---------------------------- benchmark '100': 2 tests ----------------------------
Name (time in us)                Min                Max              Mean          
-----------------------------------------------------------------------------------
bench_nunique[100] (0005)     9.0001 (4.72)     14.7661 (2.65)     9.1371 (4.67)   
bench_nunique[100] (0014)     1.9080 (1.0)       5.5791 (1.0)      1.9572 (1.0)    
-----------------------------------------------------------------------------------


----------------------------- benchmark '10000': 2 tests -----------------------------
Name (time in us)                  Min                 Max              Mean          
--------------------------------------------------------------------------------------
bench_nunique[10000] (0005)     9.0371 (4.64)      26.3380 (1.0)      9.3150 (4.57)   
bench_nunique[10000] (0014)     1.9481 (1.0)      887.4950 (33.70)    2.0378 (1.0)    
--------------------------------------------------------------------------------------


----------------------------- benchmark '1000000': 2 tests ----------------------------
Name (time in us)                    Min                Max              Mean          
---------------------------------------------------------------------------------------
bench_nunique[1000000] (0005)     9.1090 (4.64)     26.3939 (1.22)     9.7936 (4.81)   
bench_nunique[1000000] (0014)     1.9611 (1.0)      21.5990 (1.0)      2.0376 (1.0)    
---------------------------------------------------------------------------------------

Benchmarks -- isna()


------------------------------ benchmark '100': 2 tests -----------------------------
Name (time in us)               Min                 Max                Mean          
-------------------------------------------------------------------------------------
bench_isna[100] (0006)      53.5939 (1.0)      140.1899 (1.0)       55.6012 (1.0)    
bench_isna[100] (0015)     267.8588 (5.00)     513.0582 (3.66)     330.8294 (5.95)   
-------------------------------------------------------------------------------------


------------------------------- benchmark '10000': 2 tests ------------------------------
Name (time in us)                 Min                   Max                Mean          
-----------------------------------------------------------------------------------------
bench_isna[10000] (0006)      53.8230 (1.0)         89.7220 (1.0)       54.8441 (1.0)    
bench_isna[10000] (0015)     266.2621 (4.95)     1,266.3819 (14.11)    273.8039 (4.99)   
-----------------------------------------------------------------------------------------


------------------------------- benchmark '1000000': 2 tests ------------------------------
Name (time in us)                   Min                   Max                Mean          
-------------------------------------------------------------------------------------------
bench_isna[1000000] (0006)     322.7061 (1.20)     4,972.1331 (1.73)     331.0192 (1.19)   
bench_isna[1000000] (0015)     268.7820 (1.0)      2,880.7928 (1.0)      277.0955 (1.0)    
-------------------------------------------------------------------------------------------

Benchmarks -- max()


---------------------------- benchmark '100': 2 tests ----------------------------
Name (time in us)             Min                 Max               Mean          
----------------------------------------------------------------------------------
bench_max[100] (0007)     81.1052 (14.56)    147.3068 (16.35)    83.3642 (14.72)  
bench_max[100] (0016)      5.5700 (1.0)        9.0080 (1.0)       5.6622 (1.0)    
----------------------------------------------------------------------------------


---------------------------- benchmark '10000': 2 tests ----------------------------
Name (time in us)               Min                 Max               Mean          
------------------------------------------------------------------------------------
bench_max[10000] (0007)     87.3660 (15.44)    125.4501 (1.99)     89.5210 (15.41)  
bench_max[10000] (0016)      5.6571 (1.0)       63.1779 (1.0)       5.8082 (1.0)    
------------------------------------------------------------------------------------


------------------------------ benchmark '1000000': 2 tests ------------------------------
Name (time in us)                  Min                   Max                Mean          
------------------------------------------------------------------------------------------
bench_max[1000000] (0007)     383.0041 (67.56)    4,778.8059 (195.22)   396.5803 (68.24)  
bench_max[1000000] (0016)       5.6692 (1.0)         24.4791 (1.0)        5.8115 (1.0)    
------------------------------------------------------------------------------------------

Benchmarks -- min()

---------------------------- benchmark '100': 2 tests ----------------------------
Name (time in us)             Min                 Max               Mean          
----------------------------------------------------------------------------------
bench_min[100] (0008)     82.2351 (18.56)    147.4419 (7.12)     84.4832 (18.63)  
bench_min[100] (0017)      4.4298 (1.0)       20.7191 (1.0)       4.5355 (1.0)    
----------------------------------------------------------------------------------


----------------------------- benchmark '10000': 2 tests -----------------------------
Name (time in us)               Min                   Max               Mean          
--------------------------------------------------------------------------------------
bench_min[10000] (0008)     88.0619 (19.34)      152.2629 (1.0)      90.6059 (19.38)  
bench_min[10000] (0017)      4.5539 (1.0)      1,418.3088 (9.31)      4.6757 (1.0)    
--------------------------------------------------------------------------------------


----------------------------- benchmark '1000000': 2 tests -----------------------------
Name (time in us)                  Min                 Max                Mean          
----------------------------------------------------------------------------------------
bench_min[1000000] (0008)     389.9820 (85.96)    557.4350 (24.96)    399.4112 (85.46)  
bench_min[1000000] (0017)       4.5369 (1.0)       22.3320 (1.0)        4.6737 (1.0)    
----------------------------------------------------------------------------------------

@vyasr

vyasr commented Jul 14, 2022

Copy link
Copy Markdown
Contributor

OK, so I have a theory about what's going on with the isna benchmark. The performance of cupy changes quite significantly depending on whether or not cudf is imported, and when cudf is imported. Part of that makes sense to me since cudf changes cupy's memory allocator, but I do find it a bit confusing that it matters whether or not cupy performs any allocations before cudf is imported. I strongly suspect that the effect of importing cudf on cupy performance is responsible for the isna perf that you're seeing. IMO it's probably still OK to move forward since to me this indicates that the real problem is around our need/ability to improve the performance of the rmm allocator with cupy, especially since switching to pool mode makes the cupy approach faster again. The numbers below are entirely reproducible for me on multiple runs:

Python 3.8.13 | packaged by conda-forge | (default, Mar 25 2022, 06:04:10)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import cupy

In [2]: N = 10000

In [3]: %timeit cupy.zeros(N, dtype=bool)
10.6 µs ± 7.24 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [4]: %timeit cupy.zeros(N, dtype=bool)
6.82 µs ± 51.3 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

In [5]: import cudf

In [6]: %timeit cupy.zeros(N, dtype=bool)
The slowest run took 4.11 times longer than the fastest. This could mean that an intermediate result is being cached.
41.3 µs ± 26.2 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [7]: %timeit cupy.zeros(N, dtype=bool)
23.6 µs ± 1.2 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

In [8]:
Do you really want to exit ([y]/n)? y
(rapids) rapids@compose:~$ ipython
Python 3.8.13 | packaged by conda-forge | (default, Mar 25 2022, 06:04:10)
Type 'copyright', 'credits' or 'license' for more information
IPython 8.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import cupy

In [2]: import cudf

In [3]: N = 10000

In [4]: %timeit cupy.zeros(N, dtype=bool)
307 µs ± 57.8 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [5]: %timeit cupy.zeros(N, dtype=bool)
268 µs ± 3.37 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

In [6]: idx = cudf.RangeIndex(N)

In [7]: %timeit idx.isna()
79.1 µs ± 1.93 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

In [8]: cudf.set_allocator(pool=True)

In [9]: %timeit cupy.zeros(N, dtype=bool)
13.2 µs ± 71.2 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

In [10]: %timeit idx.isna()
69.9 µs ± 812 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

@shwina @galipremsagar what do you think?

@galipremsagar

Copy link
Copy Markdown
Contributor

@shwina @galipremsagar what do you think?

Yeah. That and even if you carefully see the allocated memory isn't freed up in cupy for other libraries to utilize.. I think we should move forward since this is a known problem.

@skirui-source

Copy link
Copy Markdown
Contributor Author

@vyasr is this PR ready to merge?

@vyasr vyasr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for all your hard work and all the iterations!

I was going to let Ashwin and/or Prem take a final look just in case they have a strong opinion on the isna benchmark. Let's definitely merge by EOD today though! This PR has been floating around for a long time. If they don't get to it, we can always roll back a change if they feel strongly about it. So unless someone explicitly says otherwise, I would go ahead and merge before you check out for the day.

@galipremsagar galipremsagar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a placeholder request, will unblock as I finish the review.

@galipremsagar galipremsagar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@vyasr Looks like I think it is okay for now to leave the implementation as is, these are the numbers that I was getting:

In [2]: idx = cudf.RangeIndex(0, 1000000)

# This PR (cupy.zeros)
In [3]: %timeit idx.isna()
361 µs ± 78.1 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)


# branch-22.08 (Materializing + isna)

In [3]: %timeit idx.isna()
441 µs ± 14.8 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)

Sure they aren't being quite consistent across runs. But we'd be avoiding the materialization step for now, if we see this show up in dask profiles we can think about how to optimize it then.

Comment thread python/cudf/cudf/core/index.py
Comment thread python/cudf/cudf/core/index.py
Comment thread python/cudf/cudf/core/index.py
Comment thread python/cudf/cudf/core/index.py
Comment thread python/cudf/cudf/core/index.py
Comment thread python/cudf/cudf/core/index.py
@galipremsagar galipremsagar added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 3 - Ready for Review Ready for review by team labels Jul 14, 2022
@galipremsagar

Copy link
Copy Markdown
Contributor

@gpucibot merge

@rapids-bot
rapids-bot Bot merged commit 4528d8e into NVIDIA:branch-22.08 Jul 15, 2022
@skirui-source
skirui-source deleted the RangeIndex_ branch July 15, 2022 05:14
@vyasr vyasr mentioned this pull request Oct 5, 2022
3 tasks
rapids-bot Bot pushed a commit that referenced this pull request Oct 6, 2022
These operators rely on a method that was renamed in #11272 and are also out of sync with the rest of the `RangeIndex` design now that the `__getattr__` overload has been removed (#10538).

Authors:
  - Vyas Ramasubramani (https://github.com/vyasr)

Approvers:
  - GALI PREM SAGAR (https://github.com/galipremsagar)

URL: #11868
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Performance Performance related issue Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants