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

RequestInfo is documented to be a namedtuple but its actually a dataclass or attrs
https://docs.aiohttp.org/en/stable/client_reference.html#aiohttp.ClientResponse.request_info

It would be about 2x as fast to create if it were a namedtuple though, and although dataclasses are faster to access its rarely accessed so namedtuple probably is a better choice.  frozen dataclasses are also slower to create https://docs.python.org/3.9/library/dataclasses.html#frozen-instances.... and we create them for every request.
> There is a tiny performance penalty when using frozen=True: [__init__()](https://docs.python.org/3.9/reference/datamodel.html#object.__init__) cannot use simple assignment to initialize fields, and must use [object.__setattr__()](https://docs.python.org/3.9/reference/datamodel.html#object.__setattr__).

Additionally the attrs default isn't used on 3.11/3.10 at all

https://github.com/aio-libs/aiohttp/blob/13dc0200d855bdfec1d4c4f3e1c9a1c66f88f1eb/aiohttp/client_reqrep.py#L110

```diff
diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py
index dad7fe78e..1eeaf7068 100644
--- a/aiohttp/client_reqrep.py
+++ b/aiohttp/client_reqrep.py
@@ -105,16 +105,12 @@ class ContentDisposition:
     filename: Optional[str]
 
 
-@attr.s(auto_attribs=True, frozen=True, slots=True)
-class RequestInfo:
+class RequestInfo(NamedTuple):
     url: URL
     method: str
     headers: "CIMultiDictProxy[str]"
-    real_url: URL = attr.ib()
+    real_url: URL
 
-    @real_url.default
-    def real_url_default(self) -> URL:
-        return self.url
 
 
 class Fingerprint:
@@ -401,7 +397,7 @@ class ClientRequest:
     @property
     def request_info(self) -> RequestInfo:
         headers: CIMultiDictProxy[str] = CIMultiDictProxy(self.headers)
-        return RequestInfo(self.url, self.method, headers, self.original_url)
+        return tuple.__new__(RequestInfo, (self.url, self.method, headers, self.original_url))
 
     def update_host(self, url: URL) -> None:
         """Update destination host, port and connection type (ssl)."""
```


Functions to edit:
aiohttp/client_reqrep.py:RequestInfo
aiohttp/client_reqrep.py:ClientRequest.request_info

Patch:
diff --git a/CHANGES/9692.breaking.rst b/CHANGES/9692.breaking.rst
new file mode 100644
index 00000000000..e0fdae11416
--- /dev/null
+++ b/CHANGES/9692.breaking.rst
@@ -0,0 +1,1 @@
+Changed ``ClientRequest.request_info`` to be a `NamedTuple` to improve client performance -- by :user:`bdraco`.
diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py
index 9c86d935011..59a191d19fe 100644
--- a/aiohttp/client_reqrep.py
+++ b/aiohttp/client_reqrep.py
@@ -106,8 +106,7 @@ class ContentDisposition:
     filename: Optional[str]
 
 
-@dataclasses.dataclass(frozen=True)
-class RequestInfo:
+class RequestInfo(NamedTuple):
     url: URL
     method: str
     headers: "CIMultiDictProxy[str]"
@@ -330,7 +329,11 @@ def port(self) -> Optional[int]:
     @property
     def request_info(self) -> RequestInfo:
         headers: CIMultiDictProxy[str] = CIMultiDictProxy(self.headers)
-        return RequestInfo(self.url, self.method, headers, self.original_url)
+        # These are created on every request, so we use a NamedTuple
+        # for performance reasons.
+        return tuple.__new__(
+            RequestInfo, (self.url, self.method, headers, self.original_url)
+        )
 
     def update_host(self, url: URL) -> None:
         """Update destination host, port and connection type (ssl)."""
diff --git a/docs/client_reference.rst b/docs/client_reference.rst
index f9f8ac28617..dfe93bda0d9 100644
--- a/docs/client_reference.rst
+++ b/docs/client_reference.rst
@@ -1467,7 +1467,7 @@ Response object
 
    .. attribute:: request_info
 
-       A namedtuple with request URL and headers from :class:`~aiohttp.ClientRequest`
+       A :class:`typing.NamedTuple` with request URL and headers from :class:`~aiohttp.ClientRequest`
        object, :class:`aiohttp.RequestInfo` instance.
 
    .. method:: get_encoding()
@@ -1824,7 +1824,7 @@ Utilities
 
 .. class:: RequestInfo()
 
-   A data class with request URL and headers from :class:`~aiohttp.ClientRequest`
+   A :class:`typing.NamedTuple` with request URL and headers from :class:`~aiohttp.ClientRequest`
    object, available as :attr:`ClientResponse.request_info` attribute.
 
    .. attribute:: url