classification
Title: Restore isinstance and issubclass speed in 2.6
Type: performance Stage:
Components: Versions: Python 3.0, Python 2.6
process
Status: closed Resolution: fixed
Dependencies: 2542 Superseder:
Assigned To: pitrou Nosy List: ajaksu2, benjamin.peterson, djc, facundobatista, gregory.p.smith, pitrou, rhettinger, theller
Priority: release blocker Keywords: needs review, patch

Created on 2008-04-02 09:47 by theller, last changed 2008-08-26 22:42 by pitrou. This issue is now closed.

Files
File name Uploaded Description Edit
type_instancecheck.diff theller, 2008-04-02 09:47
type_instancecheck-2.diff theller, 2008-04-02 18:47 Cleaner patch, same functionality
isinstance3k.patch pitrou, 2008-08-25 21:11
isinstance3k-2.patch pitrou, 2008-08-25 21:42
isinstance26-2.patch pitrou, 2008-08-26 11:27
Messages (16)
msg64842 - (view) Author: Thomas Heller (theller) * (Python committer) Date: 2008-04-02 09:47
This patch implements type.__instancecheck__ and type.__subclasscheck__,
which speeds up isinstance and issubclass calls quite a bit.

See also issue #2303.

Here are the performance figures for the current trunk version:

Current SNV trunk:

Using 2.6a1+ (trunk:62102, Apr  2 2008, 11:30:16) [MSC v.1500 32 bit
(Intel)]
isinstance(42, int)	   1000000 loops, best of 3: 0.28 usec per loop
isinstance(42, type)	   1000000 loops, best of 3: 0.974 usec per loop
issubclass(object, type)   1000000 loops, best of 3: 1.1 usec per loop
issubclass(object, int)    1000000 loops, best of 3: 1.1 usec per loop
issubclass(float, int)     1000000 loops, best of 3: 1.15 usec per loop

Current trunk, patch applied:

Using 2.6a1+ (trunk:62102M, Apr  2 2008, 11:21:32) [MSC v.1500 32 bit
(Intel)]
isinstance(42, int)	   1000000 loops, best of 3: 0.274 usec per loop
isinstance(42, type)	   1000000 loops, best of 3: 0.524 usec per loop
issubclass(object, type)   1000000 loops, best of 3: 0.661 usec per loop
issubclass(object, int)    1000000 loops, best of 3: 0.662 usec per loop
issubclass(float, int)     1000000 loops, best of 3: 0.731 usec per loop

Python 2.5.2, for comparison:

Using 2.5.2 (r252:60911, Feb 21 2008, 13:11:45) [MSC v.1310 32 bit (Intel)]
isinstance(42, int)	   1000000 loops, best of 3: 0.292 usec per loop
isinstance(42, type)	   1000000 loops, best of 3: 0.417 usec per loop
issubclass(object, type)   1000000 loops, best of 3: 0.626 usec per loop
issubclass(object, int)    1000000 loops, best of 3: 0.648 usec per loop
issubclass(float, int)     1000000 loops, best of 3: 0.752 usec per loop
msg64859 - (view) Author: Facundo Batista (facundobatista) * (Python committer) Date: 2008-04-02 15:06
I find similar speedups:

isinstance(42, int)       -2%
isinstance(42, type)      45%
isinstance(object, type)  -1%
isinstance(object, int)   42%
isinstance(float, int)    44%

(both negative ones are actually lower than original, but so low that it
could be considered timing glitches)

However, also I have an error in the test_exceptions.py suite:
facundo@pomcat:~/devel/reps/python/trunk$ ./python
Lib/test/test_exceptions.py
testAttributes (__main__.ExceptionTests) ... ok
testInfiniteRecursion (__main__.ExceptionTests) ... FAIL
testKeywordArgs (__main__.ExceptionTests) ... ok
testRaising (__main__.ExceptionTests) ... ok
testReload (__main__.ExceptionTests) ... ok
testSettingException (__main__.ExceptionTests) ... ok
testSlicing (__main__.ExceptionTests) ... ok
testSyntaxErrorMessage (__main__.ExceptionTests) ... ok
testUnicodeStrUsage (__main__.ExceptionTests) ... ok
test_WindowsError (__main__.ExceptionTests) ... ok

======================================================================
FAIL: testInfiniteRecursion (__main__.ExceptionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "Lib/test/test_exceptions.py", line 336, in testInfiniteRecursion
    self.assertRaises(RuntimeError, g)
AssertionError: RuntimeError not raised

----------------------------------------------------------------------
Ran 10 tests in 0.031s

FAILED (failures=1)
Traceback (most recent call last):
  File "Lib/test/test_exceptions.py", line 351, in <module>
    test_main()
  File "Lib/test/test_exceptions.py", line 348, in test_main
    run_unittest(ExceptionTests)
  File "/home/facundo/devel/reps/python/trunk/Lib/test/test_support.py",
line 577, in run_unittest
    _run_suite(suite)
  File "/home/facundo/devel/reps/python/trunk/Lib/test/test_support.py",
line 560, in _run_suite
    raise TestFailed(err)
test.test_support.TestFailed: Traceback (most recent call last):
  File "Lib/test/test_exceptions.py", line 336, in testInfiniteRecursion
    self.assertRaises(RuntimeError, g)
AssertionError: RuntimeError not raised
msg64869 - (view) Author: Daniel Diniz (ajaksu2) Date: 2008-04-02 18:37
The test fails on this:

def g():
    try:
        return g()
    except ValueError:
        return -1
self.assertRaises(RuntimeError, g)


Changing that "return -1" to "return sys.exc_info()" shows that a
RuntimeError was raised indeed. Also, using TypeError or TabError
instead of ValueError gives the same result.

Shallow testing of the new methods seem to show normal results,

[Runtime,Value]Error.__[subclass,instance]check__([Runtime,Value]Error)

is False for cross-checks and True otherwise.
msg64870 - (view) Author: Thomas Heller (theller) * (Python committer) Date: 2008-04-02 18:47
Running Daniels code interactively, with a debug build on Windows,
additionally prints 'XXX undetected error' before the Runtime error is
raised.

I cannot see anything that is wrong with my code.  Maybe this, and the
test failure, has to do with the fact that the new
'type___subclasscheck__' function is sometimes called with the error
indicator already set?  Maybe some PyErr_Fetch() and PyErr_Restore()
calls are needed inside this function, similar to what
PyObject_IsSubclass() does?  I must admit that I do not really
understand the purpose of these calls...

Anyway, I attach a new, cleaner patch although this one still behaves in
the same, buggy, way: type_instancecheck-2.diff.
msg64875 - (view) Author: Daniel Diniz (ajaksu2) Date: 2008-04-02 20:13
Thomas: I confirm your patch triggers this behavior.  I can reliably get
a __subclasscheck__ error by trying to "import sys" after the bogus
catching happens:

>>> def g():
...   try:
...     return g()
...   except ValueError:
...     return sys.exc_info()
...
>>> g()
(<type 'exceptions.RuntimeError'>, 'maximum recursion depth exceeded
while calling a Python object', <traceback object at 0xb7d9ba04>)
>>> import sys
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: maximum recursion depth exceeded in __subclasscheck__

I hope this helps...
msg64895 - (view) Author: Thomas Heller (theller) * (Python committer) Date: 2008-04-03 09:07
Problem found.  See issue #2542, which contains a patch that fixes the
failure in test_exceptions; this test now additionally prints

  Exception RuntimeError: 'maximum recursion depth exceeded in
    __subclasscheck__' in <type 'exceptions.RuntimeError'> ignored
  Exception RuntimeError: 'maximum recursion depth exceeded while
    calling a Python object' in <type 'exceptions.RuntimeError'> ignored
msg69617 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2008-07-13 18:54
speedup of this patch confirmed.  Also, it triggers the bugs mentioned
that have their own issues open.  Once #2542 is fixed this should be
looked at again.

Its a big performance regression in 2.6 over 2.5 if we don't get this
in, marking it critical.
msg70358 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2008-07-28 17:02
Can this patch go in?
msg71937 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-25 15:17
By the way, py3k suffers from this problem too, so a similar patch
should be applied if possible.
msg71951 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-25 20:09
I'm currently doing the port to py3k. It makes me find interesting flaws
in the current isinstance/issubclass implementation :-))
msg71957 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-25 21:11
Ok, here is the patch for py3k. As I said it fixes some interesting bugs
(when the second argument was a tuple, isinstance() and issubclass()
were trying to get __instancecheck__ / __subclasscheck__ on the tuple
rather than on each of the tuple items). I also had to disable
__subclasscheck__ for exception matching, otherwise there are some nasty
issues with recursion checking.

The patch for trunk should probably be reworked or regenerated from the
py3k patch.

Performance numbers:

timeit -s "val=ValueError(); cls=EOFError" "isinstance(val, cls)"
- before patch: 1.63 usec per loop
- after patch: 0.683 usec per loop

timeit -s "val=ValueError(); cls=EOFError" "isinstance(val, (cls,))"
- before patch: 1.86 usec per loop
- after patch: 0.773 usec per loop

timeit -s "val=ValueError; cls=EOFError" "issubclass(val, cls)"
- before patch: 1.95 usec per loop
- after patch: 0.624 usec per loop

timeit -s "val=ValueError; cls=EOFError" "issubclass(val, (cls,))"
- before patch: 2.35 usec per loop
- after patch: 0.721 usec per loop


pybench
-------

("this" is with patch, "other" is without)

Test                             minimum run-time        average  run-time
                                 this    other   diff    this    other 
 diff
-------------------------------------------------------------------------------
                TryRaiseExcept:    77ms   136ms  -43.5%    77ms   137ms
 -43.5%
               WithRaiseExcept:   189ms   280ms  -32.6%   190ms   282ms
 -32.6%
-------------------------------------------------------------------------------
Totals:                           266ms   416ms  -36.1%   267ms   419ms
 -36.2%
msg71960 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-25 21:42
New patch with a couple of tiny fixes.
msg71975 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-26 11:27
Here is the same patch, but backported to 2.6.
msg71980 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2008-08-26 14:36
+1 on applying this patch right away.

For 2.6 and 3.0 to be successful, we need people to prefer to upgrade 
rather than stay with 2.5.  A systemic slowdown in not in the best 
interests of the language moving forward.
msg71981 - (view) Author: Benjamin Peterson (benjamin.peterson) * (Python committer) Date: 2008-08-26 14:52
Both patches look correct to me, and I think they can be applied.
msg72001 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-26 22:42
Committed in r66042 and r66043.
History
Date User Action Args
2008-08-26 22:42:41pitrousetstatus: open -> closed
resolution: accepted -> fixed
messages: + msg72001
keywords: patch, patch, needs review
2008-08-26 14:52:16benjamin.petersonsetkeywords: patch, patch, needs review
assignee: pitrou
resolution: accepted
messages: + msg71981
2008-08-26 14:36:55rhettingersetkeywords: patch, patch, needs review
nosy: + rhettinger
messages: + msg71980
2008-08-26 11:27:27pitrousetkeywords: patch, patch, needs review
files: + isinstance26-2.patch
messages: + msg71975
2008-08-25 21:42:54pitrousetkeywords: patch, patch, needs review
files: + isinstance3k-2.patch
messages: + msg71960
2008-08-25 21:11:27pitrousetkeywords: patch, patch, needs review
files: + isinstance3k.patch
messages: + msg71957
2008-08-25 20:09:25pitrousetkeywords: patch, patch, needs review
messages: + msg71951
2008-08-25 18:24:01djcsetnosy: + djc
2008-08-25 15:45:14benjamin.petersonsetkeywords: + needs review
2008-08-25 15:19:20pitrousetkeywords: patch, patch
versions: + Python 3.0
2008-08-25 15:17:38pitrousetkeywords: patch, patch
nosy: + pitrou
messages: + msg71937
2008-08-21 14:50:51benjamin.petersonsetpriority: critical -> release blocker
keywords: patch, patch
2008-07-28 17:02:02benjamin.petersonsetkeywords: patch, patch
nosy: + benjamin.peterson
messages: + msg70358
2008-07-13 18:54:47gregory.p.smithsettitle: Speed up isinstance and issubclass -> Restore isinstance and issubclass speed in 2.6
nosy: + gregory.p.smith
messages: + msg69617
priority: critical
dependencies: + PyErr_ExceptionMatches must not fail
keywords: patch, patch
2008-04-03 09:07:34thellersetkeywords: patch, patch
messages: + msg64895
2008-04-02 20:13:14ajaksu2setmessages: + msg64875
2008-04-02 18:47:17thellersetkeywords: patch, patch
files: + type_instancecheck-2.diff
messages: + msg64870
2008-04-02 18:37:42ajaksu2setnosy: + ajaksu2
messages: + msg64869
2008-04-02 15:06:09facundobatistasetkeywords: patch, patch
nosy: + facundobatista
messages: + msg64859
2008-04-02 09:47:47thellercreate