Pickle raw tuples in FileData cache
diff --git a/CHANGES.md b/CHANGES.md
index 1334efefe7..9fa14f3ebc 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -32,6 +32,9 @@
 
 <!-- Changes that improve Black's performance. -->
 
+- Store raw tuples instead of NamedTuples in Black's cache, improving performance and
+  decreasing the size of the cache (#3877)
+
 ### Output
 
 <!-- Changes to Black's terminal output and error messages -->
diff --git a/src/black/cache.py b/src/black/cache.py
index ff15da2a94..77f66cc34a 100644
--- a/src/black/cache.py
+++ b/src/black/cache.py
@@ -67,7 +67,8 @@ def read(cls, mode: Mode) -> Self:
 
         with cache_file.open("rb") as fobj:
             try:
-                file_data: Dict[str, FileData] = pickle.load(fobj)
+                data: Dict[str, Tuple[float, int, str]] = pickle.load(fobj)
+                file_data = {k: FileData(*v) for k, v in data.items()}
             except (pickle.UnpicklingError, ValueError, IndexError):
                 return cls(mode, cache_file)
 
@@ -129,7 +130,12 @@ def write(self, sources: Iterable[Path]) -> None:
             with tempfile.NamedTemporaryFile(
                 dir=str(self.cache_file.parent), delete=False
             ) as f:
-                pickle.dump(self.file_data, f, protocol=4)
+                # We store raw tuples in the cache because pickling NamedTuples
+                # doesn't work with mypyc on Python 3.8, and because it's faster.
+                data: Dict[str, Tuple[float, int, str]] = {
+                    k: (*v,) for k, v in self.file_data.items()
+                }
+                pickle.dump(data, f, protocol=4)
             os.replace(f.name, self.cache_file)
         except OSError:
             pass
