[PATCH] libstdc++: Rsolve UNTIL save adjustment at tzdb loading time [PR116110]
Tomasz Kaminski
tkaminsk@redhat.com
Wed Jul 29 10:01:39 GMT 2026
On Tue, Jul 28, 2026 at 7:22 PM Tomasz Kamiński <tkaminsk@redhat.com> wrote:
> This patch moves the save calculation (ZoneInfo::calc_save) to database
> loading code (reload_tzdb) instead of applying it on demand when zone
> is queried (time_zone::_M_get_sys_info). This nivelates the performance
> inpact on non-first calls (that return the chached) result, caused by
> iterator adjustment checks.
>
> Local perfomance test indicate an 10% (30ns to 33ns on average) for cached
> queries with on-demand implementation (after r17-2466-g020e02fcf28),
> combined
> with huge swings on time on first calls. This patch leads 200ms increase
> (1.95s to 2.15s) on time of reload_tzdb, that happens only during initial
> load (and later explicit reload).
>
> As we need two bits of state (expanded or unitl_pending), I have decied
> to keep the four value m_state enum.
>
> PR libstdc++/116110
>
> libstdc++-v3/ChangeLog:
>
> * src/c++20/tzdb.cc (time_zone::_M_get_sys_info): Remove
> ZoneInfo::calc_save invocaiton and related iterator adjustment.
> (chrono::reload_tzdb): Calculate save (invoke calc_save) for
> all infos on all zones.
> ---
> This goes back to the initial patch from Álvaro Begué, as it turned out
> the cost of calculating save is much smaller than actually parsing the
> data. And this way we avoid performance impact on second or later calls.
>
> For the reload_tzdb I have measure this by changing the implementation
> to ignore remote_version check, and load tzdb regardless. I have
> preserved the check in _S_repleace_head, so the zone was dropped after
> loading (so we do not increase memory consumption). the results I got
> where:
> w/calc_save 2151407 ns 2141084 ns 333
> wo/calc_save 1959904 ns 1951554 ns 364
>
> To measure I have measuared call get_info for all know zones for
> on three randomly picked dates: 1970y/December/31, 2020y/January/14,
> 2026y/July/28.
> With the non-modified implementation this produced a cached result
> (only first call was exporting). To measure the first call I have again
> modified the implementation to not store new_info and not to mark
> SaveKnow (I have tried creating list of same tzdbs, but variance betwee
> runs was two high).
>
> This gave me following results, averaginhg from all zones
> Cached First %AVG(-Base/First)
> Base 30.90ns 1,630.86
> Lazy (before) 33.92ns 1,924.59 176.35%
> At_parse (this) 31.38ns 1,685.57 1.98%
>
> In general I think this is much better approach, and something
> we could backport to GCC-16.
>
> Testing on x86-64_linux. OK for trunk when all test passes?
> I will create an separate GCC-16 patch with original and follow-up
> merged.
>
> libstdc++-v3/src/c++20/tzdb.cc | 25 +++++++------------------
> 1 file changed, 7 insertions(+), 18 deletions(-)
>
> diff --git a/libstdc++-v3/src/c++20/tzdb.cc
> b/libstdc++-v3/src/c++20/tzdb.cc
> index 6fe3c6cfee6..e358b778e2d 100644
> --- a/libstdc++-v3/src/c++20/tzdb.cc
> +++ b/libstdc++-v3/src/c++20/tzdb.cc
> @@ -1069,35 +1069,19 @@ namespace std::chrono
> // Find the transition info for the time point.
> auto i = ranges::upper_bound(infos, tp, ranges::less{},
> &ZoneInfo::until);
>
> - // Perform the comparison on save adjusted until values (if needed)
> - // Assume that applying the save will not change relative order of
> - // ZoneInfo objects.
> - if (i != infos.begin() && i[-1].calc_save(node->rules) &&
> (i[-1].until() > tp))
> - --i;
> - else if (i != infos.end() && i->calc_save(node->rules) && (i->until()
> <= tp))
> - ++i;
>
I couldn't simply move this after if (i->to(info)), as expanded zone can be
produced
two ways:
* from previous expansion (i[-1].unil() is correct)
* directly from the tzdb file (no rules are used), then i[-1].until() may
need to
adjusted.
-
> if (i == infos.end())
> {
> if (infos.empty())
> __throw_runtime_error("std::chrono::time_zone::get_info: invalid
> data");
> - (--i)->calc_save(node->rules);
> - tp = i->until();
> + tp = (--i)->until();
> }
> - else // Guarantee that i->until() is correct
> - i->calc_save(node->rules);
> -
>
> sys_info info;
>
> if (i == infos.begin())
> info.begin = sys_days(year::min()/January/1);
> else
> - {
> - ZoneInfo& prev = i[-1];
> - prev.calc_save(node->rules);
> - info.begin = prev.until();
> - }
> + info.begin = i[-1].until();
>
> if (i->to(info)) // We already know a sys_info for this time.
> return info;
> @@ -2050,6 +2034,11 @@ constinit tzdb_list::_Node::NumLeapSeconds
> tzdb_list::_Node::num_leap_seconds;
> return lhs.save < rhs.save;
> });
>
> + // Calculate the SAVE value at UNTIL, and adjust it if necessary.
> + for (time_zone& tz : node->db.zones)
> + for (ZoneInfo& info : tz._M_impl->infos)
> + info.calc_save(node->rules);
> +
> return Node::_S_replace_head(std::move(head), std::move(node));
> #else
> __throw_disabled();
> --
> 2.55.0
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260729/07326ab3/attachment.htm>
More information about the Libstdc++
mailing list