Skip to content

gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ #131914

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

Open
wants to merge 29 commits into
base: main
Choose a base branch
from

Conversation

dolfinus
Copy link

@dolfinus dolfinus commented Mar 30, 2025

For python build using --enable-optimizations:

test_performance_abc_isinstance.py
from abc import ABCMeta
#from _py_abc import ABCMeta
import time
import psutil
import os

class Root(metaclass=ABCMeta):
    pass

class Class1(Root):
    pass

class Class2(Root):
    pass

class Class3(Root):
    pass

class Class4(Root):
    subclasses = []
    @classmethod
    def __subclasses__(cls):
        return cls.subclasses


def _create_subclass1():
    class NestedClass1(Class1):
        pass

    return NestedClass1


def _create_subclass2():
    class NestedClass2(Class2):
        pass

    return NestedClass2


def _create_subclass3():
    class NestedClass3:
        pass

    Class3.register(NestedClass3)

    return NestedClass3


def _create_subclass4():
    class NestedClass4:
        pass

    Class4.subclasses.append(NestedClass4)

    return NestedClass4


def test_isinstance_performance():
    iters = 2000
    print("Creating new classes to check")
    objects_subclass1 = []
    for i in range(iters):
        subclass1 = _create_subclass1()
        objects_subclass1.append(subclass1())
    objects_subclass2 = []
    for i in range(iters):
        subclass2 = _create_subclass2()
        objects_subclass2.append(subclass2())

    objects_subclass3 = []
    for i in range(iters):
        subclass3 = _create_subclass3()
        objects_subclass3.append(subclass3())

    objects_subclass4 = []
    for i in range(iters):
        subclass4 = _create_subclass4()
        objects_subclass4.append(subclass4())

    # create one more subclass for sibling check
    another_subclass1 = _create_subclass1()
    another_subclass2 = _create_subclass2()
    another_subclass3 = _create_subclass3()
    another_subclass4 = _create_subclass4()

    print("Created subclasses =", len(objects_subclass1) + len(objects_subclass2) + len(objects_subclass3) + len(objects_subclass4))
    print("Consumed memory, Mb:", psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2)
    print("Started compairing objects")

    # New subclass against parent, sibling, grantparent and cousin
    new_subclass1_vs_class1_ns = 0
    new_subclass1_vs_another_subclass1_ns = 0
    new_subclass1_vs_root_ns = 0
    new_subclass1_vs_class2_ns = 0
    for obj1 in objects_subclass1:
        start = time.perf_counter_ns()
        assert isinstance(obj1, Class1)
        new_subclass1_vs_class1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj1, another_subclass1)
        new_subclass1_vs_another_subclass1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj1, Root)
        new_subclass1_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj1, Class2)
        new_subclass1_vs_class2_ns += time.perf_counter_ns() - start

    # Same subclass against parent, sibling, grantparent and cousin
    cached_subclass1_vs_root_ns = 0
    cached_subclass1_vs_another_subclass1_ns = 0
    cached_subclass1_vs_class1_ns = 0
    cached_subclass1_vs_class2_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert isinstance(obj1, Class1)
        cached_subclass1_vs_class1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj1, another_subclass1)
        cached_subclass1_vs_another_subclass1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj1, Root)
        cached_subclass1_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj1, Class2)
        cached_subclass1_vs_class2_ns += time.perf_counter_ns() - start

    # new class (via .register) against parent, sibling, grantparent and cousin
    new_subclass3_vs_class3_ns = 0
    new_subclass3_vs_another_subclass3_ns = 0
    new_subclass3_vs_root_ns = 0
    new_subclass3_vs_class1_ns = 0
    for obj3 in objects_subclass3:
        start = time.perf_counter_ns()
        assert isinstance(obj3, Class3)
        new_subclass3_vs_class3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj3, another_subclass3)
        new_subclass3_vs_another_subclass3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj3, Root)
        new_subclass3_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj3, Class1)
        new_subclass3_vs_class1_ns += time.perf_counter_ns() - start

    # same class (via .register) against parent, sibling, grantparent and cousin
    cached_subclass3_vs_class3_ns = 0
    cached_subclass3_vs_another_subclass3_ns = 0
    cached_subclass3_vs_root_ns = 0
    cached_subclass3_vs_class1_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert isinstance(obj3, Class3)
        cached_subclass3_vs_class3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj3, another_subclass3)
        cached_subclass3_vs_another_subclass3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj3, Root)
        cached_subclass3_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj3, Class1)
        cached_subclass3_vs_class1_ns += time.perf_counter_ns() - start

    # new class (via __subclasses__) against parent, sibling, grantparent and cousin
    new_subclass4_vs_class4_ns = 0
    new_subclass4_vs_another_subclass4_ns = 0
    new_subclass4_vs_root_ns = 0
    new_subclass4_vs_class1_ns = 0
    for obj4 in objects_subclass4:
        start = time.perf_counter_ns()
        assert isinstance(obj4, Class4)
        new_subclass4_vs_class4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj4, another_subclass4)
        new_subclass4_vs_another_subclass4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj4, Root)
        new_subclass4_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj4, Class1)
        new_subclass4_vs_class1_ns += time.perf_counter_ns() - start

    # same class (via __subclasses__) against parent, sibling, grantparent and cousin
    cached_subclass4_vs_class4_ns = 0
    cached_subclass4_vs_another_subclass4_ns = 0
    cached_subclass4_vs_root_ns = 0
    cached_subclass4_vs_class1_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert isinstance(obj4, Class4)
        cached_subclass4_vs_class4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj4, another_subclass4)
        cached_subclass4_vs_another_subclass4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert isinstance(obj4, Root)
        cached_subclass4_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not isinstance(obj4, Class1)
        cached_subclass4_vs_class1_ns += time.perf_counter_ns() - start

    print("Completed compairing classes.")
    print("Consumed memory, Mb:", psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2)

    total_ns = sum([
        cached_subclass1_vs_root_ns,
        cached_subclass1_vs_another_subclass1_ns,
        cached_subclass1_vs_class1_ns,
        cached_subclass1_vs_class2_ns,
        cached_subclass3_vs_class3_ns,
        cached_subclass3_vs_another_subclass3_ns,
        cached_subclass3_vs_root_ns,
        cached_subclass3_vs_class1_ns,
        cached_subclass4_vs_class4_ns,
        cached_subclass4_vs_another_subclass4_ns,
        cached_subclass4_vs_root_ns,
        cached_subclass4_vs_class1_ns,
        new_subclass1_vs_class1_ns,
        new_subclass1_vs_another_subclass1_ns,
        new_subclass1_vs_root_ns,
        new_subclass1_vs_class2_ns,
        new_subclass3_vs_class3_ns,
        new_subclass3_vs_another_subclass3_ns,
        new_subclass3_vs_root_ns,
        new_subclass3_vs_class1_ns,
        new_subclass4_vs_class4_ns,
        new_subclass4_vs_another_subclass4_ns,
        new_subclass4_vs_root_ns,
        new_subclass4_vs_class1_ns,
    ])
    print("Total, sec:", total_ns / 10**9)

    print("isinstance(cached class, parent), us:", cached_subclass1_vs_root_ns / iters / 1000)
    print("isinstance(cached class, sibling), us:", cached_subclass1_vs_another_subclass1_ns / iters / 1000)
    print("isinstance(cached class, grandparent), us:", cached_subclass1_vs_class1_ns / iters / 1000)
    print("isinstance(cached class, cousin), us:", cached_subclass1_vs_class2_ns / iters / 1000)

    print("isinstance(cached class, parent via .register()), us:", cached_subclass3_vs_class3_ns / iters / 1000)
    print("isinstance(cached class, sibling via .register()), us:", cached_subclass3_vs_another_subclass3_ns / iters / 1000)
    print("isinstance(cached class, grandparent via .register()), us:", cached_subclass3_vs_root_ns / iters / 1000)
    print("isinstance(cached class, cousin via .register()), us:", cached_subclass3_vs_class1_ns / iters / 1000)

    print("isinstance(cached class, parent via __subclasses__), us:", cached_subclass4_vs_class4_ns / iters / 1000)
    print("isinstance(cached class, sibling via __subclasses__), us:", cached_subclass4_vs_another_subclass4_ns / iters / 1000)
    print("isinstance(cached class, grandparent via __subclasses__), us:", cached_subclass4_vs_root_ns / iters / 1000)
    print("isinstance(cached class, cousin via __subclasses__), us:", cached_subclass4_vs_class1_ns / iters / 1000)

    print("isinstance(new class, parent), us:", new_subclass1_vs_class1_ns / len(objects_subclass1) / 1000)
    print("isinstance(new class, sibling), us:", new_subclass1_vs_another_subclass1_ns / len(objects_subclass1) / 1000)
    print("isinstance(new class, grandparent), us:", new_subclass1_vs_root_ns / len(objects_subclass1) / 1000)
    print("isinstance(new class, cousin), us:", new_subclass1_vs_class2_ns / len(objects_subclass1) / 1000)

    print("isinstance(new class, parent via .register()), us:", new_subclass3_vs_class3_ns / len(objects_subclass3) / 1000)
    print("isinstance(new class, sibling via .register()), us:", new_subclass3_vs_another_subclass3_ns / len(objects_subclass3) / 1000)
    print("isinstance(new class, grandparent via .register()), us:", new_subclass3_vs_root_ns / len(objects_subclass3) / 1000)
    print("isinstance(new class, cousin via .register()), us:", new_subclass3_vs_class1_ns / len(objects_subclass3) / 1000)

    print("isinstance(new class, parent via __subclasses__), us:", new_subclass4_vs_class4_ns / len(objects_subclass4) / 1000)
    print("isinstance(new class, sibling via __subclasses__), us:", new_subclass4_vs_another_subclass4_ns / len(objects_subclass4) / 1000)
    print("isinstance(new class, grandparent via __subclasses__), us:", new_subclass4_vs_root_ns / len(objects_subclass4) / 1000)
    print("isinstance(new class, cousin via __subclasses__), us:", new_subclass4_vs_class1_ns / len(objects_subclass4) / 1000)

if __name__ == "__main__":
    test_isinstance_performance()
test_performance_abc_issubclass.py
from abc import ABCMeta
#from _py_abc import ABCMeta
import time
import psutil
import os

class Root(metaclass=ABCMeta):
    pass

class Class1(Root):
    pass

class Class2(Root):
    pass

class Class3(Root):
    pass

class Class4(Root):
    subclasses = []
    @classmethod
    def __subclasses__(cls):
        return cls.subclasses


def _create_subclass1():
    class NestedClass1(Class1):
        pass

    return NestedClass1


def _create_subclass2():
    class NestedClass2(Class2):
        pass

    return NestedClass2


def _create_subclass3():
    class NestedClass3:
        pass

    Class3.register(NestedClass3)

    return NestedClass3


def _create_subclass4():
    class NestedClass4:
        pass

    Class4.subclasses.append(NestedClass4)

    return NestedClass4


def test_issubclass_performance():
    iters = 2000
    print("Creating new classes to check")
    nested_subclass1 = []
    for i in range(iters):
        subclass1 = _create_subclass1()
        nested_subclass1.append(subclass1)
    nested_subclass2 = []
    for i in range(iters):
        subclass2 = _create_subclass2()
        nested_subclass2.append(subclass2)

    nested_subclass3 = []
    for i in range(iters):
        subclass3 = _create_subclass3()
        nested_subclass3.append(subclass3)

    nested_subclass4 = []
    for i in range(iters):
        subclass4 = _create_subclass4()
        nested_subclass4.append(subclass4)

    # create one more subclass for sibling check
    another_subclass1 = _create_subclass1()
    another_subclass2 = _create_subclass2()
    another_subclass3 = _create_subclass3()
    another_subclass4 = _create_subclass4()

    print("Created subclasses =", len(nested_subclass1) + len(nested_subclass2) + len(nested_subclass3) + len(nested_subclass4))
    print("Consumed memory, Mb:", psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2)
    print("Started compairing objects")

    # New subclass against parent, sibling, grantparent and cousin
    new_subclass1_vs_class1_ns = 0
    new_subclass1_vs_another_subclass1_ns = 0
    new_subclass1_vs_root_ns = 0
    new_subclass1_vs_class2_ns = 0
    for cls1 in nested_subclass1:
        start = time.perf_counter_ns()
        assert issubclass(cls1, Class1)
        new_subclass1_vs_class1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls1, another_subclass1)
        new_subclass1_vs_another_subclass1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls1, Root)
        new_subclass1_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls1, Class2)
        new_subclass1_vs_class2_ns += time.perf_counter_ns() - start

    # Same subclass against parent, sibling, grantparent and cousin
    cached_subclass1_vs_root_ns = 0
    cached_subclass1_vs_another_subclass1_ns = 0
    cached_subclass1_vs_class1_ns = 0
    cached_subclass1_vs_class2_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert issubclass(cls1, Class1)
        cached_subclass1_vs_class1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls1, another_subclass1)
        cached_subclass1_vs_another_subclass1_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls1, Root)
        cached_subclass1_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls1, Class2)
        cached_subclass1_vs_class2_ns += time.perf_counter_ns() - start

    # new class (via .register) against parent, sibling, grantparent and cousin
    new_subclass3_vs_class3_ns = 0
    new_subclass3_vs_another_subclass3_ns = 0
    new_subclass3_vs_root_ns = 0
    new_subclass3_vs_class1_ns = 0
    for cls3 in nested_subclass3:
        start = time.perf_counter_ns()
        assert issubclass(cls3, Class3)
        new_subclass3_vs_class3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls3, another_subclass3)
        new_subclass3_vs_another_subclass3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls3, Root)
        new_subclass3_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls3, Class1)
        new_subclass3_vs_class1_ns += time.perf_counter_ns() - start

    # same class (via .register) against parent, sibling, grantparent and cousin
    cached_subclass3_vs_class3_ns = 0
    cached_subclass3_vs_another_subclass3_ns = 0
    cached_subclass3_vs_root_ns = 0
    cached_subclass3_vs_class1_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert issubclass(cls3, Class3)
        cached_subclass3_vs_class3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls3, another_subclass3)
        cached_subclass3_vs_another_subclass3_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls3, Root)
        cached_subclass3_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls3, Class1)
        cached_subclass3_vs_class1_ns += time.perf_counter_ns() - start

    # new class (via __subclasses__) against parent, sibling, grantparent and cousin
    new_subclass4_vs_class4_ns = 0
    new_subclass4_vs_another_subclass4_ns = 0
    new_subclass4_vs_root_ns = 0
    new_subclass4_vs_class1_ns = 0
    for cls4 in nested_subclass4:
        start = time.perf_counter_ns()
        assert issubclass(cls4, Class4)
        new_subclass4_vs_class4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls4, another_subclass4)
        new_subclass4_vs_another_subclass4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls4, Root)
        new_subclass4_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls4, Class1)
        new_subclass4_vs_class1_ns += time.perf_counter_ns() - start

    # same class (via __subclasses__) against parent, sibling, grantparent and cousin
    cached_subclass4_vs_class4_ns = 0
    cached_subclass4_vs_another_subclass4_ns = 0
    cached_subclass4_vs_root_ns = 0
    cached_subclass4_vs_class1_ns = 0
    for _ in range(iters):
        start = time.perf_counter_ns()
        assert issubclass(cls4, Class4)
        cached_subclass4_vs_class4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls4, another_subclass4)
        cached_subclass4_vs_another_subclass4_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert issubclass(cls4, Root)
        cached_subclass4_vs_root_ns += time.perf_counter_ns() - start

        start = time.perf_counter_ns()
        assert not issubclass(cls4, Class1)
        cached_subclass4_vs_class1_ns += time.perf_counter_ns() - start

    print("Completed compairing classes.")
    print("Consumed memory, Mb:", psutil.Process(os.getpid()).memory_info().rss / 1024 ** 2)

    total_ns = sum([
        cached_subclass1_vs_root_ns,
        cached_subclass1_vs_another_subclass1_ns,
        cached_subclass1_vs_class1_ns,
        cached_subclass1_vs_class2_ns,
        cached_subclass3_vs_class3_ns,
        cached_subclass3_vs_another_subclass3_ns,
        cached_subclass3_vs_root_ns,
        cached_subclass3_vs_class1_ns,
        cached_subclass4_vs_class4_ns,
        cached_subclass4_vs_another_subclass4_ns,
        cached_subclass4_vs_root_ns,
        cached_subclass4_vs_class1_ns,
        new_subclass1_vs_class1_ns,
        new_subclass1_vs_another_subclass1_ns,
        new_subclass1_vs_root_ns,
        new_subclass1_vs_class2_ns,
        new_subclass3_vs_class3_ns,
        new_subclass3_vs_another_subclass3_ns,
        new_subclass3_vs_root_ns,
        new_subclass3_vs_class1_ns,
        new_subclass4_vs_class4_ns,
        new_subclass4_vs_another_subclass4_ns,
        new_subclass4_vs_root_ns,
        new_subclass4_vs_class1_ns,
    ])
    print("Total, sec:", total_ns / 10**9)

    print("issubclass(cached class, parent), us:", cached_subclass1_vs_root_ns / iters / 1000)
    print("issubclass(cached class, sibling), us:", cached_subclass1_vs_another_subclass1_ns / iters / 1000)
    print("issubclass(cached class, grandparent), us:", cached_subclass1_vs_class1_ns / iters / 1000)
    print("issubclass(cached class, cousin), us:", cached_subclass1_vs_class2_ns / iters / 1000)

    print("issubclass(cached class, parent via .register()), us:", cached_subclass3_vs_class3_ns / iters / 1000)
    print("issubclass(cached class, sibling via .register()), us:", cached_subclass3_vs_another_subclass3_ns / iters / 1000)
    print("issubclass(cached class, grandparent via .register()), us:", cached_subclass3_vs_root_ns / iters / 1000)
    print("issubclass(cached class, cousin via .register()), us:", cached_subclass3_vs_class1_ns / iters / 1000)

    print("issubclass(cached class, parent via __subclasses__), us:", cached_subclass4_vs_class4_ns / iters / 1000)
    print("issubclass(cached class, sibling via __subclasses__), us:", cached_subclass4_vs_another_subclass4_ns / iters / 1000)
    print("issubclass(cached class, grandparent via __subclasses__), us:", cached_subclass4_vs_root_ns / iters / 1000)
    print("issubclass(cached class, cousin via __subclasses__), us:", cached_subclass4_vs_class1_ns / iters / 1000)

    print("issubclass(new class, parent), us:", new_subclass1_vs_class1_ns / len(nested_subclass1) / 1000)
    print("issubclass(new class, sibling), us:", new_subclass1_vs_another_subclass1_ns / len(nested_subclass1) / 1000)
    print("issubclass(new class, grandparent), us:", new_subclass1_vs_root_ns / len(nested_subclass1) / 1000)
    print("issubclass(new class, cousin), us:", new_subclass1_vs_class2_ns / len(nested_subclass1) / 1000)

    print("issubclass(new class, parent via .register()), us:", new_subclass3_vs_class3_ns / len(nested_subclass3) / 1000)
    print("issubclass(new class, sibling via .register()), us:", new_subclass3_vs_another_subclass3_ns / len(nested_subclass3) / 1000)
    print("issubclass(new class, grandparent via .register()), us:", new_subclass3_vs_root_ns / len(nested_subclass3) / 1000)
    print("issubclass(new class, cousin via .register()), us:", new_subclass3_vs_class1_ns / len(nested_subclass3) / 1000)

    print("issubclass(new class, parent via __subclasses__), us:", new_subclass4_vs_class4_ns / len(nested_subclass4) / 1000)
    print("issubclass(new class, sibling via __subclasses__), us:", new_subclass4_vs_another_subclass4_ns / len(nested_subclass4) / 1000)
    print("issubclass(new class, grandparent via __subclasses__), us:", new_subclass4_vs_root_ns / len(nested_subclass4) / 1000)
    print("issubclass(new class, cousin via __subclasses__), us:", new_subclass4_vs_class1_ns / len(nested_subclass4) / 1000)

if __name__ == "__main__":
    test_issubclass_performance()

For 8k nested subclasses:

Impl Memory before, MB Memory after, MB
_abc 4365.906 42.313
_py_abc 2839.828 48.434
Impl Total time before, seconds Total time after, seconds
_abc 484.174 29.418
_py_abc 189.669 91.173
Check Impl us per check, before us per check, after Impl us per check, before us per check, after
isinstance(cached class, parent) _abc 0.315 0.344 _py_abc 0.614 0.518
isinstance(cached class, sibling) _abc 0.317 0.342 _py_abc 1.074 0.867
isinstance(cached class, grandparent) _abc 0.319 0.334 _py_abc 0.642 0.525
isinstance(cached class, cousin) _abc 0.302 0.327 _py_abc 1.034 0.868
isinstance(cached class, parent via .register()) _abc 1.095 0.663 _py_abc 0.621 0.620
isinstance(cached class, sibling via .register()) _abc 0.272 0.183 _py_abc 0.176 0.166
isinstance(cached class, grandparent via .register()) _abc 0.599 0.376 _py_abc 0.573 0.605
isinstance(cached class, cousin via .register()) _abc 0.632 0.413 _py_abc 0.988 1.071
isinstance(cached class, parent via __subclasses__) _abc 0.360 0.352 _py_abc 0.583 0.513
isinstance(cached class, sibling via __subclasses__) _abc 0.170 0.158 _py_abc 0.177 0.137
isinstance(cached class, grandparent via __subclasses__) _abc 0.350 0.326 _py_abc 0.557 0.480
isinstance(cached class, cousin via __subclasses__) _abc 0.375 0.351 _py_abc 0.959 0.888
isinstance(new class, parent) _abc 4.319 5.937 _py_abc 8.072 5.913
isinstance(new class, sibling) _abc 3.021 4.142 _py_abc 6.139 5.906
isinstance(new class, grandparent) _abc 1.771 2.091 _py_abc 4.068 3.269
isinstance(new class, cousin) _abc 10_074.456 2_171.508 _py_abc 8_975.521 4_839.700
isinstance(new class, parent via .register()) _abc 2.490 5.037 _py_abc 307.863 263.306
isinstance(new class, sibling via .register()) _abc 0.731 0.717 _py_abc 0.954 0.840
isinstance(new class, grandparent via .register()) _abc 78_883.538 3_892.012 _py_abc 30_999.150 10_110.654
isinstance(new class, cousin via .register()) _abc 3.770 1_965.657 _py_abc 4.778 4_995.907
isinstance(new class, parent via __subclasses__) _abc 48.480 233.732 _py_abc 110.832 170.838
isinstance(new class, sibling via __subclasses__) _abc 0.841 1.155 _py_abc 0.774 0.589
isinstance(new class, grandparent via __subclasses__) _abc 153_055.472 4_369.760 _py_abc 54_403.267 9_811.822
isinstance(new class, cousin via __subclasses__) _abc 3.035 2_053.119 _py_abc 5.259 4_685.862
Check Impl us per check, before us per check, after Impl us per check, before us per check, after
issubclass(cached class, parent) _abc 0.316 0.295 _py_abc 0.546 0.503
issubclass(cached class, sibling) _abc 0.302 0.289 _py_abc 0.909 0.841
issubclass(cached class, grandparent) _abc 0.335 0.292 _py_abc 0.571 0.526
issubclass(cached class, cousin) _abc 0.299 0.289 _py_abc 0.889 0.839
issubclass(cached class, parent via .register()) _abc 0.594 0.343 _py_abc 0.586 0.515
issubclass(cached class, sibling via .register()) _abc 0.224 0.121 _py_abc 0.141 0.125
issubclass(cached class, grandparent via .register()) _abc 0.456 0.287 _py_abc 0.548 0.495
issubclass(cached class, cousin via .register()) _abc 0.496 0.291 _py_abc 0.884 0.876
issubclass(cached class, parent via __subclasses__) _abc 0.459 0.354 _py_abc 0.588 0.538
issubclass(cached class, sibling via __subclasses__) _abc 0.185 0.141 _py_abc 0.150 0.129
issubclass(cached class, grandparent via __subclasses__) _abc 0.453 0.320 _py_abc 0.554 0.508
issubclass(cached class, cousin via __subclasses__) _abc 0.452 0.339 _py_abc 0.945 0.897
issubclass(new class, parent) _abc 2.571 2.586 _py_abc 3.999 3.313
issubclass(new class, sibling) _abc 2.325 2.517 _py_abc 4.007 3.135
issubclass(new class, grandparent) _abc 1.262 1.318 _py_abc 2.790 2.441
issubclass(new class, cousin) _abc 10_104.771 1_680.304 _py_abc 8_350.500 14_576.658
issubclass(new class, parent via .register()) _abc 0.993 3.510 _py_abc 313.716 266.526
issubclass(new class, sibling via .register()) _abc 0.291 0.329 _py_abc 0.317 0.345
issubclass(new class, grandparent via .register()) _abc 71_022.606 3_882.094 _py_abc 32_358.990 39_985.259
issubclass(new class, cousin via .register()) _abc 1.753 1_984.566 _py_abc 2.398 14_927.480
issubclass(new class, parent via __subclasses__) _abc 61.173 200.559 _py_abc 95.513 169.280
issubclass(new class, sibling via __subclasses__) _abc 0.506 0.463 _py_abc 0.274 0.306
issubclass(new class, grandparent via __subclasses__) _abc 164_455.899 3_411.897 _py_abc 49_661.247 310_012.451
issubclass(new class, cousin via __subclasses__) _abc 2.554 1_586.308 _py_abc 2.196 14_798.255

@bedevere-app
Copy link

bedevere-app bot commented Mar 30, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Mar 31, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

3 similar comments
@bedevere-app
Copy link

bedevere-app bot commented Mar 31, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Mar 31, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Mar 31, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@python-cla-bot
Copy link

python-cla-bot bot commented Apr 6, 2025

All commit authors signed the Contributor License Agreement.

CLA signed

@bedevere-app
Copy link

bedevere-app bot commented Apr 21, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Signed-off-by: Martynov Maxim <martinov_m_s_@mail.ru>
Signed-off-by: Martynov Maxim <martinov_m_s_@mail.ru>
Signed-off-by: Martynov Maxim <martinov_m_s_@mail.ru>
Signed-off-by: Martynov Maxim <martinov_m_s_@mail.ru>
Signed-off-by: Martynov Maxim <martinov_m_s_@mail.ru>
@dolfinus dolfinus force-pushed the improvement/ABCMeta_subclasscheck branch from abf4bfe to b7603e0 Compare April 21, 2025 11:03
@bedevere-app
Copy link

bedevere-app bot commented Apr 21, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Apr 21, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Apr 21, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@dolfinus dolfinus changed the title gh-92810: Avoid O(n^2) complexity in ABCMeta.__subclasscheck__ gh-92810: Reduce memory usage by ABCMeta.__subclasscheck__ Apr 23, 2025
@bedevere-app
Copy link

bedevere-app bot commented Jun 13, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@bedevere-app
Copy link

bedevere-app bot commented Jun 13, 2025

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

@dolfinus dolfinus requested a review from picnixz June 22, 2025 16:42
@Viicos
Copy link
Contributor

Viicos commented Jul 1, 2025

Do you happen to know this will play with #119719?

@dolfinus
Copy link
Author

dolfinus commented Jul 1, 2025

Do you happen to know this will play with #119719?

No, and I don't like solution of #119719 at all

@dolfinus
Copy link
Author

dolfinus commented Jul 18, 2025

@picnixz Could you please take a look on this PR?

@picnixz
Copy link
Member

picnixz commented Aug 4, 2025

Did the improvements in subclass checks actually introduced improvements in isinstance checks right? (because your benchmarks are all with respect to isinstance, not issubclass). If not, please update the benchmarks to indicate the improvements of issubclass itself.

@dolfinus
Copy link
Author

dolfinus commented Aug 4, 2025

because your benchmarks are all with respect to isinstance, not issubclass

This is because in real applications isinstance is much more abundant than issubclass. For example, pydantic checks that object is already an instance of annotated class, and skips validation in this case.

I can add both cases to benchmark, if required.

@picnixz
Copy link
Member

picnixz commented Aug 4, 2025

Yes, please add both benchmarks.

@dolfinus dolfinus requested a review from AA-Turner as a code owner August 7, 2025 11:38
@dolfinus
Copy link
Author

dolfinus commented Aug 7, 2025

Added scripts and microbenchmark results to PR description

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants