laserattackПрограмма, которую я периодически пишу и использую для ведения заметок и задач: mado - markdown organizer for Linux
Я создал 10000 заметок, которые программа должна обработать и вот что я обнаружил:
36.26 0.086568 4 20090 newfstatat
20000 системные вызовов stat на 10000 записей. при этом явно в программе stat вызывается один раз на запись. так с чего системных вызовов в 2 раза больше чем должно быть?
оказалось для каждой записи вот что происходит
newfstatat(AT_FDCWD, "/home/serr/projects/mado/MADO/20260107T020900/MAIN.md", {st_mode=S_IFREG|0644, st_size=2543, ...}, 0) = 0
> /usr/lib/libc.so.6(fstatat+0xa) [0xf76aa]
> /home/serr/projects/mado/mado(+0x0) [0x11c72]
> /home/serr/projects/mado/mado(+0x0) [0x147ce]
> /home/serr/projects/mado/mado(_ZL8cmd_listiPPc+0x713) [0xa203]
> /home/serr/projects/mado/mado(main+0xdd) [0x764d]
> /usr/lib/libc.so.6(__libc_start_call_main+0x7c) [0x2abfc]
> /usr/lib/libc.so.6(__libc_start_main+0x85) [0x2acb5]
> /home/serr/projects/mado/mado(_start+0x21) [0x7831]
newfstatat(AT_FDCWD, "/etc/localtime", {st_mode=S_IFREG|0644, st_size=1535, ...}, 0) = 0
> /usr/lib/libc.so.6(fstatat+0xa) [0xf76aa]
> /usr/lib/libc.so.6(__tzfile_read+0xec) [0xcae0c]
> /usr/lib/libc.so.6(tzset_internal+0x154) [0xca9a4]
> /usr/lib/libc.so.6(__tz_convert+0x53) [0xcaba3]
> /home/serr/projects/mado/mado(+0x0) [0x1281d]
> /home/serr/projects/mado/mado(+0x0) [0x147ce]
> /home/serr/projects/mado/mado(_ZL8cmd_listiPPc+0x713) [0xa203]
> /home/serr/projects/mado/mado(main+0xdd) [0x764d]
> /usr/lib/libc.so.6(__libc_start_call_main+0x7c) [0x2abfc]
посмотрю что там
0x0000000000012818 <+5896>: call 0x5260 <localtime@plt>
0x000000000001281d <+5901>: lea 0x9e1e(%rip),%rdx
т.е. первый системный вызов - тот, который я делаю явно, а второй -
получение информации о таймзоне через функцию localtime
вот проблемный участок кода
if (need_mtime) {
struct stat st;
if (stat(entry_file.c_str(), &st) == 0) {
std::tm *tm = std::localtime(&st.st_mtime);
char buf[16];
std::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", tm);
entry->mtime = buf;
} else {
return nullptr;
}
}
получается что функция std::localtime
std::tm *tm = std::localtime(&st.st_mtime);
для каждой записи заново узнает информацию о таймзоне
В системах Linux информация о часовом поясе (таймзоне) хранится в
файле /etc/localtime
Когда программа запрашивает локальное время, библиотека libc должна
знать:
вот тут man7.org/linux/man-pages/man3/localtime.3.html есть такой абзац
The localtime() function converts the calendar time timep to broken-down time representation, expressed relative to the user’s specified timezone. The function also sets the external variables tzname, timezone, and daylight as if it called tzset(3). The return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions. The localtime_r() function does the same, but stores the data in a user-supplied struct. It need not set tzname, timezone, and daylight.
ключевое тут про localtime вот что
The function also sets the external variables tzname, timezone, and daylight as if it called tzset(3)
т.е. она действительно при каждом вызове читает информацию о таймзоне,
а значит обращается к /etc/localtime
тут же написано про другую функцию - localtime_r
The localtime_r() function does the same, but stores the data in a user-supplied struct. It need not set tzname, timezone, and daylight.
а вот она не читает информацию о таймзоне при каждом вызове
поменял std::localtime на localtime_r
modified libmado/mado.cpp
@@ -228,9 +228,10 @@ Mado_Entry::parse(const Mado_Config *cfg,
if (need_mtime) {
struct stat st;
if (stat(entry_file.c_str(), &st) == 0) {
- std::tm *tm = std::localtime(&st.st_mtime);
+ struct tm tm_result;
+ localtime_r(&st.st_mtime, &tm_result);
char buf[16];
- std::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", tm);
+ std::strftime(buf, sizeof(buf), "%Y%m%dT%H%M%S", &tm_result);
entry->mtime = buf;
} else {
return nullptr;
и действительно получаю уменьшение числа системных вызовов в 2 раза
25.18 0.043956 4 10047 newfstatat
но обращение к /etc/localtime все таки происходит
openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = 4
> /usr/lib/libc.so.6(__open64_nocancel+0x3e) [0xfbdee]
> /usr/lib/libc.so.6(_IO_file_open+0xb5) [0x84bd5]
> /usr/lib/libc.so.6(_IO_file_fopen+0xc5) [0x84cb5]
> /usr/lib/libc.so.6(__fopen_internal+0x79) [0x79459]
> /usr/lib/libc.so.6(__tzfile_read+0x103) [0xcae23]
> /usr/lib/libc.so.6(tzset_internal+0x154) [0xca9a4]
> /usr/lib/libc.so.6(__tz_convert+0x53) [0xcaba3]
> /home/serr/projects/mado/mado(+0x0) [0x12828]
> /home/serr/projects/mado/mado(+0x0) [0x147ce]
> /home/serr/projects/mado/mado(_ZL8cmd_listiPPc+0x713) [0xa203]
вот оно тут
0x0000000000012823 <+5907>: call 0x51c0 <localtime_r@plt>
0x0000000000012828 <+5912>: mov %r13,%rcx
но происходит всего 1 раз за время работы программы. localtime_r
обращается к файлу если внутренние переменные с инфой о таймзоне не
установлены, но если они установлены - не обращается, а просто
использует инфу из них
вот что еще есть в мануале
According to POSIX.1, localtime() is required to behave as though tzset(3) was called, while localtime_r() does not have this requirement. For portable code, tzset(3) should be called before localtime_r()
т.е. рекомендуется все таки явно инициализировать внутренние
переменные таймзоны через tzset()
Заменив std::localtime() на localtime_r() + однократный вызов
tzset(), убрал лишний системный вызов stat() для /etc/localtime
на каждую запись
Результат на 100 000 записей:
newfstatatPosted on 18th August 2026