PERF: speed up PeriodArray creation by exposing dayfirst/yearfirst params
State: closed

As much of the time creating a `PeriodArray` from `int`s is actually spent importing/querying `get_option('display.date_dayfirst')` and its `yearfirst` cousin, this PR exposes those parameters in `Period.__new__()` to allow them to be queried once per array creation.

This yields a ~15x speedup:
```
asv compare upstream/master HEAD -s --sort ratio

Benchmarks that have improved:

       before           after         ratio
     [210538e4]       [32288230]
     <period_dayfirst~1>       <period_dayfirst>
-       102±0.9ms       7.08±0.2ms     0.07  period.PeriodIndexConstructor.time_from_ints('D', True)
-         101±2ms       6.56±0.3ms     0.06  period.PeriodIndexConstructor.time_from_ints('D', False)
```
- [ ] closes #xxxx
- [x] tests added / passed
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry


Labels:
name: Performance
description: Memory or execution speed performance
name: Period
description: Period data type

Comments:
Hello @qwhelan! Thanks for updating the PR.



Cheers ! There are no PEP8 issues in this Pull Request. :beers: 

##### Comment last updated on December 29, 2018 at 09:59 Hours UTC
> Fine by me. Elsewhere we're trying to move towards fewer arguments in the constructors. Should we consider a dedicated constructor for this? (not necessarily for this PR)

I am personally -1 on adding this to the Period constructor. 
(They are also not present on the Timestamp constructor. They are on DatetimeIndex, but I would rather see them deprecated there, in favor of `to_datetime` for all non-default string parsing).

I also find it strange that a `display` option actually determines how an input is parsed. I would rather deprecate that ability (as I understand this is what makes it slower?).
> I also find it strange that a display option actually determines how an input is parsed. I would rather deprecate that ability (as I understand this is what makes it slower?).

Yeah, we're spending 90%+ of runtime importing/querying these display variables. Unfortunately, this same code path is used for handling `DatetimeIndex` slicing, so deprecation is likely to be tricky.

The caveat on the above is that this speedup is only when creating a `PeriodArray` directly from integers. This was not covered by our `asv` suite until the past week, but is leveraged in places like `get_indexer_non_unique()`.

Regarding the name of the option, it seems the intent was for it to cover display + parsing, but only parsing was ever implemented (per https://github.com/pandas-dev/pandas/issues/11501#issuecomment-152848626 )
@qwhelan ok adding the benchmarks, but yea let's not mess with the Period constructor. where does the get_option things get called now that is slowings things down?
@jreback `Period.__new__()` calls `_lib.parsers.parse_datetime_string` without passing `yearfirst` or `dayfirst`. When either of these are `None`, `parse_datetime_string` imports `get_option()` and calls it for each missing parameter. This import is done inside the function due to a cyclic dependency and accounts for ~95% of the runtime in simple cases. The gains seen here are mostly due to eliminating an import-per-Period object (the rest is eliminating duplicative `get_option()` calls).

Given the desire to keep the signature the same, I dug into breaking the import cycle and think I found a sensible way to break it - this yields a 7x improvement (down from 15x). The short description of the cycle is that importing `get_option()` pulls in essentially all cython extensions via `pandas.io.formats.printing`, which imports `is_sequence()` from `pandas._libs.lib`. My thinking is to move the `is_*()` functions into a `pandas._libs.inference.pyx` module analogous to `pandas.core.dtypes.inference.py` (alternatively, we could guard the import in `pandas.io.formats.printing`). This breaks the cycle, allowing the `get_option` import to be moved into the global scope and lets us have most of the speedup without any API changes.
@qwhelan look at how the import is deferred in ``pandas.io.pytables``, e.g. ``_tables()``. not super petty but should work here. basically

```
_get_option = None



def get_option():
     global _get_option
     if _get_option is None:
          from pandas.core.config_init import get_option
          _get_option = get_option
     return _get_option
```  

then you can use it like
```
day_first = get_option().get(....)
```
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=h1) Report
> Merging [#24118](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/80295f9483176b1335226680f1b55f09ded9ad46?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24118/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24118      +/-   ##
==========================================
+ Coverage   92.32%   92.32%   +<.01%     
==========================================
  Files         166      166              
  Lines       52298    52298              
==========================================
+ Hits        48285    48286       +1     
+ Misses       4013     4012       -1
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.74% <ø> (ø)` | :arrow_up: |
| #single | `43.05% <ø> (-0.01%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `87.84% <0%> (+0.09%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=footer). Last update [80295f9...bd9eddb](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=h1) Report
> Merging [#24118](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/3086e0a170934d87b01fa910f371002a55fd07b9?src=pr&el=desc) will **decrease** coverage by `0.1%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/24118/graphs/tree.svg?width=650&token=eZ4WkYLtcO&height=150&src=pr)](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #24118      +/-   ##
==========================================
- Coverage   92.31%    92.2%   -0.11%     
==========================================
  Files         163      162       -1     
  Lines       51987    51703     -284     
==========================================
- Hits        47990    47674     -316     
- Misses       3997     4029      +32
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `90.6% <ø> (-0.12%)` | :arrow_down: |
| #single | `43.03% <ø> (+0.03%)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/io/s3.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL3MzLnB5) | `0% <0%> (-86.37%)` | :arrow_down: |
| [pandas/io/parquet.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL3BhcnF1ZXQucHk=) | `76.92% <0%> (-7.7%)` | :arrow_down: |
| [pandas/core/arrays/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYXJyYXlzL2Jhc2UucHk=) | `96.77% <0%> (-0.84%)` | :arrow_down: |
| [pandas/compat/numpy/function.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvbXBhdC9udW1weS9mdW5jdGlvbi5weQ==) | `87.13% <0%> (-0.78%)` | :arrow_down: |
| [pandas/io/common.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2lvL2NvbW1vbi5weQ==) | `72.09% <0%> (-0.78%)` | :arrow_down: |
| [pandas/core/indexes/category.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9jYXRlZ29yeS5weQ==) | `97.9% <0%> (-0.76%)` | :arrow_down: |
| [pandas/core/indexes/interval.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9pbnRlcnZhbC5weQ==) | `94.73% <0%> (-0.53%)` | :arrow_down: |
| [pandas/core/tools/datetimes.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvdG9vbHMvZGF0ZXRpbWVzLnB5) | `85.27% <0%> (-0.49%)` | :arrow_down: |
| [pandas/util/testing.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL3V0aWwvdGVzdGluZy5weQ==) | `87.41% <0%> (-0.43%)` | :arrow_down: |
| [pandas/core/dtypes/concat.py](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2NvbmNhdC5weQ==) | `96.63% <0%> (-0.43%)` | :arrow_down: |
| ... and [39 more](https://codecov.io/gh/pandas-dev/pandas/pull/24118/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=footer). Last update [3086e0a...c779265](https://codecov.io/gh/pandas-dev/pandas/pull/24118?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

@jreback Eliminated the constructor changes and replaced with your suggestion. Also added a new benchmark as the int case before was hitting a special case. Here's the new benchmark comparison:
```
$ asv compare upstream/master HEAD --sort ratio -s

Benchmarks that have improved:

       before           after         ratio
     [3086e0a1]       [b0a82749]
-        394±20μs          300±8μs     0.76  period.PeriodConstructor.time_period_constructor('D', False)
-        414±10μs          312±8μs     0.75  period.PeriodConstructor.time_period_constructor('D', True)
-         192±3ms         99.2±3ms     0.52  period.PeriodIndexConstructor.time_from_ints_daily('D', False)
-         194±3ms         95.6±2ms     0.49  period.PeriodIndexConstructor.time_from_ints_daily('D', True)
-         108±2ms         16.8±1ms     0.16  period.PeriodIndexConstructor.time_from_ints('D', False)
-         109±2ms         15.3±1ms     0.14  period.PeriodIndexConstructor.time_from_ints('D', True)
```
@jreback Updated with whatsnew and comment. There's no specific issue open for this - I believe I stumbled across it while reviewing asv results.
thanks @qwhelan nice!