Skip to content

PERF: Release GIL on some datetime ops #11263

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

Merged
merged 1 commit into from
Oct 17, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions asv_bench/benchmarks/gil.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,49 @@ def time_nogil_kth_smallest(self):
def run(arr):
algos.kth_smallest(arr, self.k)
run()

class nogil_datetime_fields(object):
goal_time = 0.2

def setup(self):
self.N = 100000000
self.dti = pd.date_range('1900-01-01', periods=self.N, freq='D')
self.period = self.dti.to_period('D')
if (not have_real_test_parallel):
raise NotImplementedError

def time_datetime_field_year(self):
@test_parallel(num_threads=2)
def run(dti):
dti.year
run(self.dti)

def time_datetime_field_day(self):
@test_parallel(num_threads=2)
def run(dti):
dti.day
run(self.dti)

def time_datetime_field_daysinmonth(self):
@test_parallel(num_threads=2)
def run(dti):
dti.days_in_month
run(self.dti)

def time_datetime_field_normalize(self):
@test_parallel(num_threads=2)
def run(dti):
dti.normalize()
run(self.dti)

def time_datetime_to_period(self):
@test_parallel(num_threads=2)
def run(dti):
dti.to_period('S')
run(self.dti)

def time_period_to_datetime(self):
@test_parallel(num_threads=2)
def run(period):
period.to_timestamp()
run(self.period)
4 changes: 4 additions & 0 deletions doc/source/whatsnew/v0.17.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ Performance Improvements

- Checking monotonic-ness before sorting on an index (:issue:`11080`)


- Release the GIL on most datetime field operations (e.g. ``DatetimeIndex.year``, ``Series.dt.year``), normalization, and conversion to and from ``Period``, ``DatetimeIndex.to_period`` and ``PeriodIndex.to_timestamp`` (:issue:`11263`)


.. _whatsnew_0171.bug_fixes:

Bug Fixes
Expand Down
8 changes: 4 additions & 4 deletions pandas/src/datetime.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ cdef extern from "datetime/np_datetime.h":
int apply_tzinfo)

npy_datetime pandas_datetimestruct_to_datetime(PANDAS_DATETIMEUNIT fr,
pandas_datetimestruct *d)
pandas_datetimestruct *d) nogil
void pandas_datetime_to_datetimestruct(npy_datetime val,
PANDAS_DATETIMEUNIT fr,
pandas_datetimestruct *result)
pandas_datetimestruct *result) nogil
int days_per_month_table[2][12]

int dayofweek(int y, int m, int d)
int is_leapyear(int64_t year)
int dayofweek(int y, int m, int d) nogil
int is_leapyear(int64_t year) nogil
PANDAS_DATETIMEUNIT get_datetime64_unit(object o)

cdef extern from "datetime/np_datetime_strings.h":
Expand Down
34 changes: 18 additions & 16 deletions pandas/src/period.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ cdef extern from "period_helper.h":

int64_t get_period_ordinal(int year, int month, int day,
int hour, int minute, int second, int microseconds, int picoseconds,
int freq) except INT32_MIN
int freq) nogil except INT32_MIN

int64_t get_python_ordinal(int64_t period_ordinal, int freq) except INT32_MIN

int get_date_info(int64_t ordinal, int freq, date_info *dinfo) except INT32_MIN
int get_date_info(int64_t ordinal, int freq, date_info *dinfo) nogil except INT32_MIN
double getAbsTime(int, int64_t, int64_t)

int pyear(int64_t ordinal, int freq) except INT32_MIN
Expand Down Expand Up @@ -139,13 +139,14 @@ def dt64arr_to_periodarr(ndarray[int64_t] dtarr, int freq, tz=None):
out = np.empty(l, dtype='i8')

if tz is None:
for i in range(l):
if dtarr[i] == iNaT:
out[i] = iNaT
continue
pandas_datetime_to_datetimestruct(dtarr[i], PANDAS_FR_ns, &dts)
out[i] = get_period_ordinal(dts.year, dts.month, dts.day,
dts.hour, dts.min, dts.sec, dts.us, dts.ps, freq)
with nogil:
for i in range(l):
if dtarr[i] == NPY_NAT:
out[i] = NPY_NAT
continue
pandas_datetime_to_datetimestruct(dtarr[i], PANDAS_FR_ns, &dts)
out[i] = get_period_ordinal(dts.year, dts.month, dts.day,
dts.hour, dts.min, dts.sec, dts.us, dts.ps, freq)
else:
out = localize_dt64arr_to_period(dtarr, freq, tz)
return out
Expand All @@ -163,11 +164,12 @@ def periodarr_to_dt64arr(ndarray[int64_t] periodarr, int freq):

out = np.empty(l, dtype='i8')

for i in range(l):
if periodarr[i] == iNaT:
out[i] = iNaT
continue
out[i] = period_ordinal_to_dt64(periodarr[i], freq)
with nogil:
for i in range(l):
if periodarr[i] == NPY_NAT:
out[i] = NPY_NAT
continue
out[i] = period_ordinal_to_dt64(periodarr[i], freq)

return out

Expand Down Expand Up @@ -245,13 +247,13 @@ def period_ordinal(int y, int m, int d, int h, int min, int s, int us, int ps, i
return get_period_ordinal(y, m, d, h, min, s, us, ps, freq)


cpdef int64_t period_ordinal_to_dt64(int64_t ordinal, int freq):
cpdef int64_t period_ordinal_to_dt64(int64_t ordinal, int freq) nogil:
cdef:
pandas_datetimestruct dts
date_info dinfo
float subsecond_fraction

if ordinal == iNaT:
if ordinal == NPY_NAT:
return NPY_NAT

get_date_info(ordinal, freq, &dinfo)
Expand Down
11 changes: 5 additions & 6 deletions pandas/src/period_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ static int dInfoCalc_SetFromDateAndTime(struct date_info *dinfo,
int yearoffset;

/* Range check */
Py_AssertWithArg(year > -(INT_MAX / 366) && year < (INT_MAX / 366),
Py_AssertWithArg(year > -(INT_MAX / 366) && year < (INT_MAX / 366),
PyExc_ValueError,
"year out of range: %i",
year);
Expand All @@ -136,7 +136,7 @@ static int dInfoCalc_SetFromDateAndTime(struct date_info *dinfo,
day);

yearoffset = dInfoCalc_YearOffset(year, calendar);
if (PyErr_Occurred()) goto onError;
if (yearoffset == INT_ERR_CODE) goto onError;

absdate = day + month_offset[leap][month - 1] + yearoffset;

Expand All @@ -155,7 +155,7 @@ static int dInfoCalc_SetFromDateAndTime(struct date_info *dinfo,

/* Calculate the absolute time */
{
Py_AssertWithArg(hour >= 0 && hour <= 23,
Py_AssertWithArg(hour >= 0 && hour <= 23,
PyExc_ValueError,
"hour out of range (0-23): %i",
hour);
Expand Down Expand Up @@ -212,8 +212,7 @@ int dInfoCalc_SetFromAbsDate(register struct date_info *dinfo,
while (1) {
/* Calculate the year offset */
yearoffset = dInfoCalc_YearOffset(year, calendar);
if (PyErr_Occurred())
goto onError;
if (yearoffset == INT_ERR_CODE) goto onError;

/* Backward correction: absdate must be greater than the
yearoffset */
Expand Down Expand Up @@ -310,7 +309,7 @@ static int calc_conversion_factors_matrix_size() {
}
matrix_size = max_value(matrix_size, period_value);
}
return matrix_size + 1;
return matrix_size + 1;
}

static void alloc_conversion_factors_matrix(int matrix_size) {
Expand Down
Loading