Skip to content

REF: cython cleanups and optimizations #23382

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

Closed
wants to merge 13 commits into from

Conversation

jbrockmendel
Copy link
Member

  • closes #xxxx
  • tests added / passed
  • passes git diff upstream/master -u -- "*.py" | flake8 --diff
  • whatsnew entry

@codecov
Copy link

codecov bot commented Oct 27, 2018

Codecov Report

Merging #23382 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master   #23382   +/-   ##
=======================================
  Coverage   92.22%   92.22%           
=======================================
  Files         161      161           
  Lines       51187    51187           
=======================================
  Hits        47209    47209           
  Misses       3978     3978
Flag Coverage Δ
#multiple 90.61% <ø> (ø) ⬆️
#single 42.26% <ø> (ø) ⬆️

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 4f71755...31a1319. Read the comment docs.

@jbrockmendel
Copy link
Member Author

Mostly changing ndarray to memoryviews where possible. A few notation cleanups around pointer types. Adds some extra type annotations, using py3-syntax where feasible.

@gfyoung gfyoung added Internals Related to non-user accessible pandas implementation Clean Performance Memory or execution speed performance labels Oct 27, 2018
@gfyoung
Copy link
Member

gfyoung commented Oct 27, 2018

@jbrockmendel : How is performance impacted, if at all, after these changes?

@jbrockmendel
Copy link
Member Author

How is performance impacted, if at all, after these changes?

Small but positive. For the changes of ndarray[dtype] to dtype[:] the impact is small enough that I can't measure it, but I trust the cython folks when they say that the memoryview usage is more performant.

Adding @cython.wraparound(False) and @cython.boundscheck(False) is a clear improvement. Easiest way to see this is in the output of cython -a. A decent chunk of python-land calls are avoided.

Changing ndarray[algos_t] to ndarray[algos_t, ndim=1] is another one where I can't measure it, but it should be an improvement according to cython folks.

Changing out.fill(1-flag_val) to out[:] = 1 - flag_val (prompted by change to memoryview) replaces a python call with a C call.

Removal of group_cummax_float64 etc doesn't make a difference. in core.groupby there is a lookup for getattr(libgroupby, "group_cummax") that now succeeds, so it never gets around to doing the dtype-specific lookup.

Adding type annotations in _libs.lib for non-cdef functions I'm not sure if cython actually uses those, so that might just be for funsies.

Replacing util.get_value_at(arr, i) with arr[i] in _libs.lib should be a clear-but-tiny win, since all the former does is some not-necessary-in-these-cases validation before returning arr[i].

So tiny-but-positive all around.

@gfyoung
Copy link
Member

gfyoung commented Oct 27, 2018

@jbrockmendel : Cool, just wanted to make sure we had the rationale documented 👍

@jreback jreback added this to the 0.24.0 milestone Oct 28, 2018
return modes[:j + 1]
# Note: For reasons unknown, slicing modes.base works but modes[:j+1].base
# returns an object with an incorrect length
return modes.base[:j + 1] # `.base` to access underlying np.ndarray
Copy link
Contributor

Choose a reason for hiding this comment

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

you need np.asarray i think

Copy link
Contributor

Choose a reason for hiding this comment

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

same comment

@@ -284,7 +284,7 @@ def dicts_to_array(list dicts, list columns):
else:
result[i, j] = onan

return result
return result.base # `.base` to access underlying np.ndarray
Copy link
Contributor

Choose a reason for hiding this comment

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

np.asarray

for i in range(n):
idx = indexer[i]
if idx != -1:
rev_indexer[idx] = i

return rev_indexer
return rev_indexer.base # `.base` to access underlying np.ndarray
Copy link
Contributor

Choose a reason for hiding this comment

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

same

Copy link
Contributor

@jreback jreback left a comment

Choose a reason for hiding this comment

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

see comments

@@ -525,11 +534,12 @@ def astype_unicode(arr: ndarray,

result[i] = arr_i

return result
return result.base # `.base` to access underlying np.ndarray
Copy link
Contributor

Choose a reason for hiding this comment

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

same

Seen seen = Seen()
object val
float64_t fval, fnan

if objects is None:
# Without explicitly raising, groupby.ops _aggregate_series_pure_python
# can pass None and incorrectly raise an AttributeError when trying
Copy link
Contributor

Choose a reason for hiding this comment

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

what fails for this? it is very odd you needed to change this

Copy link
Member Author

Choose a reason for hiding this comment

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

At the moment the call to _aggregate_series_pure_python raises because it expects an ndarray and gets None. But if we make it expect a memoryview then None is technically allowed. So this reinstates the raising explicitly.

@@ -2036,7 +2053,7 @@ def maybe_convert_objects(ndarray[object] objects, bint try_float=0,
if seen.datetimetz_:
if len({getattr(val, 'tzinfo', None) for val in objects}) == 1:
from pandas import DatetimeIndex
return DatetimeIndex(objects)
return DatetimeIndex(objects.base)
Copy link
Contributor

Choose a reason for hiding this comment

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

why accessing base here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because DatetimeIndex.__new__ doesn't know how to handle a cython memoryview object

Copy link
Contributor

Choose a reason for hiding this comment

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

then use np.asarray, that's the standard

Copy link
Contributor

@jreback jreback left a comment

Choose a reason for hiding this comment

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

let's avoid using .base anywhere. i believe we had this discussion once before. np.asarray is much more idiomatic and readable; its not a perf issue as these are the last call in a routine.

@jbrockmendel
Copy link
Member Author

Reverted change of foo.base to np.assarray(foo). It risks incorrectly returning np.array(None) instead of raising.

Copy link
Contributor

@jreback jreback left a comment

Choose a reason for hiding this comment

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

same comments as before

int64_t idx, curr_fill_idx=-1, filled_vals=0

N = len(out)

# Make sure all arrays are the same size
assert N == len(labels) == len(mask)

sorted_labels = np.argsort(labels, kind='mergesort').astype(
np.int64, copy=False)
sorted_labels = np.argsort(labels, kind='mergesort').astype(np.int64,
Copy link
Contributor

Choose a reason for hiding this comment

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

i prefer the former

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 revert.

@@ -837,8 +823,3 @@ def group_cummax(ndarray[groupby_t, ndim=2] out,
if val > mval:
accum[lab, j] = mval = val
out[i, j] = mval


group_cummax_float64 = group_cummax["float64_t"]
Copy link
Contributor

Choose a reason for hiding this comment

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

are these just relics?

Copy link
Member Author

Choose a reason for hiding this comment

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

groupby.ops (I think) looks these up by doing something like:

funcname = "group_cummax"
func = getattr(libgroupby, funcname, None)
if func is None:
    func = getattr(libgroupby, funcname+"_"+dtype)

Before we used the fused types, it would succeed on the second getattr. Now it succeeds on the first.

return modes[:j + 1]
# Note: For reasons unknown, slicing modes.base works but modes[:j+1].base
# returns an object with an incorrect length
return modes.base[:j + 1] # `.base` to access underlying np.ndarray
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment

@jbrockmendel
Copy link
Member Author

updated to use np.asarray(foo) instead of foo.base and to explicitly reject None in all appropriate places. I think we may also need to do a pass to add const to memoryview-accepting py-exposed functions. Between these two, the added verbiage is making this look less appealing.

@jreback
Copy link
Contributor

jreback commented Nov 2, 2018

needs a rebase

@jbrockmendel
Copy link
Member Author

Rebased, but pls hold off until I make another pass.

jbrockmendel added a commit to jbrockmendel/pandas that referenced this pull request Nov 2, 2018
@jbrockmendel
Copy link
Member Author

The worthwhile parts of this are in #23464. Closing.

@jbrockmendel jbrockmendel deleted the libmore branch November 3, 2018 01:08
jreback pushed a commit that referenced this pull request Nov 3, 2018
JustinZhengBC pushed a commit to JustinZhengBC/pandas that referenced this pull request Nov 14, 2018
tm9k1 pushed a commit to tm9k1/pandas that referenced this pull request Nov 19, 2018
Pingviinituutti pushed a commit to Pingviinituutti/pandas that referenced this pull request Feb 28, 2019
Pingviinituutti pushed a commit to Pingviinituutti/pandas that referenced this pull request Feb 28, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Clean Internals Related to non-user accessible pandas implementation Performance Memory or execution speed performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants