Repository: aio-libs/aiohttp
Issue category: Performance Issue

Payload registry has to do a linear search to find payloads
https://github.com/aio-libs/aiohttp/blob/21f5f92a755dc5ac7225b5e76f561553cf86565e/aiohttp/payload.py#L97

There is a note that its inefficent.

We might be able to clean it up by giving each payload a key as part of the base class and then doing a dict lookup instead


Functions to edit:
aiohttp/payload.py:PayloadRegistry.__init__
aiohttp/payload.py:PayloadRegistry.get
aiohttp/payload.py:PayloadRegistry.register
aiohttp/payload.py:Payload.__init__
aiohttp/payload.py:BytesPayload.__init__

Patch:
diff --git a/aiohttp/payload.py b/aiohttp/payload.py
index 27636977774..151f9dd497b 100644
--- a/aiohttp/payload.py
+++ b/aiohttp/payload.py
@@ -101,6 +101,7 @@ def __init__(self) -> None:
         self._first: List[_PayloadRegistryItem] = []
         self._normal: List[_PayloadRegistryItem] = []
         self._last: List[_PayloadRegistryItem] = []
+        self._normal_lookup: Dict[Any, PayloadType] = {}
 
     def get(
         self,
@@ -109,12 +110,20 @@ def get(
         _CHAIN: "Type[chain[_PayloadRegistryItem]]" = chain,
         **kwargs: Any,
     ) -> "Payload":
+        if self._first:
+            for factory, type_ in self._first:
+                if isinstance(data, type_):
+                    return factory(data, *args, **kwargs)
+        # Try the fast lookup first
+        if lookup_factory := self._normal_lookup.get(type(data)):
+            return lookup_factory(data, *args, **kwargs)
+        # Bail early if its already a Payload
         if isinstance(data, Payload):
             return data
-        for factory, type in _CHAIN(self._first, self._normal, self._last):
-            if isinstance(data, type):
+        # Fallback to the slower linear search
+        for factory, type_ in _CHAIN(self._normal, self._last):
+            if isinstance(data, type_):
                 return factory(data, *args, **kwargs)
-
         raise LookupError()
 
     def register(
@@ -124,6 +133,11 @@ def register(
             self._first.append((factory, type))
         elif order is Order.normal:
             self._normal.append((factory, type))
+            if isinstance(type, Iterable):
+                for t in type:
+                    self._normal_lookup[t] = factory
+            else:
+                self._normal_lookup[type] = factory
         elif order is Order.try_last:
             self._last.append((factory, type))
         else:
@@ -159,7 +173,8 @@ def __init__(
             self._headers[hdrs.CONTENT_TYPE] = content_type
         else:
             self._headers[hdrs.CONTENT_TYPE] = self._default_content_type
-        self._headers.update(headers or {})
+        if headers:
+            self._headers.update(headers)
 
     @property
     def size(self) -> Optional[int]:
@@ -228,9 +243,6 @@ class BytesPayload(Payload):
     def __init__(
         self, value: Union[bytes, bytearray, memoryview], *args: Any, **kwargs: Any
     ) -> None:
-        if not isinstance(value, (bytes, bytearray, memoryview)):
-            raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")
-
         if "content_type" not in kwargs:
             kwargs["content_type"] = "application/octet-stream"
 
@@ -238,8 +250,10 @@ def __init__(
 
         if isinstance(value, memoryview):
             self._size = value.nbytes
-        else:
+        elif isinstance(value, (bytes, bytearray)):
             self._size = len(value)
+        else:
+            raise TypeError(f"value argument must be byte-ish, not {type(value)!r}")
 
         if self._size > TOO_LARGE_BYTES_BODY:
             kwargs = {"source": self}