> On 25 Aug 2026, at 18:24, Jan Hubicka <[email protected]> wrote: > >> >> >>> On 25 Aug 2026, at 12:36, Richard Biener <[email protected]> wrote: >>> >>> On Mon, Aug 24, 2026 at 8:40 PM <[email protected]> wrote: >>>> >>>> From: Kyrylo Tkachov <[email protected]> >>>> >>>> branch_prob () cannot place a counter on an abnormal edge. It >>>> represents the flow of a computed goto with fake edges. One fake edge >>>> goes from the entry block to each target. Another goes from the >>>> dispatch block to the exit block. branch_prob () also marks the >>>> abnormal edges as ignored. An ignored edge that is not on the spanning >>>> tree is not instrumented. The graph solver does not infer its count. >>>> Its count therefore remains at the default zero, whatever the program >>>> did. >>>> >>>> compute_branch_probabilities () then turns that zero into a precise >>>> never. A precise never is proof of deadness. >>>> propagate_unlikely_bbs_forward () cannot reach any dispatch target from >>>> the entry block, and determine_unlikely_bbs () zeroes the measured >>>> counts of the whole region. For a threaded interpreter that region is >>>> the entire hot loop. Thus, -fprofile-use reads measured block counts >>>> and then discards them. >>>> >>>> For >>>> >>>> static const void *tab[] = { &&L_ADD, ... }; >>>> goto *tab[*pc++]; >>>> >>>> -fdump-ipa-profile shows 5000 executions for the dispatch block and 1000 >>>> for each handler. The optimized dump then reads >>>> >>>> ;; basic block 5, loop depth 1, count 0 (precise), probably never executed >>>> >>>> for every one of them. >>>> >>>> Record the probability of an ignored edge outside the spanning tree as a >>>> guess. A guessed never is still a zero probability. It does not invent >>>> a split between the dispatch targets. A target with a measured zero >>>> count also stays at zero. The guessed probability stops the unmeasured >>>> edge count from being evidence that the target is dead. The same dump >>>> now reads >>>> >>>> ;; basic block 5, loop depth 1, count 5000 (precise), maybe hot >>>> >>>> The rule is applied per edge and attributes no flow. It does not depend >>>> on how many dispatch blocks a function has. It also needs no notion of >>>> which dispatch a target belongs to. >>>> >>>> Evaluated on aarch64 (NVIDIA Vera) with CPython 3.14.5 at -O3. CPython >>>> was configured with --enable-optimizations and --with-lto. In >>>> Python/ceval.c, the share of the basic blocks of >>>> _PyEval_EvalFrameDefault left with a precise zero count falls from 98.3% >>>> to 23.1%. During the full-program LTO link, the function grows by 0.7% >>>> and the interpreter text grows by 0.1%. A fixed-work >>>> pyperformance-derived workload takes 2.5% fewer cycles. >>>> >>>> Bootstrapped and tested on aarch64-none-linux-gnu. >>> >>> Wouldn't UNINITIALIZED be what actually happens? We simply don't know >>> due to lack of counters. Or do we ICE on that later, requiring sth else >>> than >>> UNINITIALIZED? >> >> Yes, UNINITIALIZED describes the information we have, but it cannot remain >> in that state here. >> The earlier profile-estimation pass has already set cfun->cfg->full_profile. >> I tested setting these edge probabilities to UNINITIALIZED. The cleanup >> after branch_prob () then reports “probability of edge ... not initialized” >> and fails with verify_flow_info failed. > > The problem with unitialized values is that you can not really drop them > meaningfully into the cost model (i.e. estimate cost of a spill in > register allocator or benefit of inline). At the end of day we need > actual numbers to compute prioriites on. So once profile is build > everyone is supposed to fill the unknowns by reasonable guesses. > > Guessed 0 is kind of dangerous value for computed goto if it lives on a hot > path (which computed gotos may). It means that the 0 may get propagated > further and code on hot path become cold. Also BB reorder will then not > place the computed goto close to its likely target which is probably > still useful optimization
Ok, another approach could be to initialize these edges with a share of the remaining “unaccounted for” profile. That seems to also work for the Cpython case: https://gcc.gnu.org/pipermail/gcc-patches/2026-August/728947.html Thanks, Kyrill > > Honza >> >> Thanks, >> Kyrill
