PY_TYPE_CHECK or isinstance?

206 views
Skip to first unread message

Simon King

unread,
Dec 20, 2010, 2:42:06 PM12/20/10
to sage-support
Dear sage-support,

at #10496, David Roe gave me the advice to use PY_TYPE_CHECK rather
than isinstance in Cython files. I did so.

But actually I didn't know PY_TYPE_CHECK at all, and so I have a two
questions:

1) Apparently there are several PY_... functions. Where can I read
about them?

2) Is PY_TYPE_CHECK really quicker than isinstance?

It doesn't seem so, actually.

In testtype.pyx, I wrote
cpdef t1(x):
return PY_TYPE_CHECK(x,int)
cpdef t2(x):
return isinstance(x,int)


Then, I got the following timings:
{{{
sage: attach typecheck.pyx
Compiling typecheck.pyx...
sage: t1(5)
False
sage: t1(int(5))
True
sage: t2(5)
False
sage: t2(int(5))
True
sage: timeit("a=t1(5)")
625 loops, best of 3: 218 ns per loop
sage: timeit("a=t2(5)")
625 loops, best of 3: 205 ns per loop
sage: timeit("a=t1(int(5))")
625 loops, best of 3: 416 ns per loop
sage: timeit("a=t2(int(5))")
625 loops, best of 3: 401 ns per loop
}}}

So, actually isinstance is slightly quicker than PY_TYPE_CHECK. Or do
I misunderstand something?

Best regards,
Simon

Jason Grout

unread,
Dec 20, 2010, 3:27:17 PM12/20/10
to sage-s...@googlegroups.com
On 12/20/10 1:42 PM, Simon King wrote:
> Dear sage-support,
>
> at #10496, David Roe gave me the advice to use PY_TYPE_CHECK rather
> than isinstance in Cython files. I did so.
>
> But actually I didn't know PY_TYPE_CHECK at all, and so I have a two
> questions:
>
> 1) Apparently there are several PY_... functions. Where can I read
> about them?


I see them imported in devel/sage/sage/ext/stdsage.pxi:

-----------------------
cdef extern from "stdsage.h":
ctypedef void PyObject

# Global tuple -- useful optimization
void init_global_empty_tuple()
object PY_NEW(object t)
object PY_NEW_SAME_TYPE(object t)

void* PY_TYPE(object o)
bint PY_TYPE_CHECK(object o, object t)
bint PY_TYPE_CHECK_EXACT(object o, object t)

object IS_INSTANCE(object o, object t)
void PY_SET_TP_NEW(object t1, object t2)
bint HAS_DICTIONARY(object o)
bint PY_IS_NUMERIC(object o)
-----------------------


The stdsage.h file is in devel/sage/c_lib/include/stdsage.h, where we find:

-----------------------
/** Tests whether zzz_obj is of type zzz_type. The zzz_type must be a
* built-in or extension type. This is just a C++-compatible wrapper
* for PyObject_TypeCheck.
*/
#define PY_TYPE_CHECK(zzz_obj, zzz_type) \
(PyObject_TypeCheck((PyObject*)(zzz_obj), (PyTypeObject*)(zzz_type)))
-----------------------

in fact, we find later on:

-----------------------

/** This is exactly the same as isinstance (and does return a Python
* bool), but the second argument must be a C-extension type -- so it
* can't be a Python class or a list. If you just want an int return
* value, i.e., aren't going to pass this back to Python, just use
* PY_TYPE_CHECK.
*/
#define IS_INSTANCE(zzz_obj, zzz_type) \
PyBool_FromLong(PY_TYPE_CHECK(zzz_obj, zzz_type))
-----------------------


...interpret how you will....

I notice in the generated C code, Cython is smart and makes isinstance
actually call PyInt_Check, which presumably is faster than PY_TYPE_CHECK.

If instead, you are checking for the Integer type, I get faster numbers
for PY_TYPE_CHECK than isinstance. I also tried to lessen the effects
of python call overhead by running the call in a loop in Cython:
http://demo.sagenb.org/home/pub/65/


Thanks,

Jason

Volker Braun

unread,
Dec 20, 2010, 3:34:46 PM12/20/10
to sage-s...@googlegroups.com
PY_TYPE_CHECK is just a wrapper macro around PyObject_TypeCheck which dereferences and compares the object type fields. So that part should be insanely fast. 

sage: cython('cpdef t(x):\n  for i in range(0,1000):\n    PY_TYPE_CHECK(x,int)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 14.1 µs per loop
sage: cython('cpdef t(x):\n  for i in range(0,1000):\n    PY_TYPE_CHECK(x,float)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 14.1 µs per loop

sage: cython('cpdef t(x):\n  for i in range(0,1000):\n    isinstance(x,int)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 182 ns per loop
sage: cython('cpdef t(x):\n  for i in range(0,1000):\n    isinstance(x,float)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 14.1 µs per loop

It seems like isinstance and PY_TYPE_CHECK are equally fast, except that isinstance caches the result if it was positive.

But if you really write Cython code then you probably want to type the argument so that the compiler knows what x is. Then I get

sage: cython('cpdef t(int x):\n  for i in range(0,1000):\n    PY_TYPE_CHECK(x,int)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 7.72 µs per loop
sage: cython('cpdef t(int x):\n  for i in range(0,1000):\n    PY_TYPE_CHECK(x,float)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 12 µs per loop

sage: cython('cpdef t(int x):\n  for i in range(0,10000):\n    isinstance(x,int)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 111 µs per loop
sage: cython('cpdef t(int x):\n  for i in range(0,10000):\n    isinstance(x,float)'); timeit("t(5000)", repeat=100)
625 loops, best of 100: 118 µs per loop

Now isinstance is a lot slower, while PY_TYPE_CHECK got moderately faster. I guess isinstance calls from C into Python while the PY_TYPE_CHECK stays in C?

Volker Braun

unread,
Dec 20, 2010, 3:40:49 PM12/20/10
to sage-s...@googlegroups.com
Aha, with Jason's answer:

Calling isinstance(x,int) on a python x is cheating, because Cython replaces it with PyInt_Check (no inheritance to check!).

Calling isinstance on a C variable x goes through IS_INSTANCE which builds an unnecessary python boolean. 

Robert Bradshaw

unread,
Dec 20, 2010, 4:31:17 PM12/20/10
to sage-s...@googlegroups.com
The PY_TYPE_CHECK macro exists primarily because Cython didn't used to
optimize isinstance.

On Mon, Dec 20, 2010 at 12:34 PM, Volker Braun <vbrau...@gmail.com> wrote:
> PY_TYPE_CHECK is just a wrapper macro around PyObject_TypeCheck which
> dereferences and compares the object type fields. So that part should be
> insanely fast.

If A is a cdef'd type (which is the precondition for using
PY_TYPE_CHECK), then "isinstance(x, A)" produces exactly the same code
as "PY_TYPE_CHECK(x, A)" (a single inline call to PyObject_TypeCheck).
Thus I'd say it's better to use the Pythonic isinstance. For some
builtins it can use even more specialized code.

No, it's because your loop is over 10000 rather than 1000.

- Robert

Robert Bradshaw

unread,
Dec 20, 2010, 4:33:13 PM12/20/10
to sage-s...@googlegroups.com

if you do "from sage.rings.integer cimport Integer" the speed
difference should go away.

- Robert

Simon King

unread,
Dec 20, 2010, 4:43:18 PM12/20/10
to sage-support
Dear Volker, dear Jason,

thank you for your answers!

So, I chose the wrong example "isinstance(x,int)". With other types to
test, PY_TYPE_CHECK will be faster.

@Volker
> But if you really write Cython code then you probably want to type the
> argument so that the compiler knows what x is.
>...
> Calling isinstance on a C variable x goes through IS_INSTANCE which builds
> an unnecessary python boolean.

Well, here I do *not* know at compile time what type to expect, so
that I can't type the argument (otherwise I won't need isinstance/
PY_TYPE_CHECK at all).

@Jason
> I see them imported in devel/sage/sage/ext/stdsage.pxi:

But is there a manual section or other reference? Searching for
PY_TYPE_CHECK on http://www.sagemath.org did not give a single hit.

Cheers,
Simon

Volker Braun

unread,
Dec 21, 2010, 7:16:06 AM12/21/10
to sage-s...@googlegroups.com
> No, it's because your loop is over 10000 rather than 1000.

Sharp eyes! :)

So, to summarize, with the improved Cython one should always use isinstance as it will be optimized to be at least as fast. I guess we should remove the PY_TYPE_CHECK macro from Sage altogether and replace every occurrence with isinstance?

 

Robert Bradshaw

unread,
Dec 21, 2010, 12:36:31 PM12/21/10
to sage-s...@googlegroups.com
On Tue, Dec 21, 2010 at 4:16 AM, Volker Braun <vbrau...@gmail.com> wrote:
>> No, it's because your loop is over 10000 rather than 1000.
>
> Sharp eyes! :)
> So, to summarize, with the improved Cython one should always use isinstance
> as it will be optimized to be at least as fast.

Yes, as long as the rhs is known by the compiler to be a type
(extension or built-in).

> I guess we should remove the
> PY_TYPE_CHECK macro from Sage altogether and replace every occurrence with
> isinstance?

Exactly. However, I just found a compiler crash with isinstance(x,
<type>t), so we should wait for the next Cython to go in before doing
this. There's a lot of cruft from when Cython just wasn't as good as
it is now that's still being used.

- Robert

Jason Grout

unread,
Dec 21, 2010, 1:29:32 PM12/21/10
to sage-s...@googlegroups.com


Do you mean wait until Cython 0.14 goes into Sage, or wait until Cython
0.15 is released and goes into Sage?

Jason

Robert Bradshaw

unread,
Dec 21, 2010, 2:46:58 PM12/21/10
to sage-s...@googlegroups.com

0.14.1, which I'm thinking will be out in January (in time for 4.6.2,
and I think the 4.6.1 window is almost closed anyway).

- Robert

Reply all
Reply to author
Forward
0 new messages