At least for glibc: localtime() calls always check for timezone configuration changes which usually includes a stat syscall, localtime_r() does not. # Explanation At least for glibc, every call of `localtime()` will internally check for (and react to) changes of the timezone: either the `TZ` environment variable (if set) or changes to a used file, typically `/etc/localtime` - the latter causes a 'stat' syscall. In contrast, calls to `localtime_r()` are faster because they don't check for such changes (and also wont update `tzname`, `timezone`, `daylight` global variables). This behavior is extremely poorly documented (on glibc side) such that people regularly rediscover it, often because of the performance impact. # Consequences of this change - for `weekday_to_mday()` and `find_dst_change()`: - this may actually prevent potential issues, if the timezone were to be changed during these search loops - performance may improve, but is likely irrelevant/unnoticeable since this is rarely running code (as far as i can tell) - for `get_timezone_info()`: - it massively improves performance: `ftime` and `time` calls (of MSVCRT) will eventuall call this function, thus incur the massive syscall overhead - after the first use, the timezone is not *automatically* updated when changed via either: - changing the `TZ` environment variable of the running process - changing the timezone information file (usually `/etc/localtime`) and it was actually used e.g. typically when `TZ` is unset Personally, the potential performance gains are worth the limitation that a timezone change of the system will only be effective after application restart. But his could understandably be a point of contention. # Alternative Workarounds The performance issues of the `localtime()` call in the hotpath of `get_timezone_info()` can be: - mitigate for `time()` calls: by not querying timezone info - see upcoming MR TBD - partially mitigate by setting the `TZ` environment variable, but: - in many distros it is not set by default - the value of the variable is used by both the Unix C runtime (e.g. glibc) and MSVCRT and their supported format are not identical, so setting the correct value may be more difficult than usual or even impossible - when using `TZ=:/etc/localtime`, a special format for glibc, the information is still initally read from `/etc/localtime` but not checked for freshness on every subsequent call via stat - but this format is not understood by MSVCRT, thus breaking stuff there. # Origin of Discovery I've a private application that typically is running fairly well, but during certain workloads it becomes sluggish and downright unusable. I took some profiles with `perf` and found fairly clean culprit with `localtime()` and its stat syscall, which I measure the rate for: Under normal conditions I have about 200 per second and during the problematic phases it staturated at about 100k per second. I tracked the origin of call down to `_time64` -> `_ftime64` -> ... -> `get_timezone_info()` by matching callstack information with the list of exported symbols of the involved *.dll files. # Beyond `localtime_r()` Generally speaking, there are also a few opportunities to use _r variants of these time related functions on the Unix side of wine, e.g. for `gmtime()` in `get_zomezone_info()`. Some of such changes could also improve performance, but likely not to the same degree as getting rid of a entire syscall by switching from `localtime()` to `localtime_r()`. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10892