By default every play begins with an implicit setup task that collects several hundred facts from each host, including network interfaces, mounts, and hardware details. On a large inventory, or against hosts reached over a slow link, this dominates runtime for plays whose tasks use none of it.
Set gather_facts: false on plays that do not need facts. When only a few are needed, gather_subset limits collection, for example gather_subset: ['!all', '!min', 'network']. The !all,!min prefix is required because subsets are additive to the default set otherwise.
For repeated runs, fact caching stores results between plays. Setting fact_caching = jsonfile with a fact_caching_connection directory and a fact_caching_timeout in ansible.cfg lets later runs skip collection entirely for hosts whose cache is fresh. A play that sets gather_facts: false can still access cached facts, which is the combination that makes large inventories usable. Note that a task using become does not need facts, but any template referencing ansible_hostname or ansible_default_ipv4 does, and the failure is an undefined-variable error rather than an obvious explanation.