Wie vermeide ich übermäßige stat(/etc/localtime)-Aufrufe in strftime() unter Linux?

Lesezeit: 6 Minuten

Ich habe ein Plattenbearbeitungsprogramm von mir ein paar Minuten unter Streß laufen lassen.

Dies wies in jenen Minuten über 200.000.000 Anrufe auf stat("/etc/localtime",..) das klingt ein bisschen übertrieben und unnötig.

Die Strace-Ausgabe sieht so aus:

write(1, "C137015 393393093052629137110 47"..., 16384) = 16384
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=2225, ...}) = 0
read(0, "\224q\1\207\0\0\202\1\4\203\1\4\204\1\1\205\1\1\206\1\7\207\1\6\211\1\22\212\1\22\213\1"..., 16384) = 16384

Im Wesentlichen stellte sich heraus, dass es 1 stat()-Aufruf für jeden verarbeiteten Datensatz war, und der Übeltäter stellte sich als diese ganz gewöhnliche Codezeile heraus

strftime(call->date_time,DATELEN,"%Y%m%d %H%M%S",&tm_buf);

Also – wie kann ich vermeiden, dass strftime() bei jedem Aufruf stat(/etc/localtime) aufruft?

  • Haben Sie einen Beweis dafür, dass dies tatsächlich ein messbares Problem verursacht (dh erhebliche Laufzeitkosten, die von einem Profiler gemessen werden)? Sie sollten nicht versuchen, basierend auf zu optimieren Gefühle (wie “klingt etwas übertrieben”). Holen Sie sich Messungen und verwenden Sie Fakten.

    – niemand

    29. Dezember 2010 um 14:13 Uhr


  • +1 für die Frage, wie man Glibc Bloat auf den Grund geht. 🙂

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    29. Dezember 2010 um 15:14 Uhr

  • Hinzufügen export TZ=:/etc/localtime zu meinem Shell-Skript eliminiert die wiederholten Syscalls für mich.

    – shuckc

    7. April 2013 um 14:57 Uhr

  • Und obwohl diese Frage alt ist: AndrewMedico: Ja, natürlich. Es wurde gemessen, dass dies etwa 10 % Auswirkung auf die Leistung hat. @R .. Es scheint auch erforderlich zu sein, um zB Zeitzonenänderungen abzufangen, und posix schreibt vor, dass sich bestimmte Funktionen so verhalten, als ob tzset() aufgerufen wird

    – Nr

    18. Juni 2014 um 7:35 Uhr

  • @nein: tzset ist erforderlich, um auf Änderungen der zu reagieren TZ Variable. Es ist nicht erforderlich zu prüfen, ob sich die Datei auf der Festplatte geändert hat, wenn sich die Variable nicht geändert hat.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    18. Juni 2014 um 7:39 Uhr

Benutzer-Avatar
Linus Kleen

Dies kann daran liegen, dass Ihre Zeitzone nicht festgelegt ist. strftime Abfragen /etc/localtime es zu finden.

Versuchen Sie die Einstellung TZ Umgebungsvariable.

Hier ist ein Link für dieses Verhalten.

  • Das Setzen von TZ=”:/etc/localtime” “behebt” dies. Auf dieser Seite steht jedoch auch “Sie sollten normalerweise TZ nicht einstellen müssen”.

    – Nr

    29. Dezember 2010 um 13:10 Uhr

  • @nein In der GNU C-Bibliothek ist die Standardzeitzone wie die Angabe ‘TZ=:/etc/localtime’ […]. Da ist die Quelle des Wiederholten stat Anrufe.

    – Linus Kleen

    29. Dezember 2010 um 13:14 Uhr

  • @awang anscheinend nicht. Wenn die TZ-Variable gesetzt ist, verwendet glibc die TZ-Variable und liest die Zeitzone aus einer Datei, die mit dem übereinstimmt, was in der TZ-Variablen einmal (oder bis tzset() aufgerufen wird) übereinstimmt – beim ersten Aufruf in einem Programm, das dies benötigt Information . Wenn die TZ-Umgebungsvariable nicht gesetzt ist, wird glibc jedes Mal in die /etc/localtime-Datei schauen (oder vielmehr prüfen, ob sich /etc/localtime seit dem letzten Lesen geändert hat – was der stat() Anrufe tut)

    – Nr

    24. September 2014 um 15:38 Uhr


  • Diese Situation hat sich in sechs Jahren nicht geändert, trotz dieser kürzlichen und etwas publizierten Untersuchung, die dies bestätigt: blog.packagecloud.io/eng/2017/02/21/… Mir ist derzeit keine Distribution bekannt, die TZ einstellt.

    – Gesetz29

    12. Juli 2017 um 12:55 Uhr

  • Für die Interessierten, ich habe es gerade versucht setenv("TZ", ":/etc/localtime", 0); am Anfang eines C-Programms, das ich schreibe, und es scheint zu funktionieren. Kommentare willkommen; Ich erinnere mich vage an handgewellte Vorstellungen darüber, die Umgebung von innerhalb eines Programms für andere Zwecke als zu stochern exec() Sein Schlechtaber das scheint zu funktionieren, und jetzt brauche ich kein Wrapper-Skript für meine Binärdatei.

    – i336_

    29. September 2019 um 8:03 Uhr


Benutzer-Avatar
Rachid K.

Mit der GNU libc-Bibliothek ist hier ein weiterer Tipp, um dieses Problem zu lösen /etc/lokalzeit nicht konfiguriert ist (dies ist häufig bei winzigen eingebetteten Systemen der Fall). Diese besteht aus zu setzen TZ Umgebungsvariable in die leere Zeichenfolge. Dadurch verwendet die Bibliothek standardmäßig die UTC.

Hier ist ein Beispiel strace Ausgabe auf einem eingebetteten System, das eine Protokollierungsfunktion aufruft, die aufruft localtime() und strftime() wenn weder /etc/lokalzeit Noch TZ eingestellt sind. Bei glibc 2.23 löst dies mehrere erfolglose Aufrufe von “openat(/etc/localtime)“:

[...]
[pid 200113] gettid() = 200113
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] gettid() = 200113
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] getpid() = 200113
[pid 200113] writev(4, [{iov_base="DUH\1\1\0\0\0", iov_len=8}, {iov_base="=\2\0\261ECU1\0\3\r\261\r\32\265\3431\2SWMCSWMC", iov_len=26}, {iov_base="\0\2\0\0'\0Tue Mar 1 13:49:20 2022 _"..., iov_len=151}], 3) = 185
[pid 200113] gettid() = 200113
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] openat(AT_FDCWD, "/etc/localtime", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
[pid 200113] gettid() = 200113
[...]

Und hier ist die Ausgabe des gleichen Programms mit strace Wenn TZ wird auf die leere Zeichenfolge gesetzt:

$ export TZ=
[...]
[pid 201837] gettid() = 201837
[pid 201837] gettid() = 201837
[pid 201837] getpid() = 201837
[pid 201837] writev(4, [{iov_base="DUH\1\1\0\0\0", iov_len=8}, {iov_base="=\2\0\261ECU1\0\3\24m\r7\253\3171\2SWMCSWMC", iov_len=26}, {iov_base="\0\2\0\0'\0Tue Mar 1 13:52:30 2022 _"..., iov_len=151}], 3) = 185
[pid 201837] gettid() = 201837
[pid 201857] <... read resumed>"DUH\1\f\0\0\0\0DUH\1\6\0\0\0\5\0\0\0\0\0DUH\1\6\0\0\0\5"..., 10024) = 37
[pid 201837] gettid( <unfinished ...>
[pid 201857] read(3, <unfinished ...>
[pid 201837] <... gettid resumed>) = 201837
[pid 201837] getpid() = 201837
[pid 201837] writev(4, [{iov_base="DUH\1\1\0\0\0", iov_len=8}, {iov_base="=\3\0\257ECU1\0\3\24m\r7\253\364A\2SWMCSWMC", iov_len=26}, {iov_base="\0\2\0\0'\0Tue Mar 1 13:52:30 2022 _"..., iov_len=149}], 3) = 183
[...]

NB: Hier ist das entsprechende Quellcode-Snippet der glibc time/tzset.c die UTC setzt, wenn TZ leer ist:

/* Interpret the TZ envariable. */
static void
internal_function
tzset_internal (int always, int explicit)
{
 static int is_initialized;
 const char *tz;
if (is_initialized && !always)
 return;
 is_initialized = 1;
/* Examine the TZ environment variable. */
 tz = getenv ("TZ");
 if (tz == NULL && !explicit)
 /* Use the site-wide default. This is a file name which means we
 would not see changes to the file if we compare only the file
 name for change. We want to notice file changes if tzset() has
 been called explicitly. Leave TZ as NULL in this case. */
 tz = TZDEFAULT;
 if (tz && *tz == '\0')
 /* User specified the empty string; use UTC explicitly. */
 tz = "Universal";
[...]

1334870cookie-checkWie vermeide ich übermäßige stat(/etc/localtime)-Aufrufe in strftime() unter Linux?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy