Bug 115639 - Large variations in compilation times involving static_assert
Summary: Large variations in compilation times involving static_assert
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 15.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: compile-time-hog
Depends on:
Blocks:
 
Reported: 2024-06-25 11:13 UTC by Paul Keir
Modified: 2024-06-26 20:07 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2024-06-26 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Paul Keir 2024-06-25 11:13:01 UTC
I often want to be sure that an expression is evaluated at compile time, while also checking that it produces the expected value. With a `constexpr` function called `big_calc`, expected to return zero, I might either use:

static_assert(0==big_calc());    // (1)

...or:

constexpr int ret = big_calc();  // (2)
static_assert(0==ret);

Surprisingly, the first of these (1) takes around 76% longer than the second (2) to compile.

constexpr int big_calc()
{
  long i = 1, count = 0, n = (1<<22);

  for (; i < n; i++)
    count = count + i;

  bool b = count == (i * (i - 1)) / 2;

  return 0;
}

This is true even when `big_calc` involves more calculations. I am using Ubuntu 24.04 with an Intel i9-13900K, and g++ (GCC) 15.0.0 20240623 (experimental). With `n` of `big_calc` set at `(1<<22)` (as above) average compilation times are 6.5 secs. for approach (1); and 3.7 secs. for approach (2).

With `n` of `big_calc` set at `(1<<24)` average compilation times are 28.3 secs. for approach (1); and 16.2 secs. for approach (2).

time /opt/gcc-latest/bin/g++ -std=c++20 performance-bug-double-constant-eval.cpp -fconstexpr-ops-limit=$((2**31-1)) -fconstexpr-loop-limit=$((2**31-1))

Other approaches to invoke `big_calc` (e.g. SFINAE/Concepts class/function templates) are also slow; and have the performance profile of method (1).

Perhaps the constant expression is being evaluated twice.

It does look odd that the `big_calc` function returns zero. This was reduced from a larger problem. Curiously, if `big_calc` instead returns a boolean (i.e. `b`), and the `0==` part of each constant expression is removed, the issue disappears.

This is reminiscent of a recently resolved Clang issue (https://github.com/llvm/llvm-project/issues/92924), though with 2 notable differences: firstly, with GCC the problem only arises when the `big_calc` function is actually invoked; and secondly, the `-Wno-invalid-constexpr` flag makes no difference to the performance with GCC.
Comment 1 Marek Polacek 2024-06-26 17:53:13 UTC
Reproduced:

$ time ./cc1plus -quiet 115639.C -fconstexpr-ops-limit=$((2**31-1)) -fconstexpr-loop-limit=$((2**31-1))

real	0m46.072s
user	0m45.541s
sys	0m0.273s

vs.

$ time ./cc1plus -quiet 115639.C -fconstexpr-ops-limit=$((2**31-1)) -fconstexpr-loop-limit=$((2**31-1))

real	0m25.545s
user	0m25.125s
sys	0m0.285s
Comment 2 Marek Polacek 2024-06-26 20:07:36 UTC
With

  static_assert(0==big_calc());    // (1)

we evaluate the big_calc call twice.  Once, while parsing the static_assert:

#0  cxx_eval_call_expression (ctx=0x7fffffffc3e0, t=<call_expr 0x7fffea3c5b40>, lval=vc_prvalue, 
    non_constant_p=0x7fffffffc50f, overflow_p=0x7fffffffc50e) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:2801
#1  0x0000000000e45583 in cxx_eval_constant_expression (ctx=0x7fffffffc3e0, t=<call_expr 0x7fffea3c5b40>, 
    lval=vc_prvalue, non_constant_p=0x7fffffffc50f, overflow_p=0x7fffffffc50e, jump_target=0x0)
    at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:7554
#2  0x0000000000e4b6b8 in cxx_eval_outermost_constant_expr (t=<call_expr 0x7fffea3c5b40>, allow_non_constant=true, 
    strict=true, manifestly_const_eval=mce_value::mce_unknown, constexpr_dtor=false, object=<tree 0x0>)
    at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:8852
#3  0x0000000000e4c8ee in maybe_constant_value (t=<call_expr 0x7fffea3c5b40>, decl=<tree 0x0>, 
    manifestly_const_eval=mce_value::mce_unknown) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:9161
#4  0x0000000000e95c58 in cp_fully_fold (x=<call_expr 0x7fffea3c5b40>, manifestly_const_eval=mce_value::mce_unknown)
    at /home/mpolacek/src/gcc/gcc/cp/cp-gimplify.cc:2852
#5  0x0000000000e95dc7 in cp_fully_fold (x=<call_expr 0x7fffea3c5b40>)
    at /home/mpolacek/src/gcc/gcc/cp/cp-gimplify.cc:2871
#6  0x000000000122ee78 in cp_build_binary_op (location=..., code=EQ_EXPR, orig_op0=<non_lvalue_expr 0x7fffea3cd540>, 
    orig_op1=<call_expr 0x7fffea3c5b40>, complain=3) at /home/mpolacek/src/gcc/gcc/cp/typeck.cc:6669
#7  0x0000000000dc774e in build_new_op (loc=..., code=EQ_EXPR, flags=1, arg1=<non_lvalue_expr 0x7fffea3cd540>, 
    arg2=<call_expr 0x7fffea3c5b40>, arg3=<tree 0x0>, lookups=<tree 0x0>, overload=0x7fffffffcc30, complain=3)
    at /home/mpolacek/src/gcc/gcc/cp/call.cc:7516

and the second time while in finish_static_assert:

#0  cxx_eval_call_expression (ctx=0x7fffffffce20, t=<call_expr 0x7fffea3c5b40>, lval=vc_prvalue, 
    non_constant_p=0x7fffffffcf4f, overflow_p=0x7fffffffcf4e) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:2801
#1  0x0000000000e45583 in cxx_eval_constant_expression (ctx=0x7fffffffce20, t=<call_expr 0x7fffea3c5b40>, 
    lval=vc_prvalue, non_constant_p=0x7fffffffcf4f, overflow_p=0x7fffffffcf4e, jump_target=0x0)
    at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:7554
#2  0x0000000000e35b6b in cxx_eval_binary_expression (ctx=0x7fffffffce20, t=<eq_expr 0x7fffea3cc5a0>, lval=vc_prvalue, 
    non_constant_p=0x7fffffffcf4f, overflow_p=0x7fffffffcf4e) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:3820
#3  0x0000000000e46f1f in cxx_eval_constant_expression (ctx=0x7fffffffce20, t=<eq_expr 0x7fffea3cc5a0>, lval=vc_prvalue, 
    non_constant_p=0x7fffffffcf4f, overflow_p=0x7fffffffcf4e, jump_target=0x0)
    at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:7966
#4  0x0000000000e4b6b8 in cxx_eval_outermost_constant_expr (t=<eq_expr 0x7fffea3cc5a0>, allow_non_constant=true, 
    strict=true, manifestly_const_eval=mce_value::mce_true, constexpr_dtor=false, object=<tree 0x0>)
    at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:8852
#5  0x0000000000e4c7c2 in maybe_constant_value (t=<eq_expr 0x7fffea3cc5a0>, decl=<tree 0x0>, 
    manifestly_const_eval=mce_value::mce_true) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:9140
#6  0x0000000000e4ce02 in fold_non_dependent_expr (t=<eq_expr 0x7fffea3cc5a0>, complain=3, manifestly_const_eval=true, 
    object=<tree 0x0>) at /home/mpolacek/src/gcc/gcc/cp/constexpr.cc:9280
#7  0x00000000011dd851 in finish_static_assert (condition=<eq_expr 0x7fffea3cc5a0>, message=<string_cst 0x7fffcde811f8>, 
    location=2147483661, member_p=false, show_expr_p=false) at /home/mpolacek/src/gcc/gcc/cp/semantics.cc:11887


The second time around, we're not finding the call in constexpr_call_table.