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

PERF: optimize is_scalar, is_iterator #31294

Merged
merged 1 commit into from
Jan 26, 2020

Conversation

jbrockmendel
Copy link
Member

While working on #30349 I noticed that is_scalar is pretty slow for non-scalar args, turns out we can get a 9-19x improvement for listlike cases, 5-10x improvement for Decimal/Period/DateOffset/Interval, with small improvements in nearly every other case while we're at it (the fractions.Fraction object is the only one where i found a small decrease in perf, within the margin of error)

xref #31291, assuming this is the desired behavior for is_iterator, this closes that.

Setup:

import pandas as pd                                                     
from pandas.core.dtypes.common import *                                 
import decimal, fractions

dec = decimal.Decimal(19.45678)
frac = fractions.Fraction(1)
i64 = np.int64(-1)
arr = np.arange(5)
ser = pd.Series(arr)
idx = pd.Index(arr)
interval = pd.Interval(0, 1)
per = pd.Period("2017")
offset = per - per
iterator = (x for x in [])

Non-Scalar Cases

In [6]: %timeit is_scalar([])
1.53 µs ± 36.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
79.7 ns ± 5.26 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [7]: %timeit is_scalar(arr)
1.58 µs ± 43.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
77.6 ns ± 5.02 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [8]: %timeit is_scalar(ser)
1.01 µs ± 51.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
92.3 ns ± 0.505 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [9]: %timeit is_scalar(idx)
876 ns ± 4.86 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
92.7 ns ± 1.47 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [10]: %timeit is_scalar(iterator)
2.08 µs ± 22.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)  # <-- master
869 ns ± 20.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- PR

Non-Built-In Scalar Cases

In [10]: %timeit is_scalar(dec)
1.05 µs ± 9.75 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
79.5 ns ± 2.16 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [12]: %timeit is_scalar(interval)
748 ns ± 22.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
139 ns ± 5.38 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [13]: %timeit is_scalar(per)
706 ns ± 24.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
127 ns ± 14.2 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [14]: %timeit is_scalar(offset)
930 ns ± 40.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
211 ns ± 2.45 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- PR

Built-In Scalar Cases

In [3]: %timeit is_scalar(4)
60.1 ns ± 2.39 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- master
54.9 ns ± 1.15 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [4]: %timeit is_scalar(4.0)
53 ns ± 4.94 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- master
48.2 ns ± 0.613 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [5]: %timeit is_scalar(i64)
58.5 ns ± 1.39 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- master
58.1 ns ± 5.88 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [11]: %timeit is_scalar(frac)
93.5 ns ± 2.05 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- master
94.1 ns ± 6.4 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

is_iterator check

In [16] %timeit is_iterator(iterator)
283 ns ± 5.35 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
58.9 ns ± 2.31 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [17] %timeit is_iterator(arr)
228 ns ± 3.98 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
55 ns ± 3.53 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

In [17] %timeit is_iterator("foo")
231 ns ± 15.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)  # <-- master
49.4 ns ± 5.08 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)  # <-- PR

Copy link
Member

@WillAyd WillAyd left a comment

Choose a reason for hiding this comment

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

lgtm

or is_interval(val)
or util.is_offset_object(val))


def is_iterator(obj: object) -> bool:
Copy link
Member

Choose a reason for hiding this comment

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

Could arguably just use PyIter_Check where needed now and avoid overhead of def call

Copy link
Member Author

Choose a reason for hiding this comment

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

so far no usage of this in the cython code, it is just replacing the version in core.dtypes.inference

@WillAyd WillAyd added the Performance Memory or execution speed performance label Jan 24, 2020
@jreback jreback added this to the 1.1 milestone Jan 26, 2020
@jreback jreback merged commit f1d7ac6 into pandas-dev:master Jan 26, 2020
@jreback
Copy link
Contributor

jreback commented Jan 26, 2020

sure

@jbrockmendel
Copy link
Member Author

i was expecting you to be more excited about this!

@jreback
Copy link
Contributor

jreback commented Jan 26, 2020

lol, after going thru lots of PRs i type shortest things! this is good
😄

keechongtan added a commit to keechongtan/pandas that referenced this pull request Jan 27, 2020
…ndexing-1row-df

* upstream/master: (194 commits)
  DOC Remove Python 2 specific comments from documentation (pandas-dev#31198)
  Follow up PR: pandas-dev#28097 Simplify branch statement (pandas-dev#29243)
  BUG: DatetimeIndex.snap incorrectly setting freq (pandas-dev#31188)
  Move DataFrame.info() to live with similar functions (pandas-dev#31317)
  ENH: accept a dictionary in plot colors (pandas-dev#31071)
  PERF: add shortcut to Timestamp constructor (pandas-dev#30676)
  CLN/MAINT: Clean and annotate stata reader and writers (pandas-dev#31072)
  REF: define _get_slice_axis in correct classes (pandas-dev#31304)
  BUG: DataFrame.floordiv(ser, axis=0) not matching column-wise bheavior (pandas-dev#31271)
  PERF: optimize is_scalar, is_iterator (pandas-dev#31294)
  BUG: Series rolling count ignores min_periods (pandas-dev#30923)
  xfail sparse warning; closes pandas-dev#31310 (pandas-dev#31311)
  REF: DatetimeIndex.get_value wrap DTI.get_loc (pandas-dev#31314)
  CLN: internals.managers (pandas-dev#31316)
  PERF: avoid copies if possible in fill_binop (pandas-dev#31300)
  Add test for multiindex json (pandas-dev#31307)
  BUG: passing TDA and wrong freq to TimedeltaIndex (pandas-dev#31268)
  BUG: inconsistency between PeriodIndex.get_value vs get_loc (pandas-dev#31172)
  CLN: remove _set_subtyp (pandas-dev#31301)
  CI: Updated version of macos image (pandas-dev#31292)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Performance Memory or execution speed performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants