Merge pull request #821 from wiredfool/pypy-performance

PyPy performance improvements
This commit is contained in:
Hugo 2014-07-29 13:12:54 +03:00
commit 58c56e9aa4
6 changed files with 126 additions and 136 deletions

View File

@ -124,7 +124,8 @@ class PillowTestCase(unittest.TestCase):
self.assertTrue(found) self.assertTrue(found)
return result return result
def skipKnownBadTest(self, msg=None, platform=None, travis=None): def skipKnownBadTest(self, msg=None, platform=None,
travis=None, interpreter=None):
# Skip if platform/travis matches, and # Skip if platform/travis matches, and
# PILLOW_RUN_KNOWN_BAD is not true in the environment. # PILLOW_RUN_KNOWN_BAD is not true in the environment.
if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)): if bool(os.environ.get('PILLOW_RUN_KNOWN_BAD', False)):
@ -136,6 +137,8 @@ class PillowTestCase(unittest.TestCase):
skip = sys.platform.startswith(platform) skip = sys.platform.startswith(platform)
if travis is not None: if travis is not None:
skip = skip and (travis == bool(os.environ.get('TRAVIS', False))) skip = skip and (travis == bool(os.environ.get('TRAVIS', False)))
if interpreter is not None:
skip = skip and (interpreter == 'pypy' and hasattr(sys, 'pypy_version_info'))
if skip: if skip:
self.skipTest(msg or "Known Bad Test") self.skipTest(msg or "Known Bad Test")

View File

@ -5,12 +5,6 @@ import sys
class TestImagePoint(PillowTestCase): class TestImagePoint(PillowTestCase):
def setUp(self):
if hasattr(sys, 'pypy_version_info'):
# This takes _forever_ on PyPy. Open Bug,
# see https://github.com/python-pillow/Pillow/issues/484
self.skipTest("Too slow on PyPy")
def test_sanity(self): def test_sanity(self):
im = lena() im = lena()
@ -29,11 +23,24 @@ class TestImagePoint(PillowTestCase):
def test_16bit_lut(self): def test_16bit_lut(self):
""" Tests for 16 bit -> 8 bit lut for converting I->L images """ Tests for 16 bit -> 8 bit lut for converting I->L images
see https://github.com/python-pillow/Pillow/issues/440 see https://github.com/python-pillow/Pillow/issues/440
""" """
# This takes _forever_ on PyPy. Open Bug,
# see https://github.com/python-pillow/Pillow/issues/484
#self.skipKnownBadTest(msg="Too Slow on pypy", interpreter='pypy')
im = lena("I") im = lena("I")
im.point(list(range(256))*256, 'L') im.point(list(range(256))*256, 'L')
def test_f_lut(self):
""" Tests for floating point lut of 8bit gray image """
im = lena('L')
lut = [0.5 * float(x) for x in range(256)]
out = im.point(lut, 'F')
int_lut = [x//2 for x in range(256)]
self.assert_image_equal(out.convert('L'), im.point(int_lut, 'L'))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -42,6 +42,30 @@ class TestImagePutData(PillowTestCase):
self.assertEqual(put(sys.maxsize), (255, 255, 255, 127)) self.assertEqual(put(sys.maxsize), (255, 255, 255, 127))
def test_pypy_performance(self):
im = Image.new('L', (256,256))
im.putdata(list(range(256))*256)
def test_mode_i(self):
src = lena('L')
data = list(src.getdata())
im = Image.new('I', src.size, 0)
im.putdata(data, 2, 256)
target = [2* elt + 256 for elt in data]
self.assertEqual(list(im.getdata()), target)
def test_mode_F(self):
src = lena('L')
data = list(src.getdata())
im = Image.new('F', src.size, 0)
im.putdata(data, 2.0, 256.0)
target = [2.0* float(elt) + 256.0 for elt in data]
self.assertEqual(list(im.getdata()), target)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -5,18 +5,22 @@ from PIL import Image
class TestModeI16(PillowTestCase): class TestModeI16(PillowTestCase):
original = lena().resize((32,32)).convert('I')
def verify(self, im1): def verify(self, im1):
im2 = lena("I") im2 = self.original.copy()
self.assertEqual(im1.size, im2.size) self.assertEqual(im1.size, im2.size)
pix1 = im1.load() pix1 = im1.load()
pix2 = im2.load() pix2 = im2.load()
for y in range(im1.size[1]): for y in range(im1.size[1]):
for x in range(im1.size[0]): for x in range(im1.size[0]):
xy = x, y xy = x, y
p1 = pix1[xy]
p2 = pix2[xy]
self.assertEqual( self.assertEqual(
pix1[xy], pix2[xy], p1, p2,
("got %r from mode %s at %s, expected %r" % ("got %r from mode %s at %s, expected %r" %
(pix1[xy], im1.mode, xy, pix2[xy]))) (p1, im1.mode, xy, p2)))
def test_basic(self): def test_basic(self):
# PIL 1.1 has limited support for 16-bit image data. Check that # PIL 1.1 has limited support for 16-bit image data. Check that
@ -24,7 +28,7 @@ class TestModeI16(PillowTestCase):
def basic(mode): def basic(mode):
imIn = lena("I").convert(mode) imIn = self.original.convert(mode)
self.verify(imIn) self.verify(imIn)
w, h = imIn.size w, h = imIn.size
@ -92,7 +96,7 @@ class TestModeI16(PillowTestCase):
def test_convert(self): def test_convert(self):
im = lena("I") im = self.original.copy()
self.verify(im.convert("I;16")) self.verify(im.convert("I;16"))
self.verify(im.convert("I;16").convert("L")) self.verify(im.convert("I;16").convert("L"))

View File

@ -365,9 +365,12 @@ getbands(const char* mode)
static void* static void*
getlist(PyObject* arg, int* length, const char* wrong_length, int type) getlist(PyObject* arg, int* length, const char* wrong_length, int type)
{ {
int i, n; int i, n, itemp;
double dtemp;
void* list; void* list;
PyObject* seq;
PyObject* op;
if (!PySequence_Check(arg)) { if (!PySequence_Check(arg)) {
PyErr_SetString(PyExc_TypeError, must_be_sequence); PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL; return NULL;
@ -383,71 +386,35 @@ getlist(PyObject* arg, int* length, const char* wrong_length, int type)
if (!list) if (!list)
return PyErr_NoMemory(); return PyErr_NoMemory();
switch (type) { seq = PySequence_Fast(arg, must_be_sequence);
case TYPE_UINT8: if (!seq) {
if (PyList_Check(arg)) { free(list);
for (i = 0; i < n; i++) { PyErr_SetString(PyExc_TypeError, must_be_sequence);
PyObject *op = PyList_GET_ITEM(arg, i); return NULL;
int temp = PyInt_AsLong(op); }
((UINT8*)list)[i] = CLIP(temp);
} for (i = 0; i < n; i++) {
} else { op = PySequence_Fast_GET_ITEM(seq, i);
for (i = 0; i < n; i++) { // DRY, branch prediction is going to work _really_ well
PyObject *op = PySequence_GetItem(arg, i); // on this switch. And 3 fewer loops to copy/paste.
int temp = PyInt_AsLong(op); switch (type) {
Py_XDECREF(op); case TYPE_UINT8:
((UINT8*)list)[i] = CLIP(temp); itemp = PyInt_AsLong(op);
} ((UINT8*)list)[i] = CLIP(itemp);
break;
case TYPE_INT32:
itemp = PyInt_AsLong(op);
((INT32*)list)[i] = itemp;
break;
case TYPE_FLOAT32:
dtemp = PyFloat_AsDouble(op);
((FLOAT32*)list)[i] = (FLOAT32) dtemp;
break;
case TYPE_DOUBLE:
dtemp = PyFloat_AsDouble(op);
((double*)list)[i] = (double) dtemp;
break;
} }
break;
case TYPE_INT32:
if (PyList_Check(arg)) {
for (i = 0; i < n; i++) {
PyObject *op = PyList_GET_ITEM(arg, i);
int temp = PyInt_AsLong(op);
((INT32*)list)[i] = temp;
}
} else {
for (i = 0; i < n; i++) {
PyObject *op = PySequence_GetItem(arg, i);
int temp = PyInt_AsLong(op);
Py_XDECREF(op);
((INT32*)list)[i] = temp;
}
}
break;
case TYPE_FLOAT32:
if (PyList_Check(arg)) {
for (i = 0; i < n; i++) {
PyObject *op = PyList_GET_ITEM(arg, i);
double temp = PyFloat_AsDouble(op);
((FLOAT32*)list)[i] = (FLOAT32) temp;
}
} else {
for (i = 0; i < n; i++) {
PyObject *op = PySequence_GetItem(arg, i);
double temp = PyFloat_AsDouble(op);
Py_XDECREF(op);
((FLOAT32*)list)[i] = (FLOAT32) temp;
}
}
break;
case TYPE_DOUBLE:
if (PyList_Check(arg)) {
for (i = 0; i < n; i++) {
PyObject *op = PyList_GET_ITEM(arg, i);
double temp = PyFloat_AsDouble(op);
((double*)list)[i] = temp;
}
} else {
for (i = 0; i < n; i++) {
PyObject *op = PySequence_GetItem(arg, i);
double temp = PyFloat_AsDouble(op);
Py_XDECREF(op);
((double*)list)[i] = temp;
}
}
break;
} }
if (length) if (length)
@ -1253,6 +1220,8 @@ _putdata(ImagingObject* self, PyObject* args)
Py_ssize_t n, i, x, y; Py_ssize_t n, i, x, y;
PyObject* data; PyObject* data;
PyObject* seq;
PyObject* op;
double scale = 1.0; double scale = 1.0;
double offset = 0.0; double offset = 0.0;
@ -1292,69 +1261,61 @@ _putdata(ImagingObject* self, PyObject* args)
x = 0, y++; x = 0, y++;
} }
} else { } else {
if (scale == 1.0 && offset == 0.0) { seq = PySequence_Fast(data, must_be_sequence);
/* Clipped data */ if (!seq) {
if (PyList_Check(data)) { PyErr_SetString(PyExc_TypeError, must_be_sequence);
for (i = x = y = 0; i < n; i++) { return NULL;
PyObject *op = PyList_GET_ITEM(data, i); }
image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); if (scale == 1.0 && offset == 0.0) {
if (++x >= (int) image->xsize) /* Clipped data */
x = 0, y++; for (i = x = y = 0; i < n; i++) {
} op = PySequence_Fast_GET_ITEM(data, i);
} else { image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op));
for (i = x = y = 0; i < n; i++) { if (++x >= (int) image->xsize){
PyObject *op = PySequence_GetItem(data, i); x = 0, y++;
image->image8[y][x] = (UINT8) CLIP(PyInt_AsLong(op)); }
Py_XDECREF(op); }
if (++x >= (int) image->xsize)
x = 0, y++;
}
}
} else { } else {
if (PyList_Check(data)) { /* Scaled and clipped data */
/* Scaled and clipped data */ for (i = x = y = 0; i < n; i++) {
for (i = x = y = 0; i < n; i++) { PyObject *op = PySequence_Fast_GET_ITEM(data, i);
PyObject *op = PyList_GET_ITEM(data, i); image->image8[y][x] = CLIP(
image->image8[y][x] = CLIP( (int) (PyFloat_AsDouble(op) * scale + offset));
(int) (PyFloat_AsDouble(op) * scale + offset)); if (++x >= (int) image->xsize){
if (++x >= (int) image->xsize) x = 0, y++;
x = 0, y++; }
} }
} else { }
for (i = x = y = 0; i < n; i++) { PyErr_Clear(); /* Avoid weird exceptions */
PyObject *op = PySequence_GetItem(data, i);
image->image8[y][x] = CLIP(
(int) (PyFloat_AsDouble(op) * scale + offset));
Py_XDECREF(op);
if (++x >= (int) image->xsize)
x = 0, y++;
}
}
}
PyErr_Clear(); /* Avoid weird exceptions */
} }
} else { } else {
/* 32-bit images */ /* 32-bit images */
seq = PySequence_Fast(data, must_be_sequence);
if (!seq) {
PyErr_SetString(PyExc_TypeError, must_be_sequence);
return NULL;
}
switch (image->type) { switch (image->type) {
case IMAGING_TYPE_INT32: case IMAGING_TYPE_INT32:
for (i = x = y = 0; i < n; i++) { for (i = x = y = 0; i < n; i++) {
PyObject *op = PySequence_GetItem(data, i); op = PySequence_Fast_GET_ITEM(data, i);
IMAGING_PIXEL_INT32(image, x, y) = IMAGING_PIXEL_INT32(image, x, y) =
(INT32) (PyFloat_AsDouble(op) * scale + offset); (INT32) (PyFloat_AsDouble(op) * scale + offset);
Py_XDECREF(op); if (++x >= (int) image->xsize){
if (++x >= (int) image->xsize)
x = 0, y++; x = 0, y++;
}
} }
PyErr_Clear(); /* Avoid weird exceptions */ PyErr_Clear(); /* Avoid weird exceptions */
break; break;
case IMAGING_TYPE_FLOAT32: case IMAGING_TYPE_FLOAT32:
for (i = x = y = 0; i < n; i++) { for (i = x = y = 0; i < n; i++) {
PyObject *op = PySequence_GetItem(data, i); op = PySequence_Fast_GET_ITEM(data, i);
IMAGING_PIXEL_FLOAT32(image, x, y) = IMAGING_PIXEL_FLOAT32(image, x, y) =
(FLOAT32) (PyFloat_AsDouble(op) * scale + offset); (FLOAT32) (PyFloat_AsDouble(op) * scale + offset);
Py_XDECREF(op); if (++x >= (int) image->xsize){
if (++x >= (int) image->xsize)
x = 0, y++; x = 0, y++;
}
} }
PyErr_Clear(); /* Avoid weird exceptions */ PyErr_Clear(); /* Avoid weird exceptions */
break; break;
@ -1365,16 +1326,15 @@ _putdata(ImagingObject* self, PyObject* args)
INT32 inkint; INT32 inkint;
} u; } u;
PyObject *op = PySequence_GetItem(data, i); op = PySequence_Fast_GET_ITEM(data, i);
if (!op || !getink(op, image, u.ink)) { if (!op || !getink(op, image, u.ink)) {
Py_DECREF(op);
return NULL; return NULL;
} }
/* FIXME: what about scale and offset? */ /* FIXME: what about scale and offset? */
image->image32[y][x] = u.inkint; image->image32[y][x] = u.inkint;
Py_XDECREF(op); if (++x >= (int) image->xsize){
if (++x >= (int) image->xsize)
x = 0, y++; x = 0, y++;
}
} }
PyErr_Clear(); /* Avoid weird exceptions */ PyErr_Clear(); /* Avoid weird exceptions */
break; break;

View File

@ -21,14 +21,6 @@ if len(sys.argv) == 1:
# Make sure that nose doesn't muck with our paths. # Make sure that nose doesn't muck with our paths.
if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv): if ('--no-path-adjustment' not in sys.argv) and ('-P' not in sys.argv):
sys.argv.insert(1, '--no-path-adjustment') sys.argv.insert(1, '--no-path-adjustment')
if 'NOSE_PROCESSES' not in os.environ:
for arg in sys.argv:
if '--processes' in arg:
break
else: # for
sys.argv.insert(1, '--processes=-1') # -1 == number of cores
sys.argv.insert(1, '--process-timeout=30')
if __name__ == '__main__': if __name__ == '__main__':
profile.run("nose.main()", sort=2) profile.run("nose.main()", sort=2)