Ist es für den Compiler legal, die zeitliche Komplexität eines Programms zu verringern? Wird dies als beobachtbares Verhalten betrachtet?

Lesezeit: 6 Minuten

Benutzeravatar von user541686
Benutzer541686

(Hinweis: Dies soll eine Sprachanwaltsfrage sein; Ich beziehe mich nicht auf bestimmte vorhandene Compiler.)

Wann, wenn überhaupt, darf der Compiler die Zeitkomplexität eines Programms verringern?
Unter welchen Umständen (falls zutreffend) wird dies als „beobachtbares Verhalten“ betrachtet und warum?
(Kann der Compiler beispielsweise ein Programm in Polynomialzeit legal auf ein Programm in Exponentialzeit “reduzieren”?)

Wenn sich die Antwort in C und C++ oder in verschiedenen Versionen von beiden unterscheidet, erläutern Sie bitte die Unterschiede.

  • Mit “verschlechtern” meinst du, dass es schlimmer wird? Es fällt mir schwer zu erkennen, WIE der Compiler das tun könnte, da fast alles, was mit O zu tun hat, auf dem Gesamtalgorithmus basiert, und ich sehe nicht, wie der Compiler das noch verschlimmern kann (er erkennt möglicherweise, was Sie tun möchten, und in einigen seltenen Fällen besser machen, aber das ist nicht dasselbe)

    – Mats Petersson

    7. Oktober 2014 um 7:38 Uhr

  • Es gibt mindestens eine einfache Antwort: wenn undefiniertes Verhalten auftritt! Sagt der Standard überhaupt etwas zur Userland-Komplexität?

    – Benutzer657267

    7. Oktober 2014 um 7:38 Uhr

  • @MatsPetersson: Ich weiß nicht, mir fallen viele Beispiele ein. Kann ein Compiler beispielsweise die binäre Suche durch eine lineare Suche ersetzen, vielleicht weil er glaubt, dass dies für kleinere Arrays schneller sein könnte?

    – Benutzer541686

    7. Oktober 2014 um 7:40 Uhr


  • @BasileStarynkevitch: Nun, das Schöne (oder Verdammte?) an der Zeitkomplexität ist, dass sie nichts mit der Geschwindigkeit der Maschine zu tun hat, auf der Sie das Programm ausführen.

    – Benutzer541686

    7. Oktober 2014 um 7:41 Uhr

  • Sie beziehen sich also auf einen pessimistischen, noch nicht geschriebenen Compiler, der hergestellt wurde, um so viele Taktzyklen wie möglich zu verbrauchen, der von einer bösen Firma hergestellt wird, die dafür bezahlt, dass er verwendet wird, um uns zu zwingen, neue, schnellere Prozessoren zu bekommen? Wie bei den bereits geschriebenen Antworten gibt es keine rechtlichen Einschränkungen für die Möglichkeiten des Compilers. Aber es wäre verrückt und/oder dumm, all diese Mühe aufzuwenden, um Suchen oder Sortieren (usw.) zu erkennen und “es noch schlimmer zu machen”. Beachten Sie, dass es auch keine Regel gibt, die besagt, dass der Compiler nicht 100 zusätzliche Anweisungen für jede “echte” Anweisung einfügen kann … 😉

    – Mats Petersson

    7. Oktober 2014 um 8:26 Uhr


Benutzeravatar von Fred Foo
Fred Fu

Der C-Standard hat eigentlich kein Zeitkomplexitätsmodell, weder für seine primitiven Operationen noch für seine Bibliotheksfunktionen, so dass Compiler so ziemlich alles tun dürfen, was die Programmsemantik (beobachtbares Verhalten) bewahrt.

Der C++-Standard gibt nur für einige seiner Bibliotheksfunktionen Komplexitätsgarantien und sagt (17.5.1.4 [structure.specifications]):

Die in den Bibliotheksklauseln angegebenen Komplexitätsanforderungen sind Obergrenzen, und Implementierungen, die bessere Komplexitätsgarantien bieten, erfüllen die Anforderungen.

Ein Compiler behält diese Grenzen besser bei (und da viele der Funktionen auf Vorlagen basieren/inline sein können, ist der Compiler beteiligt), aber die Grenzen beziehen sich auf die Anzahl der Elemente in Containern und beschränken die Anzahl der Aufrufe von Vergleichsoperatoren und die wie. Ansonsten ist der Compiler wieder frei zu tun, was er will.

  • Dies ist ein sehr guter Kommentar (+1), aber zumindest für C++ keine Antwort, da es hier nicht um Bibliotheksfunktionen, sondern um Benutzercode geht.

    – Benutzer541686

    7. Oktober 2014 um 7:47 Uhr


  • @Mehrdad Die Antwort besagt eigentlich, dass der Standard nichts enthält, was dem Compiler verbietet, die Leistung zu beeinträchtigen. Da die erwähnte Klausel im C++-Standard eine Ausnahme darstellt, sollte sie in der Antwort erwähnt werden. Die obige Antwort sollte also so gelesen werden: Weder der C- noch der C++-Standard verbieten es dem Compiler, die zeitliche Komplexität des Benutzercodes zu verringern.

    – Klas Lindbäck

    7. Oktober 2014 um 7:53 Uhr

  • @Mehrdad Viele (vielleicht alle) Funktionen mit Zeitgrenzen sind Vorlagenfunktionen, die vom Compiler erweitert werden.

    – Fred Foo

    7. Oktober 2014 um 10:21 Uhr


  • Wenn Sie die Zeitkomplexität von Benutzercode wissen möchten, müssen Sie die Zeitkomplexität von Benutzercode kennen, der Bibliotheksfunktionen aufruft, also ist diese Antwort in Ordnung.

    – gnasher729

    7. Oktober 2014 um 12:21 Uhr

  • Die Komplexitätsanforderungen spezifizieren nur “Anzahl X Operationen”, zB X = “Anrufe an den Benutzer operator <” oder X = “ruft an std::swap

    – o11c

    8. Oktober 2014 um 8:06 Uhr

David Rodríguez - Benutzeravatar von dribeas
David Rodríguez – Dribeas

Die Leistung des Codes wird nicht als beobachtbares Verhalten betrachtet und könnte möglicherweise vom Compiler in beide Richtungen geändert werden. In der Praxis sind z Qualität der Umsetzung (QoI) Gründe, warum Compiler Ihre Programme nicht verschlechtern, aber es gibt Fälle, in denen QoI nicht die Leistung ist.

Ein Compiler könnte mit den entsprechenden Flags dem Programm, das er zu Debugging-Zwecken erstellt, eine Instrumentierung hinzufügen (dies ist häufig bei Bibliotheksimplementierungen der Fall, z. B. bei geprüften Iteratoren).

Beachten Sie, dass die einfache Antwort auf Wenn Der Compiler würde Ihr Programm in zweierlei Hinsicht verschlechtern: Wenn der Client danach fragt oder wenn der Implementierer keine Benutzer für den Compiler haben möchte.

Benutzeravatar von Jonathan Wakely
Jonathan Wakely

5.1.2.3 im C-Standard sagt

Die semantischen Beschreibungen in dieser Internationalen Norm beschreiben das Verhalten einer abstrakten Maschine, bei der Optimierungsfragen keine Rolle spielen.

Der C++-Standard hat in 1.9 einen ähnlichen Wortlaut [intro.execution]

Beide Standards haben die gleiche Definition von beobachtbarem Verhalten:

Die Mindestanforderungen an eine konforme Implementierung sind:
— Zugriffe auf flüchtige Objekte werden streng nach den Regeln der abstrakten Maschine ausgewertet.
— Bei Programmende müssen alle in Dateien geschriebenen Daten mit dem Ergebnis identisch sein, das die Ausführung des Programms gemäß der abstrakten Semantik hervorgebracht hätte.
— Die Eingangs- und Ausgangsdynamik von interaktiven Geräten muss wie in 7.21.3 spezifiziert erfolgen. Die Absicht dieser Anforderungen besteht darin, dass eine ungepufferte oder zeilengepufferte Ausgabe so schnell wie möglich erscheint, um sicherzustellen, dass Aufforderungsmeldungen tatsächlich erscheinen, bevor ein Programm auf eine Eingabe wartet.
Dies ist das beobachtbares Verhalten des Programms.

Also alles andere, zB Leistung von a for Schleife oder die Anzahl der Lese-/Schreibvorgänge für nichtflüchtige Variablen wird nicht als beobachtbar angesehen, und daher gibt es keine entsprechenden Leistungsanforderungen an den Compiler.

Wenn der Compiler einen Codeblock 100 Mal neu auswerten wollte (unter der Annahme, dass er keine beobachtbaren Nebenwirkungen hatte und nur den Zustand nichtflüchtiger Variablen änderte) und überprüfen wollte, dass jedes Mal dieselben Ergebnisse erzielt wurden (und nicht von kosmischen Strahlen oder fehlerhafte Hardware), die der Standard zulassen würde.

  • Aber selbst das 100-malige Ausführen des gesamten Codes (abzüglich der Nebeneffekte) ändert nichts an der Zeitkomplexität. Um dies zu erreichen, sollte der Compiler entweder immer längere Verzögerungen (mit fortschreitender Schleife) in Schleifen einbauen oder den geschriebenen Code durch etwas völlig anderes ersetzen (aber mit dem gleichen beobachtbaren Verhalten). In jedem Fall verbringt das Programm notwendigerweise immer mehr Zeit ohne jegliches beobachtbares Verhalten.

    – Marc van Leeuwen

    7. Oktober 2014 um 14:35 Uhr


  • In jedem Fall darf der Compiler dies tun, da er das gleiche beobachtbare Verhalten aufweist.

    – Jonathan Wakely

    7. Oktober 2014 um 14:44 Uhr

Andere haben darauf hingewiesen, dass der Standard nicht die Funktionsweise der C-Laufzeitumgebung einschränkt, sondern nur ihr beobachtbares Verhalten. Es gibt keinen Grund, warum Sie beispielsweise C nicht interpretiert oder JIT-kompiliert haben können.

Stellen Sie sich eine C-Implementierung vor, bei der alle Speicherzellen in einer verknüpften Liste auf einem zugrunde liegenden System gespeichert sind. Zeiger sind dann ein Index in diese verkettete Liste. Alle Zeigeroperationen würden normal funktionieren, außer dass die Laufzeit bei jedem Speicherzugriff über die verknüpfte Liste iterieren müsste. Allerlei gängige Algorithmen würden plötzlich einen zusätzlichen Faktor N in ihrer Komplexität gewinnen, zum Beispiel die gängigen nullterminierten String-Operationen.

1401110cookie-checkIst es für den Compiler legal, die zeitliche Komplexität eines Programms zu verringern? Wird dies als beobachtbares Verhalten betrachtet?

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

Privacy policy