diff --git a/testsuite/driver/testlib.py b/testsuite/driver/testlib.py
index 7ba54da41589db73078131d37f83b6a98b6fda89..9f4f811a652ce4f2572588ccd0026a479c93ce69 100644
--- a/testsuite/driver/testlib.py
+++ b/testsuite/driver/testlib.py
@@ -748,7 +748,7 @@ def find_so(lib):
 def find_non_inplace_so(lib):
     return _find_so(lib,path_from_ghcPkg(lib, "dynamic-library-dirs"),False)
 
-# Define the a generic stat test, which computes the statistic by calling the function
+# Define a generic stat test, which computes the statistic by calling the function
 # given as the third argument.
 def collect_generic_stat ( metric, deviation, get_stat ):
     return collect_generic_stats ( { metric: { 'deviation': deviation, 'current': get_stat } } )
@@ -1766,6 +1766,9 @@ async def multi_compile( name, way, top_mod, extra_mods, extra_hc_opts ):
 async def multi_compile_fail( name, way, top_mod, extra_mods, extra_hc_opts ):
     return await do_compile( name, way, True, top_mod, extra_mods, [], extra_hc_opts)
 
+async def make_depend( name, way, mods, extra_hc_opts ):
+    return await do_compile( name, way, False,  ' '.join(mods), [], [], extra_hc_opts, mode = '-M')
+
 async def do_compile(name: TestName,
                way: WayName,
                should_fail: bool,
@@ -2044,7 +2047,9 @@ async def simple_build(name: Union[TestName, str],
                  addsuf: bool,
                  backpack: bool = False,
                  suppress_stdout: bool = False,
-                 filter_with: str = '') -> Any:
+                 filter_with: str = '',
+                 # Override auto-detection of whether to use --make or -c etc.
+                 mode: Optional[str] = None) -> Any:
     opts = getTestOpts()
 
     # Redirect stdout and stderr to the same file
@@ -2061,7 +2066,9 @@ async def simple_build(name: Union[TestName, str],
     else:
         srcname = Path(name)
 
-    if top_mod is not None:
+    if mode is not None:
+        to_do = mode
+    elif top_mod is not None:
         to_do = '--make '
         if link:
             to_do = to_do + '-o ' + name
diff --git a/testsuite/tests/perf/compiler/large-project/all.T b/testsuite/tests/perf/compiler/large-project/all.T
new file mode 100644
index 0000000000000000000000000000000000000000..2cd643674a1d9df4216baac24ee1ddd8dab5d883
--- /dev/null
+++ b/testsuite/tests/perf/compiler/large-project/all.T
@@ -0,0 +1,21 @@
+# These tests are supposed to prevent severe performance regressions when
+# operating on projects with unusually large numbers of modules.
+# Inefficient algorithms whose complexity depends on the number of modules won't
+# be noticed when running the test suite or compiling medium size projects.
+
+def large_project_makedepend(num):
+    return test(
+        f'large-project-makedepend-{num}',
+        [
+            collect_compiler_stats('bytes allocated', 1),
+            pre_cmd(f'./large-project.sh {num}'),
+            extra_files(['large-project.sh']),
+            ignore_stderr,
+            windows_skip,
+        ],
+        make_depend,
+        [[f'Mod{i:04d}' for i in range(0, num - 1)], ''],
+        )
+
+large_project_makedepend(4000)
+large_project_makedepend(10000)
diff --git a/testsuite/tests/perf/compiler/large-project/large-project.sh b/testsuite/tests/perf/compiler/large-project/large-project.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ad65655f0bafe96807b43bced5f6c299fb174c21
--- /dev/null
+++ b/testsuite/tests/perf/compiler/large-project/large-project.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -eu
+
+total="$1"
+
+for ((i = 1; i < $total; i++))
+do
+  # Important to write directly to variables with `-v`, otherwise the script takes a second per 1000 modules
+  printf -v j "%04d" "$i"
+  printf -v k "%04d" "$(($i - 1))"
+  echo -e "module Mod${j} where
+import Mod${k}
+f_${j} :: ()
+f_${j} = f_$k" > "Mod${j}.hs"
+done
+
+echo "
+module Mod0000 where
+f_0000 :: ()
+f_0000 = ()
+" > "Mod0000.hs"