Wird printf auch dann noch Kosten verursachen, wenn ich die Ausgabe nach /dev/null umleite?

Lesezeit: 10 Minuten

Michaels Benutzeravatar
Michael

Wir haben einen Daemon, der viele Druckmeldungen enthält. Da wir an einem eingebetteten Gerät mit einer schwachen CPU und anderer eingeschränkter Hardware arbeiten, möchten wir alle Arten von Kosten (IO, CPU usw.) von printf-Nachrichten in unserer endgültigen Version minimieren. (Benutzer haben keine Konsole)

Mein Teamkollege und ich haben eine Meinungsverschiedenheit. Er meint, wir könnten einfach alles nach /dev/null umleiten. Es kostet keinen IO, also wird die Zuneigung minimal sein. Aber ich denke, es wird immer noch CPU kosten und wir definieren besser ein Makro für printf, damit wir “printf” umschreiben können (vielleicht nur zurück).

Also brauche ich ein paar Meinungen darüber, wer Recht hat. Wird Linux schlau genug sein, um printf zu optimieren? Ich bezweifle es wirklich.

  • Achtung Nebenwirkungen: printf("%d", x=a+b); Wenn Sie umleiten zu /dev/null Nebenwirkungen werden auftreten; wenn Sie umschreiben als nichts tun Makro, Nebenwirkungen gehen verloren

    – pmg

    15. Januar 2019 um 9:42 Uhr


  • Bereitstellung von a myprintf(...) { return; } ist wahrscheinlich das, was Sie wollen. Sie können dann ein Makro für die Weiterleitung von printf an diese Methode haben, wobei Nebenwirkungen erhalten bleiben, aber keine Zeichenfolge formatiert oder Write aufgerufen wird

    – msrd0

    15. Januar 2019 um 10:10 Uhr

  • @pmg: Nebenwirkungen in a printf Aussage sind böse. Bei der Codeüberprüfung würde ich definitiv ein Problem darüber ansprechen.

    – MSalter

    15. Januar 2019 um 14:37 Uhr

  • Ich würde einen Schritt zurückgehen. In einem Linux-Daemon gibt es weitaus bessere Optionen als printf … Verwenden Sie beispielsweise stattdessen syslog, wenn Sie die Protokollebene beim Start festlegen (idealerweise von einer Umgebungsvariable) und Sie die Protokolle über die in eine Datei oder auf einen anderen Computer leiten können Netzwerk trivial, und die Protokollebene ermöglicht es Ihnen, die Protokollierung von Dingen, die Sie nicht interessieren, zu relativ geringen Kosten in der Ausführungszeit auszuschalten. Noch besser, wenn Sie so etwas wie das Abfangen einiger Signale tun, können Sie die Protokollebene zur Laufzeit ändern, ohne anzuhalten, geschweige denn den Daemon neu kompilieren zu müssen.

    – Dan Mühlen

    15. Januar 2019 um 19:33 Uhr

  • Anscheinend benötigen Sie ein geeignetes Protokollierungsframework. Zumindest eine, die eine verzögerte Auswertung der Nachricht unterstützt.

    – Alexander

    15. Januar 2019 um 23:48 Uhr

Benutzeravatar von iBug
iBug

Ja schon.

Wenn Sie die Standardausgabe des Programms umleiten zu /dev/nulljeder Anruf an printf(3) wertet weiterhin alle Argumente aus, und der Zeichenfolgenformatierungsprozess findet weiterhin vor dem Aufruf statt write(2), wodurch die vollständig formatierte Zeichenfolge in die Standardausgabe des Prozesses geschrieben wird. Auf der Kernel-Ebene werden die Daten nicht auf die Festplatte geschrieben, sondern von dem Handler verworfen, der dem speziellen Gerät zugeordnet ist /dev/null.

Im besten Fall umgehen oder vermeiden Sie also den Aufwand, die Argumente auszuwerten und an zu übergeben printfder String-Formatierungsjob dahinter printfund mindestens einen Systemaufruf, um die Daten tatsächlich zu schreiben, einfach durch Umleiten von stdout zu /dev/null. Nun, das ist ein echter Unterschied zu Linux. Die Implementierung gibt nur die Anzahl der Bytes zurück, die Sie schreiben wollten (angegeben durch das dritte Argument Ihres Aufrufs an write(2)) und ignoriert alles andere (siehe diese Antwort). Abhängig von der zu schreibenden Datenmenge und der Geschwindigkeit des Zielgeräts (Festplatte oder Terminal) kann der Leistungsunterschied stark variieren. Auf eingebetteten Systemen wird im Allgemeinen das Schreiben auf die Festplatte durch Umleiten auf unterbrochen /dev/null kann einige Systemressourcen für eine nicht triviale Menge an geschriebenen Daten einsparen.

Theoretisch könnte das Programm das zwar erkennen /dev/null und einige Optimierungen innerhalb der Einschränkungen der Standards durchführen, denen sie entsprechen (ISO C und POSIX), basierend auf dem allgemeinen Verständnis gängiger Implementierungen, tun sie dies praktisch nicht (dh mir ist kein Unix- oder Linux-System bekannt, das dies tut).

Der POSIX-Standard schreibt vor, dass für jeden Aufruf an die Standardausgabe geschrieben wird printf(3)daher ist es nicht standardkonform, den Aufruf von zu unterdrücken write(2) abhängig von den zugehörigen Dateideskriptoren. Weitere Einzelheiten zu den POSIX-Anforderungen finden Sie in Damons Antwort. Oh, und eine kurze Anmerkung: Alle Linux-Distributionen sind praktisch POSIX-konform, obwohl sie es nicht sind zertifiziert so zu sein.

Beachten Sie, dass, wenn Sie ersetzen printf vollständig, einige Nebenwirkungen können zum Beispiel schief gehen printf("%d%n", a++, &b). Wenn Sie die Ausgabe abhängig von der Programmausführungsumgebung wirklich unterdrücken müssen, ziehen Sie in Betracht, ein globales Flag zu setzen und printf zu beenden, um das Flag vor dem Drucken zu überprüfen – es wird das Programm nicht in einem Ausmaß verlangsamen, in dem der Leistungsverlust sichtbar ist , da es sich um eine einzelne Bedingungsprüfung handelt viel schneller als anrufen printf und die gesamte Zeichenfolgenformatierung durchführen.

  • Antworten wie diese sollten angeben, dass sie auf einem allgemeinen Verständnis gängiger Implementierungen und nicht auf einer spezifischen Dokumentation basieren. Theoretisch gibt es keinen Grund, warum eine C-Implementierung nicht inspizieren könnte stdouterfahren Sie, dass es /dev/null ist, und unterdrücken Sie es printf Anrufe, die nicht enthalten %n und dessen Rückgabewert nicht verwendet wird. Wir können nicht wirklich behaupten, dass niemand dies getan hat, und Studenten sollten die Herkunft von Informationen lernen, da ein wichtiger Teil des Ingenieurwesens darin besteht, zu wissen, woher man etwas weiß (ist es in einer Norm festgelegt, wird es nur angenommen, ist es beweisbar usw.). ).

    – Eric Postpischil

    15. Januar 2019 um 11:34 Uhr

  • @EricPostpischil Danke dafür! Sehr wertvolle Informationen.

    – iBug

    15. Januar 2019 um 11:39 Uhr

  • @EricPostpischil Siehe Damons Antwort als Referenz aus dem POSIX-Standard – Unterdrückung von a write(2) Der Aufruf zum Schreiben von stdout ist vom Standard verboten.

    – iBug

    15. Januar 2019 um 13:29 Uhr

  • AFAIK, die Konsolenausgabe ist ziemlich langsam, daher könnte die Zeitersparnis durch das Schreiben nach /dev/null erheblich sein. Es wäre jedoch weniger effektiv, wenn Sie in eine Datei schreiben würden.

    – SJuan76

    15. Januar 2019 um 15:12 Uhr

  • @ SJuan76 Das Schreiben auf ein Terminal ist nicht unbedingt langsam, anzeigen es ist (dh hängt vom Endgerät ab).

    – iBug

    15. Januar 2019 um 15:13 Uhr


Das printf Funktion Wille schreiben an stdout. Es entspricht nicht der Optimierung /dev/null. Daher müssen Sie den Formatstring analysieren und alle erforderlichen Argumente auswerten, und Sie haben mindestens einen Systemaufruf, und Sie kopieren einen Puffer in den Kernel-Adressraum (was im Vergleich zu den Kosten des Systemaufrufs vernachlässigbar ist). .

Diese Antwort basiert auf der spezifischen Dokumentation von POSIX.

Systemschnittstellen

dprintf, fprintf, printf, snprintf, sprintf – formatierte Ausgabe drucken

Die Funktion fprintf() soll die Ausgabe auf dem benannten Ausgabestrom platzieren. Die Funktion printf() soll die Ausgabe auf dem Standardausgabestrom stdout platzieren. Die sprintf()-Funktion platziert die Ausgabe gefolgt vom Null-Byte, ‘\0’, in aufeinanderfolgenden Bytes, beginnend bei *s; Es liegt in der Verantwortung des Benutzers sicherzustellen, dass genügend Platz vorhanden ist.

Basisdefinitionen

soll
Beschreibt für eine Implementierung, die POSIX.1-2017 entspricht, eine Funktion oder ein Verhalten, das obligatorisch ist. Eine Anwendung kann sich auf die Existenz der Funktion oder des Verhaltens verlassen.

  • Der Kernel ist für das Kopieren des Puffers in den Kernel-Adressraum und den Nulltreiber verantwortlich wahrscheinlich lässt diesen Schritt aus (unter Linux ist dies definitiv der Fall – es wird nicht einmal überprüft, ob es sich um eine gültige Adresse handelt)

    – Random832

    15. Januar 2019 um 15:13 Uhr

  • Machen Sie es vielleicht etwas klarer, dass nicht jeder Aufruf von printf zu einem Systemaufruf führt – im Moment ist dies etwas zweideutig, ansonsten eine großartige Antwort.

    – Voo

    15. Januar 2019 um 19:46 Uhr

  • -1, ignoriert dies die Als-Ob-Regel (die für die gesamte Umsetzung, nicht nur der Compiler). Wenn die Implementierung als Ganzes beweisen kann, dass es keinen Unterschied im beobachtbaren Verhalten gibt, kann sie jede beliebige Optimierung vornehmen, selbst nachdem die Kompilierung abgeschlossen ist. Wenn wir jetzt über eine andere Sprache als C sprechen, könnten Sie Recht haben.

    – Kevin

    15. Januar 2019 um 19:47 Uhr

  • @Kevin Macht die Als-ob-Regel der C Norm gelten für die Posix auch Standard?

    – Angew ist nicht mehr stolz auf SO

    16. Januar 2019 um 12:31 Uhr

  • @Kevin Da Aufrufe von E/A-Funktionen selbst beobachtbares Verhalten sind, ist die Optimierung nicht eingeschränkt. Wenn der Standard besagt, dass nur E/A ein beobachtbarer Effekt ist, wäre das Entfernen von printf nach /dev/null möglich.

    – Jinawee

    16. Januar 2019 um 16:34 Uhr

Der Benutzer-Avatar eines Programmierer-Typen
Irgendein Programmierer

Das printf Funktion schreibt an stdout. Wenn der Dateideskriptor verbunden ist mit stdout wird umgeleitet /dev/null dann wird nirgendwo eine Ausgabe geschrieben (aber es wird trotzdem geschrieben), aber der Aufruf an printf selbst und die Formatierung, die es tut, wird immer noch passieren.

  • @OP: Zusatz: Sie können die Kosten weiter reduzieren printf() indem Sie einen neuen Treiber erstellen, der eine neue bereitstellt FILE * (je nachdem, ob Ihre Plattform dies unterstützt). In diesem Fall können Sie eine Datensenke erstellen, die die Daten verwirft. Der Aufwand für Formatierung etc. bleibt weiterhin bestehen, aber das Betriebssystem ruft zum Schreiben auf /dev/null geht weg.

    – glglgl

    15. Januar 2019 um 9:48 Uhr

  • Antworten wie diese sollten angeben, dass sie auf einem allgemeinen Verständnis gängiger Implementierungen und nicht auf einer spezifischen Dokumentation basieren. Theoretisch gibt es keinen Grund, warum eine C-Implementierung nicht inspizieren könnte stdouterfahren Sie, dass es /dev/null ist, und unterdrücken Sie es printf Anrufe, die nicht enthalten %n und dessen Rückgabewert nicht verwendet wird. Wir können nicht wirklich behaupten, dass niemand dies getan hat, und Studenten sollten die Herkunft von Informationen lernen, da ein wichtiger Teil des Ingenieurwesens darin besteht, zu wissen, woher man etwas weiß (ist es in einer Norm festgelegt, wird es nur angenommen, ist es beweisbar usw.). ).

    – Eric Postpischil

    15. Januar 2019 um 11:32 Uhr

Benutzeravatar von MAXdB
MAXdB

Schreiben Sie Ihr eigenes, das printf() umschließt, indem Sie die printf()-Quelle als Richtlinie verwenden und sofort zurückkehren, wenn ein noprint-Flag gesetzt ist. Der Nachteil davon ist, dass beim tatsächlichen Drucken mehr Ressourcen verbraucht werden, da die Formatzeichenfolge zweimal analysiert werden muss. Aber es verbraucht vernachlässigbare Ressourcen, wenn es nicht druckt. Kann printf() nicht einfach ersetzen, da sich die zugrunde liegenden Aufrufe in printf() mit einer neueren Version der stdio-Bibliothek ändern können.

void printf2(const char *formatstring, …);

Im Allgemeinen darf eine Implementierung solche Optimierungen durchführen, wenn sie die beobachtbaren (funktionalen) Ausgaben des Programms nicht beeinflussen. Im Falle des printf()das würde bedeuten, dass, wenn das Programm den Rückgabewert nicht verwendet, und wenn es keine gibt %n Konvertierungen, dann dürfte die Implementierung nichts tun.

In der Praxis ist mir keine Implementierung unter Linux bekannt, die derzeit (Anfang 2019) eine solche Optimierung durchführt – die Compiler und Bibliotheken, mit denen ich vertraut bin, formatieren die Ausgabe und schreiben das Ergebnis auf das Null-Gerät, wobei sie sich auf den Kernel verlassen “, um es zu ignorieren.

Möglicherweise möchten Sie eine eigene Weiterleitungsfunktion schreiben, wenn Sie wirklich die Kosten für die Formatierung sparen müssen, wenn die Ausgabe nicht verwendet wird – Sie möchten, dass sie zurückkehrt voidund Sie sollten die Formatzeichenfolge auf überprüfen %n. (Du könntest benutzen snprintf mit einer NULL und 0 Puffer, wenn Sie diese Nebeneffekte benötigen, aber die Einsparungen den investierten Aufwand wahrscheinlich nicht zurückzahlen werden).

  • Da das beobachtbare Verhalten der abstrakten Maschine sicherlich Aufrufe von I/O-Funktionen der Bibliothek beinhaltet printf zumindest müsste es richtig heißen? Obwohl ich nicht sicher bin, wie zwei verschiedene Aufrufe einer Bibliotheksfunktion als unterschiedlich angesehen werden oder nicht …

    – Jinawee

    16. Januar 2019 um 16:29 Uhr


in C schreiben 0; führt aus und nichts, was ähnlich ist ;.

bedeutet, dass Sie ein Makro wie schreiben können

#if DEBUG
#define devlognix(frmt,...) fprintf(stderr,(frmt).UTF8String,##__VA_ARGS__)
#else
#define nadanix 0
#define devlognix(frmt,...) nadanix
#endif
#define XYZKitLogError(frmt, ...) devlognix(frmt)

wo XYZKitLogError wäre Ihr Log-Befehl. oder auch

#define nadanix ;

Dadurch werden alle Protokollaufrufe zur Kompilierzeit rausgeschmissen und durch ersetzt 0; oder ; also wird es geparst.

Sie erhalten Unbenutzte Variable Warnungen, aber es tut, was Sie wollen, und dieser Nebeneffekt kann sogar hilfreich sein, weil er Sie über Berechnungen informiert, die in Ihrem Release nicht benötigt werden.

.UTF8String ist eine Objective-C-Methode, die NSStrings in const char* konvertiert – Sie brauchen es nicht.

  • Da das beobachtbare Verhalten der abstrakten Maschine sicherlich Aufrufe von I/O-Funktionen der Bibliothek beinhaltet printf zumindest müsste es richtig heißen? Obwohl ich nicht sicher bin, wie zwei verschiedene Aufrufe einer Bibliotheksfunktion als unterschiedlich angesehen werden oder nicht …

    – Jinawee

    16. Januar 2019 um 16:29 Uhr


1414000cookie-checkWird printf auch dann noch Kosten verursachen, wenn ich die Ausgabe nach /dev/null umleite?

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

Privacy policy