Path: csiph.com!eternal-september.org!reader02.eternal-september.org!.POSTED!not-for-mail
From: Tim Rentsch
Newsgroups: comp.lang.c++
Subject: Re: Constexpr evaluation of heavy stuff
Date: Thu, 19 May 2022 08:53:06 -0700
Organization: A noiseless patient Spider
Lines: 99
Message-ID: <86tu9l1pul.fsf@linuxsc.com>
References:
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii
Injection-Info: reader02.eternal-september.org; posting-host="2a3388073214779a12c7a9c38773b260"; logging-data="19239"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX196cZSTikRQhmFNPITljy1I5UsCOckaMWg="
User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux)
Cancel-Lock: sha1:uLfW0HNc+In/oxs7GNyQYFYErsM= sha1:ZbQW7XQDRHPOAcMKWPrSh6vZt+g=
Xref: csiph.com comp.lang.c++:84186
Andrey Tarasevich writes:
> Hello
>
> Just for the sake of experiment I tried this
>
> #include
>
> template constexpr auto collect_primes()
> {
> std::array primes = { 2, 3 };
> std::size_t n_primes = 2;
>
> for (unsigned i = 0; n_primes < N; ++i)
> {
> unsigned value = 6u * (i / 2u + 1u) + i % 2u * 2u - 1;
>
> std::size_t i_prime = 0;
> for (; i_prime < n_primes; ++i_prime)
> if (value % primes[i_prime] == 0)
> break;
>
> if (i_prime == n_primes)
> primes[n_primes++] = value;
> }
>
> return primes;
> }
>
> int main()
> {
> constexpr auto primes = collect_primes<78498>();
> ...
> }
>
> Given the above code GCC will [attempt to] build the table of primes
> at compile time. Command line options like
>
> -fconstexpr-ops-limit=10000000000
>
> are needed to prevent it from aborting this overly heavy compile-time
> evaluation prematurely. And this compile-time evaluation takes hours
> and hours and hours and... well... honestly, I haven't been able to
> see it to completion.
>
> If I get rid of the `constexpr`, i.e. switch to regular run-time
> evaluation, the above table will be built in about 15 seconds on the
> same machine.
>
> Reducing the table size to 10000 results in 8 minute `constexpr`
> evaluation vs. 0.25 second run-time table generation.
>
> Granted, it is naive to expect the same efficiency from compile-time
> constexpr` evaluation as we expect from `-O3` run-time code. They
> can't just paste the `constexpr` code into a temp file, compile it
> with `-O3` and run it to obtain the result (can they?). The compiler
> will apparently have to "pseudo-run" the code inside an "abstract
> virtual C++ machine" of sorts... But the actual difference in
> performance is still rather startling. What could be the primary
> reason behind `constexpr` evaluation being so much slower? Is it just
> a quality of implementation issue? Or is there a bunch of fundamental
> reasons it will always be slow? I see that GCC consumes huge amounts
> of memory wile compiling this code... Is it trying to fully unwrap the
> cycle/logic, which could be the reason for massive memory consumption
> and subsequent slowdown once swapping begins?
I don't have answers for your questions, but I did play around
with this a little bit, enough to offer some ideas for what they
might be worth. Disclaimer: my version of gcc is almost
certainly less advanced than yours, so that may have affected my
observations.
One: I think -O0 is a better basis than -O3 for comparing a
constexpr version to a non-constexpr version. Probably gcc
evaluates constexpr expressions using a very straightforward
interpretation than an optimized interpretation.
Two: In evaluating constexpr code, std::array is much more
expensive than just a plain array (eg, unsigned primes[N];).
I measured the time ratio between them at about 6.7.
Three: Apparently the amount of memory required for std::array
is also significantly more than a plain array. I didn't try to
measure this but there were other indications in the respective
compilation behaviors.
Four: Changing the source so a plain array could be used instead
of a std::array, and also a somewhat different method of testing
for primality, I saw a time ratio (between a compile-time version
and run-time only version) of about 125-150 to 1. That is on a
much smaller interval than the original above because trying to
run the full interval made the compiler go belly up.
Five: Related to the last point, I suspect there is a knee in
the curve for compile-time evaluation, perhaps because the array
of primes is a local variable, which implies that a very large
stack frame may be needed for the full interval.
Just some ideas for possible directions to explore...