localtime() 比 Linux 上的 gmtime() 性能问题多 24 倍

localtime() takes 24 times more than gmtime() performance issue on linux

本文关键字:问题 性能 上的 Linux localtime gmtime      更新时间:2023-10-16

由于项目的性能问题,我制作了以下测试程序(甚至使用不同的变量进行健全性检查):

int main()
{
struct tm *timeinfo;
time_t rawtime;
clock_t begin, end, begin1, end1,begin2,end2;
double time_spent;
begin = clock();
for (int i = 0; i < 1000000; i++){
    time ( &rawtime );
    timeinfo = localtime(&rawtime);
}
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("Time elapsed using localtime()      : %fsn", time_spent);    
//--------------------------------
begin1 = clock();
for (int i = 0; i < 1000000; i++){
    time ( &rawtime );
    timeinfo = gmtime(&rawtime);
}
end1 = clock();
time_spent = (double)(end1 - begin1) / CLOCKS_PER_SEC;
printf("Time elapsed using gmtime()         : %fsn", time_spent);           
//--------------------------------
begin2 = clock();
for (int i = 0; i < 1000000; i++){
    time ( &rawtime );
    localtime_r( &rawtime, timeinfo);
}
end2 = clock();
time_spent = (double)(end2 - begin2) / CLOCKS_PER_SEC;
printf("Time elapsed using localtime_r()    : %fsn", time_spent);

return 0;
}

我得到的结果很奇怪,localtime 花费的时间是 ~24 倍,localtime_r() 函数似乎比 localtime() 花费的时间少,但仍然比 gmtime() 多得多:

Time elapsed using localtime()      : 0.958033s
Time elapsed using gmtime()         : 0.038769s
Time elapsed using localtime_r()    : 0.860276s

用gcc 5和4.x版本编译了它,我使用了linux Mint(更新到今天)和CentOS5。此外,它在不同的物理机器上进行了测试,性能差异相似。

为什么会有如此(巨大)的性能差异?

如果在perf record下运行此代码,然后执行perf report,您会注意到每次迭代时localtime调用getenv("TZ")。而gmtime没有。

查看glibc的源代码确认了调用图:

gmtime
    __tz_convert(, 0, )
       tzset_internal(0, 1)
mktime
    __tz_convert(, 1, )
       tzset_internal(1, 1)
           getenv("TZ")

请参阅localtime的源代码glibcgmtime__tz_convert .

perf report

Samples: 78K of event 'cycles:u', Event count (approx.): 21051445406
  Children      Self       Samples  Command  Shared Object      Symbol                          ▒
+   99.48%     0.59%           349  test     test               [.] main                        ▒
+   99.48%     0.00%             0  test     libc-2.23.so       [.] __libc_start_main           ▒
+   99.48%     0.00%             0  test     test               [.] _start                      ▒
+   98.02%     7.07%          4208  test     libc-2.23.so       [.] __tz_convert                ◆
-   24.51%    23.87%         20812  test     libc-2.23.so       [.] getenv                      ▒
   - 23.87% _start                                                                              ▒
        __libc_start_main                                                                       ▒
        main                                                                                    ▒
        __tz_convert                                                                            ▒
        getenv                                                                                  ▒
   + 0.64% getenv                                                                               ▒
+   22.41%    12.75%          9877  test     libc-2.23.so       [.] __tzfile_compute            ▒
+   15.49%    15.49%          8025  test     libc-2.23.so       [.] __offtime                   ▒
+   12.72%     6.28%          5476  test     libc-2.23.so       [.] __tzfile_read               ▒
+    9.66%     3.54%          3086  test     libc-2.23.so       [.] __tzstring                  ▒
+    8.36%     1.40%          1221  test     libc-2.23.so       [.] __strdup                    ▒
+    7.68%     3.38%          2946  test     libc-2.23.so       [.] free                        ▒
+    5.98%     3.07%          2682  test     libc-2.23.so       [.] malloc                      ▒
+    4.96%     0.68%           611  test     libc-2.23.so       [.] __xstat64                   ▒
+    4.30%     4.30%          3750  test     libc-2.23.so       [.] _int_free                   ▒
+    4.28%     4.28%          3731  test     [kernel.kallsyms]  [k] entry_SYSCALL_64            ▒
+    4.08%     4.08%          3564  test     libc-2.23.so       [.] strlen                      ▒
+    3.64%     3.64%          3168  test     libc-2.23.so       [.] __memcmp_sse2               ▒
+    2.91%     2.91%          2561  test     libc-2.23.so       [.] _int_malloc                 ▒
+    1.25%     1.25%          1094  test     libc-2.23.so       [.] __memcpy_sse2               ▒
+    0.68%     0.68%           587  test     libc-2.23.so       [.] localtime                   ▒
     0.26%     0.26%           222  test     libc-2.23.so       [.] 0x000000000001f910          ▒
     0.18%     0.18%            37  test     test               [.] gmtime@plt                  ▒
     0.15%     0.15%           126  test     test               [.] localtime@plt               ▒
     0.11%     0.11%            23  test     libc-2.23.so       [.] gmtime                      ▒
     0.01%     0.01%             6  test     [kernel.kallsyms]  [k] __irqentry_text_start       ▒
     0.00%     0.00%             3  test     ld-2.23.so         [.] _dl_lookup_symbol_x         ▒
     0.00%     0.00%             5  test     ld-2.23.so         [.] do_lookup_x                 ▒
     0.00%     0.00%             2  test     ld-2.23.so         [.] _dl_relocate_object         ▒
     0.00%     0.00%             1  test     libc-2.23.so       [.] 0x000000000001f8e8          ▒
     0.00%     0.00%             1  test     ld-2.23.so         [.] strlen                      ▒
     0.00%     0.00%             1  test     ld-2.23.so         [.] _dl_debug_initialize        ▒
     0.00%     0.00%             1  test     ld-2.23.so         [.] _dl_start                   ▒
     0.00%     0.00%             1  test     [kernel.kallsyms]  [k] page_fault                  ▒
     0.00%     0.00%             6  test     ld-2.23.so         [.] _start     
缓存

localtimegmtime的部分工作。

因此,只有第一次调用这些函数之一才能完成真正的工作,即读取/etc/localtime以获取本地时区。此操作也适用于gmtime(),即使可能没有必要。

为了获得可靠的结果,请在第一次测量之前致电gmtimelocaltime。那么这两个功能的性能应该是相似的。