Vorteile purer Funktion

Lesezeit: 7 Minuten

Benutzeravatar des Grünen Kobolds
Grüner Kobold

Heute habe ich über die reine Funktion gelesen und bin mit ihrer Verwendung durcheinander gekommen:

Eine Funktion wird als rein bezeichnet, wenn sie denselben Satz von Werten für denselben Satz von Eingaben zurückgibt und keine beobachtbaren Nebenwirkungen hat.

z.B strlen() ist eine reine Funktion während rand() ist ein unreines.

__attribute__ ((pure)) int fun(int i)
{
    return i*i;
}

int main()
{
    int i=10;
    printf("%d",fun(i));//outputs 100
    return 0;
}

http://ideone.com/33XJU

Das obige Programm verhält sich genauso wie ohne pure Erklärung.

Was sind die Vorteile der Deklaration einer Funktion als pure[if there is no change in output]?

  • Ja – sehen Sie sich die generierte Assembly an.

    – Philipp Kendall

    22. Juni 2012 um 9:47 Uhr

  • Ich glaube nicht, dass diese Definition von Reinheit richtig ist – printfwürde sich beispielsweise qualifizieren (ein zweimaliges Aufrufen mit denselben Argumenten ergibt denselben Rückgabewert), aber es ist nicht rein.

    – tdammer

    22. Juni 2012 um 11:51 Uhr

  • @tdammers: In der Tat, es fehlt die ...and no side-effects... Teil.

    – Frerich Raabe

    22. Juni 2012 um 12:15 Uhr

  • @Ben: woher kommt die Entropie? Wir haben es hier mit (theoretisch) deterministischen Maschinen zu tun, die einzige Möglichkeit, echte Entropie in sie zu bekommen, ist von externen Quellen, was Nebenwirkungen bedeutet. Natürlich könnten wir Programmiersprachen erlauben, nicht deterministische Funktionen zu definieren, indem wir so tun, als ob die technischen Nebeneffekte nicht da wären und die Funktionen wirklich nicht deterministisch wären; Aber wenn wir das tun, gehen die meisten praktischen Vorteile der Verfolgung der Reinheit verloren.

    – tdammer

    22. Juni 2012 um 13:50 Uhr

  • tdammers ist richtig – die Definition von rein oben angegeben ist falsch. Rein bedeutet, dass die Ausgabe abhängig ist nur an den Eingängen der Funktion; außerdem dürfen keine beobachtbaren Nebenwirkungen auftreten. „Gleicher Output für gleichen Input“ ist eine sehr ungenaue Zusammenfassung dieser Anforderungen. en.wikipedia.org/wiki/Pure_function

    – Dancrumb

    22. Juni 2012 um 14:04 Uhr

pure lässt den Compiler wissen, dass er bestimmte Optimierungen an der Funktion vornehmen kann: Stellen Sie sich ein bisschen Code wie vor

for (int i = 0; i < 1000; i++)
{
    printf("%d", fun(10));
}

Bei einer reinen Funktion kann der Compiler wissen, dass er auswerten muss fun(10) Einmal und nur einmal, statt 1000 Mal. Für eine komplexe Funktion ist das ein großer Gewinn.

  • Das heißt, Sie können Memoization sicher verwenden

    – Joel Coehoorn

    22. Juni 2012 um 17:48 Uhr

  • @Mob Was meinst du? Warum nicht?

    – Konrad Rudolf

    22. Juni 2012 um 18:17 Uhr

  • Weil Sie den String (Zeichenfolge ab einer Adresse) ändern können, ohne die Eingabe (den Zeiger auf die Adresse, an der der String beginnt) zu ändern, dh Sie können ihn nicht speichern. Es wäre nur eine reine Funktion in einer Sprache mit unveränderlichen Zeichenfolgen (z. B. Java).

    – Pöbel

    22. Juni 2012 um 19:35 Uhr

  • @KonradRudolph: Stellen Sie sich einen String mit einer Länge von 1000 vor. Anruf strlen darauf. Dann wieder. Dasselbe ja? Ändern Sie nun das zweite Zeichen zu sein \0. Tut strlen jetzt noch 1000 zurückgeben? Die Startadresse ist dieselbe (== Eingabe ist dieselbe), aber die Funktion gibt jetzt einen anderen Wert zurück.

    – Mike Bailey

    23. Juni 2012 um 16:10 Uhr


  • @mob Das ist ein guter Einwand, offensichtlich hast du Recht. Ich habe mich dadurch täuschen lassen sogar Bücher behaupten, dass strlen (in gcc/glibc) geht nämlich rein. Aber ein Blick auf die glibc-Implementierung zeigte, dass dies falsch war.

    – Konrad Rudolf

    23. Juni 2012 um 16:44 Uhr


Benutzeravatar von ArjunShankar
ArjunShankar

Wenn Sie sagen, dass eine Funktion “rein” ist, garantieren Sie, dass sie keine äußerlich sichtbaren Nebenwirkungen hat (und wie ein Kommentar sagt, wenn Sie lügen, können schlimme Dinge passieren). Zu wissen, dass eine Funktion „rein“ ist, hat Vorteile für den Compiler, der dieses Wissen nutzen kann, um bestimmte Optimierungen vorzunehmen.

Hier ist, was die GCC-Dokumentation sagt über die pure Attribut:

rein

Viele Funktionen haben außer dem Rückgabewert keine Auswirkungen und ihr Rückgabewert hängt nur von den Parametern und/oder globalen Variablen ab. Eine solche Funktion kann genau wie ein arithmetischer Operator der Eliminierung gemeinsamer Unterausdrücke und der Schleifenoptimierung unterzogen werden. Diese Funktionen sollten mit dem Attribut pure deklariert werden. Zum Beispiel,

          int square (int) __attribute__ ((pure));

Die Antwort von Philip zeigt bereits, wie das Wissen, dass eine Funktion „rein“ ist, bei Schleifenoptimierungen helfen kann.

Hier ist eine für die gemeinsame Eliminierung von Unterausdrücken (gegeben foo ist rein):

a = foo (99) * x + y;
b = foo (99) * x + z;

Kann werden:

_tmp = foo (99) * x;
a = _tmp + y;
b = _tmp + z;

  • Ich bin mir nicht sicher, ob dies irgendjemand tut, aber reine Funktionen ermöglichen es dem Compiler auch, neu zu ordnen, wenn die Funktion aufgerufen wird, falls eine Neuordnung für vorteilhaft erachtet wird. Wenn die Möglichkeit von Seiteneffekten besteht, muss der Compiler konservativer sein.

    – mpdonadio

    22. Juni 2012 um 17:12 Uhr

  • @MPD – Ja, das klingt vernünftig. Und seit A call Befehl ist ein Engpass für superskalare CPUs, etwas Hilfe vom Compiler kann helfen.

    – ArjunShankar

    22. Juni 2012 um 17:50 Uhr

  • Ich erinnere mich vage, dass ich vor einigen Jahren einen DSP-Compiler verwendet habe, der diese Technik verwendet hat, um früher/später Rückgabewerte zu erhalten. Dadurch konnten Pipeline-Stalls minimiert werden.

    – mpdonadio

    22. Juni 2012 um 18:53 Uhr

  • Könnte “foo(99)” vorberechnet werden, da 99 eine Konstante ist und foo immer das gleiche Ergebnis liefert? Vielleicht in einer Art zweistufiger Kompilierung?

    – markwatson

    25. Juni 2012 um 20:41 Uhr

  • @markwatson – Ich bin mir nicht sicher. Es kann Fälle geben, in denen es einfach nicht möglich ist. zB wenn foo Teil einer anderen Kompilationseinheit (einer anderen C-Datei) oder in einer vorkompilierten Bibliothek ist. In beiden Fällen weiß der Compiler nicht, was foo kann und kann nicht vorberechnen.

    – ArjunShankar

    25. Juni 2012 um 23:08 Uhr


Zusätzlich zu möglichen Laufzeitvorteilen ist eine reine Funktion beim Lesen von Code viel einfacher zu begründen. Außerdem ist es viel einfacher, eine reine Funktion zu testen, da Sie wissen, dass der Rückgabewert nur von den Werten der Parameter abhängt.

  • +1, Ihr Punkt zum Testen ist interessant. Kein Auf- und Abbau erforderlich.

    – ArjunShankar

    22. Juni 2012 um 9:59 Uhr


Eine nicht reine Funktion

int foo(int x, int y) // possible side-effects

ist wie eine Erweiterung einer reinen Funktion

int bar(int x, int y) // guaranteed no side-effects

in der Sie neben den expliziten Funktionsargumenten x, y den Rest des Universums (oder irgendetwas, mit dem Ihr Computer kommunizieren kann) als implizite potenzielle Eingabe haben. Ebenso ist neben dem expliziten ganzzahligen Rückgabewert alles, worauf Ihr Computer schreiben kann, implizit Teil des Rückgabewerts.

Es sollte klar sein, warum es viel einfacher ist, über eine reine Funktion nachzudenken als über eine nicht reine.

Nur als Add-On möchte ich erwähnen, dass C++11 Dinge mit dem Schlüsselwort constexpr etwas kodiert. Beispiel:

#include <iostream>
#include <cstring>

constexpr unsigned static_strlen(const char * str, unsigned offset = 0) {
        return (*str == '\0') ? offset : static_strlen(str + 1, offset + 1);
}

constexpr const char * str = "asdfjkl;";

constexpr unsigned len = static_strlen(str); //MUST be evaluated at compile time
//so, for example, this: int arr[len]; is legal, as len is a constant.

int main() {
    std::cout << len << std::endl << std::strlen(str) << std::endl;
    return 0;
}

Die Einschränkungen bei der Verwendung von constexpr machen es so, dass die Funktion beweisbar rein ist. Auf diese Weise kann der Compiler aggressiver optimieren (stellen Sie nur sicher, dass Sie die Endrekursion verwenden, bitte!) und die Funktion zur Kompilierzeit statt zur Laufzeit auswerten.

Um Ihre Frage zu beantworten: Wenn Sie C ++ verwenden (ich weiß, Sie sagten C, aber sie sind verwandt), ermöglicht das Schreiben einer reinen Funktion im richtigen Stil dem Compiler, alle möglichen coolen Dinge mit der Funktion zu tun: -)

Benutzeravatar von Uri Goren
Uri Goren

Im Allgemeinen haben reine Funktionen gegenüber unreinen Funktionen drei Vorteile, die der Compiler nutzen kann:

Caching

Nehmen wir an, Sie haben reine Funktion f das 100000 Mal aufgerufen wird, da es deterministisch ist und nur von seinen Parametern abhängt, kann der Compiler seinen Wert einmal berechnen und bei Bedarf verwenden

Parallelität

Reine Funktionen lesen oder schreiben in keinen gemeinsam genutzten Speicher und können daher ohne unerwartete Folgen in separaten Threads ausgeführt werden

Übergabe durch Referenz

Eine Funktion f(struct t) bekommt sein Argument t nach Wert, und andererseits kann der Compiler übergeben t unter Bezug auf f wenn es als rein deklariert wird und gleichzeitig den Wert garantiert t wird sich nicht ändern und Leistungsgewinne haben


Zusätzlich zu den Überlegungen zur Kompilierzeit können reine Funktionen ziemlich einfach getestet werden: Rufen Sie sie einfach auf.

Keine Notwendigkeit, Objekte zu konstruieren oder Verbindungen zu DBs/Dateisystem zu simulieren.

1418790cookie-checkVorteile purer Funktion

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

Privacy policy