"Resolve" in the first line of the commit.

On Tue, 28 Jul 2026 at 19:10 +0200, Tomasz Kamiński 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

I'm not sure what nivelates was meant to be.

inpact on non-first calls (that return the chached) result, caused by

"impact", "cached"

iterator adjustment checks.

Local perfomance test indicate an 10% (30ns to 33ns on average) for cached

"an 10%" -> "a 10% cost"

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

"until_pending", "decided"

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.

"invocation"

        (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?

OK with the typos above fixed.

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;
-
    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



Reply via email to