Issue 153431
Summary Quadratic compile times with pack indexing that can be avoided
Labels new issue
Assignees
Reporter ilya-biryukov
    See https://gcc.godbolt.org/z/de4srbec7 
Clang (at head and released versions) will take quadratic time to compile the code below. E.g. the following bechmark clearly shows the trend:
```
$ hyperfine -L n "1000,2000,4000,8000"  './bin/clang++ -std=c++26 -fsyntax-only large_expansions.cc -DELEMENTS_TO_CHECK={n}'
Benchmark 1: ./bin/clang++ -std=c++26 -fsyntax-only large_expansions.cc -DELEMENTS_TO_CHECK=1000
  Time (mean ± σ):     394.8 ms ±  17.4 ms    [User: 331.1 ms, System: 63.5 ms]
  Range (min … max):   367.4 ms … 416.5 ms    10 runs

Benchmark 2: ./bin/clang++ -std=c++26 -fsyntax-only large_expansions.cc -DELEMENTS_TO_CHECK=2000
  Time (mean ± σ):      1.180 s ±  0.024 s    [User: 1.049 s, System: 0.131 s]
  Range (min … max):    1.132 s …  1.221 s    10 runs

Benchmark 3: ./bin/clang++ -std=c++26 -fsyntax-only large_expansions.cc -DELEMENTS_TO_CHECK=4000
  Time (mean ± σ):      4.288 s ±  0.156 s    [User: 3.819 s, System: 0.468 s]
  Range (min … max):    4.109 s …  4.633 s    10 runs

Benchmark 4: ./bin/clang++ -std=c++26 -fsyntax-only large_expansions.cc -DELEMENTS_TO_CHECK=8000
  Time (mean ± σ):     16.062 s ±  0.410 s    [User: 15.048 s, System: 1.011 s]
  Range (min … max):   15.569 s … 17.037 s    10 runs
```

Code:
```cpp
#include <utility>

template <class... T>
struct Types {
    template <int ...I>
    struct Take {
        using type = Types<T...[I]...>;
    };
};

template <int I>
struct X {};
template <class T>
struct MakeXs;
template <int... I>
struct MakeXs<std::integer_sequence<int, I...>> {
    using type = Types<X<I>...>;
};

template <int N>
struct CheckTake {
    using Xs = MakeXs<std::make_integer_sequence<int, N>>::type;

    static constexpr int check() {
        []<int... I>(std::integer_sequence<int, I...>) {
            static_assert(std::is_same_v<typename Xs::template Take<I...>::type,
                                         Types<X<I>...>>);
        }(std::make_integer_sequence<int, N>());

        return 1;
    }
};

static_assert(1 == CheckTake<ELEMENTS_TO_CHECK>::check());
```

gcc seems to fare much better, but I do not have a gcc 15 version on my machine to confirm it is actually linear vs having smaller constant factors.
I believe Clang is quadratic because it proceeds as follows when instantiating `T...[I]...`:
- for each `I`
- create a list of substitutions corresponding to `T...`,
- pick i-th element from the list.
The complexity is linear in the size of `T`.

The standard, however, guarantees that `T` always refers to a pack, so we should be able to pick i-th element in `O(1)`, making the example above perform on `O(n)` rather than `O(n^2)`.
This improvement seems worthwhile as pack indexing is likely to be used in template-metaprogramming-heavy contexts and we should expect similar examples to pop up in the wild.

@mizvekov @cor3ntin any thoughts? Would it be useful to prototype a proof-of-concept that shows the compile-time goes to linear if we have that assumption?
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to