Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
179038f
ENH: Added libdiv
ganesh-k13 Nov 7, 2020
e89175b
ENH: Fixed typos in header | use in2 over ip2
ganesh-k13 Nov 7, 2020
565759b
ENH: Added optimal divisor
ganesh-k13 Nov 8, 2020
d0c934c
ENH: Added libdivide header
ganesh-k13 Nov 8, 2020
b02399a
ENH: Made libdivide default
ganesh-k13 Nov 8, 2020
f0ddb7c
ENH: Handled divide by 0 case
ganesh-k13 Nov 8, 2020
72dcc04
ENH: Added libdivide zlib license
ganesh-k13 Nov 9, 2020
19835d2
ENH: Removed empty structure
ganesh-k13 Nov 10, 2020
3975a28
ENH: Auto generate libdivide structs
ganesh-k13 Nov 11, 2020
90e6cf5
ENH: Logic to optimize %
ganesh-k13 Nov 11, 2020
969aa03
ENH: Fix breaking case
ganesh-k13 Nov 11, 2020
44a3a31
ENH: Change comments
ganesh-k13 Nov 11, 2020
b3d70ef
ENH: Improved floor division (#17727)
ganesh-k13 Nov 11, 2020
931134b
ENH: Added asv benchmarks
ganesh-k13 Nov 11, 2020
6e2e281
ENH: Change comments
ganesh-k13 Nov 12, 2020
90a84af
ENH: Linting
ganesh-k13 Nov 12, 2020
61c3d38
MAINT: Added libdivide as linguist-vendored
ganesh-k13 Nov 12, 2020
827bc38
ENH: Removed legacy division
ganesh-k13 Nov 12, 2020
0ce0ebd
ENH: Improved floor division (#17727)
ganesh-k13 Nov 12, 2020
c85c44a
ENH: Added libdivide to timedelta
ganesh-k13 Nov 13, 2020
0517f13
TST: Added UT for floor divide
ganesh-k13 Nov 20, 2020
a769d6f
ENH: Improved floor division (#17727)
ganesh-k13 Nov 21, 2020
0e2116f
ENH: Optimized 0 divisor cases
ganesh-k13 Nov 21, 2020
f93ca93
TST: Minor changes to floor divide | Added cases for timedelta divide
ganesh-k13 Nov 21, 2020
285d810
ENH: Remove looping definitions | Renamed fast loop macros
ganesh-k13 Nov 22, 2020
9825795
ENH: Removed unsed macro check
ganesh-k13 Nov 22, 2020
1f104fd
BUG: Added better 0 checks
ganesh-k13 Nov 23, 2020
2fde590
BENCH: Added floor divide benchmarks (#17727)
ganesh-k13 Nov 23, 2020
8912ffd
DOC: Improved floor division (#17727)
ganesh-k13 Nov 23, 2020
a5e1235
BENCH: Improve floor divide benchmarks (#17727)
ganesh-k13 Nov 23, 2020
ca4ba20
BUG,TST: Fixed division by 0 status setting
ganesh-k13 Nov 23, 2020
28aa883
MAINT: Linting fixes
ganesh-k13 Dec 1, 2020
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
12 changes: 12 additions & 0 deletions numpy/core/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@ def check_mathlib(config_cmd):
"MATHLIB env variable")
return mathlibs

def check_libdivide():
return os.environ.get('NPY_USE_LIBDIVIDE') is not None

def check_optimal_divisor():
return os.environ.get('NPY_USE_OPTIMAL_DIVISOR') is not None

def visibility_define(config):
"""Return the define value to use for NPY_VISIBILITY_HIDDEN (may be empty
string)."""
Expand Down Expand Up @@ -442,6 +448,12 @@ def generate_config_h(ext, build_dir):
mathlibs = check_mathlib(config_cmd)
moredefs.append(('MATHLIB', ','.join(mathlibs)))

# Check if libdivide needs to be used
check_libdivide() and moredefs.append('USE_LIBDIVIDE')

# Check if optimal divisor code needs to be used
check_optimal_divisor() and moredefs.append('USE_OPTIMAL_DIVISOR')

check_math_capabilities(config_cmd, ext, moredefs, mathlibs)
moredefs.extend(cocache.check_ieee_macros(config_cmd)[0])
moredefs.extend(cocache.check_complex(config_cmd, mathlibs)[0])
Expand Down
13 changes: 11 additions & 2 deletions numpy/core/src/umath/fast_loop_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,23 @@ abs_ptrdiff(char *a, char *b)
npy_intp i;\
for(i = 0; i < n; i++, ip1 += is1, op1 += os1, op2 += os2)

/** (ip1, ip2) -> (op1) */
#define BINARY_LOOP\
#define BINARY_LOOP_BASE\
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated
char *ip1 = args[0], *ip2 = args[1], *op1 = args[2];\
npy_intp is1 = steps[0], is2 = steps[1], os1 = steps[2];\
npy_intp n = dimensions[0];\
npy_intp i;\
Comment thread
ganesh-k13 marked this conversation as resolved.

#define BINARY_LOOP_SLIDING\
for(i = 0; i < n; i++, ip1 += is1, ip2 += is2, op1 += os1)

#define BINARY_LOOP_FIXED\
for(i = 0; i < n; i++, ip1 += is1, op1 += os1)

/** (ip1, ip2) -> (op1) */
#define BINARY_LOOP\
BINARY_LOOP_BASE\
BINARY_LOOP_SLIDING

/** (ip1, ip2) -> (op1, op2) */
#define BINARY_LOOP_TWO_OUT\
char *ip1 = args[0], *ip2 = args[1], *op1 = args[2], *op2 = args[3];\
Expand Down
112 changes: 112 additions & 0 deletions numpy/core/src/umath/loops.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "ufunc_object.h"

#include <string.h> /* for memchr */
#include <libdivide.h>
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated

/*
* cutoff blocksize for pairwise summation
Expand Down Expand Up @@ -826,6 +827,7 @@ NPY_NO_EXPORT void
* #TYPE = BYTE, SHORT, INT, LONG, LONGLONG#
* #type = npy_byte, npy_short, npy_int, npy_long, npy_longlong#
* #c = ,,,l,ll#
* #div = s32, s32, s32, s64, s64#
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated
*/

NPY_NO_EXPORT NPY_GCC_OPT_3 void
Expand All @@ -840,6 +842,115 @@ NPY_NO_EXPORT NPY_GCC_OPT_3 void
UNARY_LOOP_FAST(@type@, @type@, *out = in > 0 ? 1 : (in < 0 ? -1 : 0));
}

#ifdef USE_LIBDIVIDE
NPY_NO_EXPORT void
@TYPE@_divide(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
{
BINARY_LOOP_BASE

if(!is2) {
const @type@ in2 = *(@type@ *)ip2;
struct libdivide_@div@_t fast_d = libdivide_@div@_gen(in2);
BINARY_LOOP_FIXED {
const @type@ in1 = *(@type@ *)ip1;
/*
* FIXME: On x86 at least, dividing the smallest representable integer
* by -1 causes a SIFGPE (division overflow). We treat this case here
* (to avoid a SIGFPE crash at python level), but a good solution would
* be to treat integer division problems separately from FPU exceptions
* (i.e. a different approach than npy_set_floatstatus_divbyzero()).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yeah, true, and on our agenda, but currently not something we can do. Happy to keep the comment though, even if the second part applies much more generally.

*/
if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) {
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated
npy_set_floatstatus_divbyzero();
Comment thread
seberg marked this conversation as resolved.
Outdated
*((@type@ *)op1) = 0;
}
else if (((in1 > 0) != (in2 > 0)) && (in1 % in2 != 0)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can libdivide compute in1 % in2 for you? It seems a bit silly to use libdivide only to then perform a remainder calculation without it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I honestly think we can avoid this % and rewrite it as postprocessing?

@ganesh-k13 ganesh-k13 Nov 10, 2020

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So I still don't know how removing the % gave no performance boost :). The compiler is magically optimizing something. #17727 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh, interesting. @ganesh-k13 two things: First make sure you are dividing a positive by a negative number (or vice versa), otherwise this is not hit at all. Second, was the timing difference with libdivide? I guess it might be the compiler is smart enough to optimize the modulo away, but I would be surprised if it is smart enough when libdivide is being used?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

They seem to have not done it yet: ridiculousfish/libdivide#9

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All this changes is subtract one for rounding purproses, Now unless there is some edge case again, I think you can just do without the subtract, and then move the if to later, so that if (res < 0) && (res * in2 != in1) { res -= 1}?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You were right about not hitting the case, @seberg , seems like in the profile script I forgot to invert the signs. Above method seems to work, few edge cases to iron out(like <= 0, etc), will try them.

@ganesh-k13 ganesh-k13 Nov 11, 2020

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I found three edge cases:

  1. res is 0, then possible negative divisor/dividend
  2. divisor is 0 or -0, handled by putting inside else
  3. dividend is 0, handled by the same logic as 1.

Let me know if any more are there.
[EDIT]: Can use the same logic in sliding as well.

*((@type@ *)op1) = libdivide_@div@_do(in1, &fast_d) - 1;
}
else {
*((@type@ *)op1) = libdivide_@div@_do(in1, &fast_d);
}
}
}
else {
BINARY_LOOP_SLIDING { // XXX Lot of repeated code
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated
const @type@ in1 = *(@type@ *)ip1;
const @type@ in2 = *(@type@ *)ip2;
/*
* FIXME: On x86 at least, dividing the smallest representable integer
* by -1 causes a SIFGPE (division overflow). We treat this case here
* (to avoid a SIGFPE crash at python level), but a good solution would
* be to treat integer division problems separately from FPU exceptions
* (i.e. a different approach than npy_set_floatstatus_divbyzero()).
*/
if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) {
npy_set_floatstatus_divbyzero();
*((@type@ *)op1) = 0;
}
else if (((in1 > 0) != (in2 > 0)) && (in1 % in2 != 0)) {
*((@type@ *)op1) = in1/in2 - 1;
}
else {
*((@type@ *)op1) = in1/in2;
}
}
}
}
#elif defined(USE_OPTIMAL_DIVISOR)
NPY_NO_EXPORT void
@TYPE@_divide(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
{
BINARY_LOOP_BASE

if(!is2) {
const @type@ in2 = *(@type@ *)ip2;
const float in2_f = (float) in2;
Comment thread
seberg marked this conversation as resolved.
Outdated
BINARY_LOOP_FIXED {
const @type@ in1 = *(@type@ *)ip1;
/*
* FIXME: On x86 at least, dividing the smallest representable integer
* by -1 causes a SIFGPE (division overflow). We treat this case here
* (to avoid a SIGFPE crash at python level), but a good solution would
* be to treat integer division problems separately from FPU exceptions
* (i.e. a different approach than npy_set_floatstatus_divbyzero()).
*/
if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) {
npy_set_floatstatus_divbyzero();
*((@type@ *)op1) = 0;
}
else if ((in1 > 0) != (in2 > 0)) {
*((@type@ *)op1) = floor(in1/in2_f);
}
else {
*((@type@ *)op1) = in1/in2;
}
}
}
else {
BINARY_LOOP_SLIDING { // XXX Lot of repeated code
const @type@ in1 = *(@type@ *)ip1;
const @type@ in2 = *(@type@ *)ip2;
/*
* FIXME: On x86 at least, dividing the smallest representable integer
* by -1 causes a SIFGPE (division overflow). We treat this case here
* (to avoid a SIGFPE crash at python level), but a good solution would
* be to treat integer division problems separately from FPU exceptions
* (i.e. a different approach than npy_set_floatstatus_divbyzero()).
*/
if (in2 == 0 || (in1 == NPY_MIN_@TYPE@ && in2 == -1)) {
npy_set_floatstatus_divbyzero();
*((@type@ *)op1) = 0;
}
else if (((in1 > 0) != (in2 > 0)) && (in1 % in2 != 0)) {
Comment thread
ganesh-k13 marked this conversation as resolved.
Outdated
*((@type@ *)op1) = in1/in2 - 1;
}
else {
*((@type@ *)op1) = in1/in2;
}
}
}
}
#else
NPY_NO_EXPORT void
@TYPE@_divide(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
{
Expand All @@ -865,6 +976,7 @@ NPY_NO_EXPORT void
}
}
}
#endif

NPY_NO_EXPORT void
@TYPE@_remainder(char **args, npy_intp const *dimensions, npy_intp const *steps, void *NPY_UNUSED(func))
Expand Down