<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/97239>97239</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            Counterintuitive effects of module import location on compilation performance
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          kamrann
      </td>
    </tr>
</table>

<pre>
    I recently experienced an extreme improvement and subsequent regression in compilation performance whilst migrating a library to modules. It turned out the key change had little to do with my gradually encapsulating more into modules, but was down to something that I did by accident. Here's a reduced and much simplified repro.
```
// lots_of_std.h

#include <chrono>
#include <format>
#include <mutex>
#include <thread>
#include <memory>
#include <ranges>
#include <algorithm>
#include <iostream>
#include <functional>
#include <filesystem>
```
```
// pmi_x.ixx

module;

#include "lots_of_std.h"

export module x;
```
```
// maybe_import_x.h

#ifdef import_x_in_gmf
import x;
#endif
```
```
// ip_y_a.ixx

module;

#include "maybe_import_x.h"
#include "lots_of_std.h"

export module y:a;

import x;
```
```
// ip_y_b.ixx

module;

#include "maybe_import_x.h"
#include "lots_of_std.h"

export module y:b;

import x;
import :a;
```
```
<ip_y_c.ixx, ip_y_d.ixx, ip_y_e.ixx, ip_y_f.ixx as above, each importing the preceding partition>
```
```
// pmi_y.ixx

export module y;

export import :a;
export import :b;
export import :c;
export import :d;
export import :e;
export import :f;
```
So a simple module including some heavy headers in its GMF, then a second module with a linear chain of interface partitions each including the same headers in their GMF, importing the first module, and importing the preceding partition. Each partition also conditionally (via preprocessor switch) imports the first module at the top of its GMF, on top of doing so in its purview.

Compilation times from `-ftime-trace` below. Note that in the default (fat BMI) mode, each TU generates two JSON files from the time trace; one appears largely insignificant time-wise, so below numbers are from the significant one only. 

![clang-modules-perf-chart](https://github.com/llvm/llvm-project/assets/2475657/f6a80fff-6860-49c4-bfc6-968d2eec995a)

In addition to the above, compilation time for `pmi_x.ixx` was constant at around 3.8s for all cases.

BMI sizes were as follows (MB):
| | x [default] | x [import-in-gmf] | y partitions [default] | y partitions [import-in-gmf] |
|--------|--------|--------|--------|--------|
| fat | 30 | 30 | 30-32 | 14 |
| thin | 25 | 25 | 10 | 10 |

Observations:
1. Most obviously, including the additional `import x;` statement at the top of the partition GMFs produces a huge improvement in compilation performance. If the import were moved there from the purview then perhaps I might expect some improvement since if I understand right, that would be a semantic change as the declarations would not be passed on to downstream importing partitions. But since it's just being added there as an addition to the import in the purview, this is very counterintuitive to me. When I noticed I'd done this accidentally I wasn't even sure it was legal to import in the GMF. My understanding was that as all code becomes more and more modularized, the goal would be for the GMF to contain fewer and fewer things, so it was super surprising to find that my compilation times got slashed (by 3x in my real use case) by just dumping an additional, seemingly superfluous import statement into the GMF.
2. With the fat BMI, TU compilation time increases with depth of the TU in the DAG of module partitions. This is also kind of surprising, since there is nothing new being added at each level, and each partition only imports its directly preceding partition, so it's not clear why compiling `f` should take any longer than `b`. Note that from looking at the time traces, the increased time is entirely confined within `WriteAST`; the preceding frontend times barely change. Also, though in the fat BMI case there is a small variance in partition BMI sizes in the default (no import in GMF) case, it appeared somewhat arbitrary - the size was not increasing from `a` to `f`.

One other question is why the BMI contains much of anything at all in the above repro since nothing is exported or even declared in the module purviews. Though I'm guessing this must be known and is perhaps just a pending implementation issue.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzMWEFz4zbP_jXKBWONI9lOfPBhnTT7-ZtJe-h29pihSEhiQ5EqSdnW_vp3QEq2HMdvuz29O7srSxAJ4gHwABBzTlYacZMst8ny-Y51vjZ2884ay7S-K4zoNzuwyFF71QMeW7QSNUcBTAMevcUGQTatNXtsUHtgWoDrCod_dXRrsbLonDQapAZumlYq5um2RVsa2zDNEQ61VM5DIyvLvNQVMFCysMz24A00RnQKXQo7D76zGgWYzoOvEd6xB14zXSHUTICS3iukNcLAQfoamh4qy0THFB1fc9a6TkUdjbEIUp8VJNkTFJ2HA3MgzEHTPs406Gt63dfMww6EFFD0wDiXArVP4f_QYpI9OGBgUXQRGgFNx2twsmmVLCUKsNhakybz52T-JVnNh7_xNntJshdQxrs3U745L9J6kAzyXGquOoGQ5E-8tkabJP_lM2FA1N8QNp3H4w2Zry0ycWshNsb2N4SW0Hc3hExVxkpfNzfk0jhvkd0Sl53mFCxM3XpBKnS98zjZ4QO4n2LdNvLtmMrjcYpzDIMk394CP8sufZRl0zfx2Brrh2CC43mff3KghvUFvsmGtng7Xvu_FFjCKH6T-q1qyiiND6f6shy1kOVPaJftW__G_gUeV8c-QfIvYeuT_Av7oPLawn9sU_G_YlPxdzYNDy7N_-925k_BRh5szJ6ixeLiDi_uSroD5oAVZo_0GBmvh7CKHIfQEtsLumuZ9ZLS7-dzq_-I-xUi20-kn2FwJSpui_htkbgtwtui8pYrfjfAIrvjaFMMDcKNagbUyPY9_S_QOqp90jv4-vpCqPsaNa1HbqhSxPWhXFHd08gsFTWpwZRUoNCWjOPZHW5w20khuc2xqHRU52uUdlR46eFSWje6gqRUrv42BlL4hZSe7oEpZ4AMkJGhVQ9J9riXjFa31nB0zlhwB-l5nWTrQYW7OgKwWMu9aYPBZ5yMHh8KE4EdgWw7u5d4SKdB9DRpLrxs0EFpTQPJaj4r6X7mLeOYrOZQoDKHFH41HmNZj3iBwJJ1ypMdJfOwfd3RuRsjzrny7Q-oUKNlHh34g4H___23XyGUoaguWCIbhKgt34LRCKxtkVkHitkKVQ9SU98lS8mZ9uH92UG6oMaZeD7QXVOQM5nF89bTZbSx0apP4ZLM7pPlliumq9nQ2Myo15rxmlmfLJ-T7LH2vnVJPmRsJX3dFSk3TZK9KLUfL7PWmj-R-yR7Yc6hd0n2ki0elqvlQ5K9lCv2OC_LcrZ6XM1nizVfzIqSr2br1aPIEPl6vWRJtp4ebaeBiRgv1FuRPScm4h-8B6Wx5LtzpV7NQ2fGjXaezGcemDWdFpCnjy68z5QCzhy6i8DYvu7AyR_o4IAWif9Ko5Q5OHL065YOmY_gPTwB_TtCstwO4ZAsn8_PYhDPpJ5RAR4k_TQ3rxd-FH-2x0n9bPjzkz_Px6fIpWs-v7zM8iz8ul9c6APqboMgW04v9_PJZYrmb4VDuw-Ocifc7lN4Nc6DKfbSdE71gXUuCGr0PFPk1mn9W83BeeaH-eGCDAIZnSjn6-uLg9Ya6rKp36676nL4uD1ipLCLuw2KQyQ0Zo-Cnk4zbKCWSNIt2pq1DnY0m9Q-DEDcR4afKnaSxhhZwg46TRTsiVQtrYmEzzwcTKcEFBiYv2HaSz7OLswN_MMVsxHa4XVtPC1pKQFFJMQwm8S2eULb5xBLYdudTuTDaPJn52ibMFkJcbKZOoHrlBwQGjhxgCNaIR1IB3u0PXDTUWWS2nfSy30YuhpM4TvBtqNzSxqFdkn2IEAQVYXl49wU6sWOElon2YMH3KMG19FEFgcwhRVTtOnlcb6-vqTw2k9QJqMOAUAiBBc5wAiEArmhKhDmvDCSGTtUa2blDxRDKYbKMHX2DvHIoInUc6M9leISD2jDNvFXGArdQNjDmV3XoiUrWitdiHsDpdQinq3pr0jOQWU8OMVcjYLYqOghP5KtTQ8WmYLOYWA0KkRFHx0puqYNrtSTpAonQWykrlQfT1KqznRuBPCcYmHmHcGM-Zul8J36j1Cbx9L3RNXuipel5haJY2PHIrD19Ziq3_4Y_fT85Ss9HCr8NDi_DVEU-od3QseUE8yCHSF2Y4xKR7EUJnCNh4sgZj7WZIV7VGMjg5ddCtXHU-dBnYOQFrlX_ad97ujNkDSUelxRM3aoR9fR68lqXgbOqkPIePZO4dWDMroKgcE0vVMkq_m0xwgEo4x5Dwb4D42CG4NxhFcMaDtA7aWlpoEbXUqNIgAvg5LvVnr88vs3akzz7YferbRGe9RiCLWCxV0C5aTwRTkTlZquqke_Dc4PMXf2AAPXUF7tmZXhg43UE4zP9fW6j9LTBA5t3XqI5yfKmtgXoQiMeggZbAvpw5ef2dDw_MCQXOSOAZzBuNDbMXKFN6NXLgr_b9QgkRHwV4cunFW64E3aOZgZs9vFzzWmJEfGYKOjKDVaFNqU-A1niM4xKMlBYWwgeraRxyKPoxhXj1kQqTTkQMCcyLGBqkMX2YIyo4lUDe_aHHRszd2pDIX0Z9BipL0wf1BKs8E212F6Jza5WOdrdoeb-4f79cPjap2t7urNvHhYLRargrNFVmRsLeY8FznO14t8Wayz9Z3cZPNsMV_l8yzLl9kqZcWSZcWKr_lC3C-zIlnMsWFSpdQdpsZWd0HlZv2Q5es7xQpULnxJzDLK1SCkyXj5fGc3oaMsusoli7mSzrvzLl56hZunjxUFyxK5dxMeGUJJGc6G7L5V7-86qzY_3eaGE1ObGy3ab7L_BAAA__9sU-uq">