Repository: scikit-hep/awkward

Issue:
Checking isinstance of a Protocol is slow
### Version of Awkward Array

2.0.2

### Description and code to reproduce

In a profiling run of a coffea processor with awkward2 (eager mode) I found that 20% of the time was spent in the following line:
https://github.com/scikit-hep/awkward/blob/7e6f504c3cb0310cdbe0be7b5d662722ee73aaa7/src/awkward/contents/content.py#L94
![image](https://user-images.githubusercontent.com/6587412/209037377-c6ad3e62-7349-470c-a340-d0dd223f0a90.png)
This instance check would normally be very fast but I suspect because the type is a `@runtime_checkable` protocol, it is doing more work.
https://github.com/scikit-hep/awkward/blob/7e6f504c3cb0310cdbe0be7b5d662722ee73aaa7/src/awkward/_backends.py#L42-L45

Perhaps there is a way to have it first check the class `__mro__` and then fall back to the protocol?

If this time is removed from the profile, the remaining time is in line with what I get running the same processor in awkward 1.


Discussion:

Resolving patch:
diff --git a/src/awkward/_backends.py b/src/awkward/_backends.py
--- a/src/awkward/_backends.py
+++ b/src/awkward/_backends.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from abc import abstractmethod
+from abc import ABC, abstractmethod
 
 import awkward_cpp
 
@@ -18,18 +18,7 @@
     nplike_of,
 )
 from awkward._typetracer import NoKernel, TypeTracer
-from awkward.typing import (
-    Any,
-    Callable,
-    Final,
-    Protocol,
-    Self,
-    Tuple,
-    TypeAlias,
-    TypeVar,
-    Unpack,
-    runtime_checkable,
-)
+from awkward.typing import Callable, Final, Tuple, TypeAlias, TypeVar, Unpack
 
 np = NumpyMetadata.instance()
 
@@ -39,8 +28,7 @@
 KernelType: TypeAlias = Callable[..., None]
 
 
-@runtime_checkable
-class Backend(Protocol[T]):
+class Backend(Singleton, ABC):
     name: str
 
     @property
@@ -53,16 +41,11 @@ def nplike(self) -> NumpyLike:
     def index_nplike(self) -> NumpyLike:
         raise ak._errors.wrap_error(NotImplementedError)
 
-    @classmethod
-    @abstractmethod
-    def instance(cls) -> Self:
-        raise ak._errors.wrap_error(NotImplementedError)
-
     def __getitem__(self, key: KernelKeyType) -> KernelType:
         raise ak._errors.wrap_error(NotImplementedError)
 
 
-class NumpyBackend(Singleton, Backend[Any]):
+class NumpyBackend(Backend):
     name: Final[str] = "cpu"
 
     _numpy: Numpy
@@ -82,7 +65,7 @@ def __getitem__(self, index: KernelKeyType) -> NumpyKernel:
         return NumpyKernel(awkward_cpp.cpu_kernels.kernel[index], index)
 
 
-class CupyBackend(Singleton, Backend[Any]):
+class CupyBackend(Backend):
     name: Final[str] = "cuda"
 
     _cupy: Cupy
@@ -112,7 +95,7 @@ def __getitem__(self, index: KernelKeyType) -> CupyKernel | NumpyKernel:
             )
 
 
-class JaxBackend(Singleton, Backend[Any]):
+class JaxBackend(Backend):
     name: Final[str] = "jax"
 
     _jax: Jax
@@ -135,7 +118,7 @@ def __getitem__(self, index: KernelKeyType) -> JaxKernel:
         return JaxKernel(awkward_cpp.cpu_kernels.kernel[index], index)
 
 
-class TypeTracerBackend(Singleton, Backend[Any]):
+class TypeTracerBackend(Backend):
     name: Final[str] = "typetracer"
 
     _typetracer: TypeTracer


Test patch: