Was können Menschen aus dem einschränkenden Qualifizierer machen?

Lesezeit: 12 Minuten

Benutzer-Avatar
Jérémie König

Wenn ich den C99 habe restrict Schlüsselwort right, die Qualifizierung eines Zeigers damit ist ein Versprechen, dass die Daten, auf die er verweist, nicht hinter dem Rücken des Compilers durch Aliasing modifiziert werden.

Dagegen verstehe ich das const Qualifizierer ist eine vom Compiler erzwungene Dokumentation, dass ein bestimmtes Objekt nicht hinter dem Rücken eines Menschen geändert wird, der Code schreibt. Der Compiler erhält vielleicht als Nebeneffekt einen Hinweis, aber als Programmierer ist mir das egal.

Wäre es ähnlich angebracht, a restrict Qualifier in einem Funktionsprototyp als Anforderung, dass der Benutzer für die Dauer des Aufrufs exklusiven Zugriff (“Aviasing vermeiden” oder vielleicht etwas Stärkeres) sicherstellt? Soll es als “Dokumentation” verwendet werden?

Daran ist auch etwas zu verstehen restrict qualifiziert einen Zeiger und nicht die Daten, auf die er zeigt (als const tut) ?

EDIT: Ich habe das ursprünglich geglaubt restrict könnte Auswirkungen auf Thread-Code haben, aber das scheint falsch zu sein, also entferne ich Verweise auf Threads aus der Frage, um die Leser nicht zu verwirren.

  • Nun, es gibt ein C-Schlüsselwort, von dem ich noch nie zuvor gehört habe …

    – Nick Bedford

    1. Oktober 2009 um 22:23 Uhr

  • @ Nick Bedford: Es ist in C99, nicht in C89. Also nur zehn Jahre ein Standard-Stichwort…

    – Jonathan Leffler

    1. Oktober 2009 um 22:45 Uhr

  • @Jeremie: Irgendeine Ahnung, was ein flüchtiger eingeschränkter Zeiger bedeutet? Ich denke, es ist das Fehlen des flüchtigen Elements, das bedeutet, dass ein eingeschränkter Zeiger nicht hinter dem Rücken des Compilers geändert wird. Eingeschränktes “mehr oder weniger” bedeutet, dass es keine Möglichkeit gibt, dass sich die Parameter der Funktion im Speicher überlappen. Überprüfen Sie die Diskussion von restict in stackoverflow.com/questions/1485505 und die Links von dort (die Links zu einem Python-PEP und zu Cell Project-Dokumenten).

    – Jonathan Leffler

    1. Oktober 2009 um 22:54 Uhr

  • @ Jonathan: A volatile restrict Auf den Zeiger muss für jede im Quellcode angegebene Zeit genau einmal zugegriffen werden, aber der Compiler kann diese Zugriffe mit Zugriff auf nichtflüchtige Variablen neu anordnen. Wenn es einfach ist volatile aber auch nicht restrictmuss es nicht nur jedes Mal das Ziel lesen, sondern damit warten, bis eine andere Variable geschrieben wurde, die das Ziel aliasieren könnte.

    – Ben Voigt

    6. Januar 2011 um 19:54 Uhr

Benutzer-Avatar
Absturz

Chris Dodd hat die richtige Beschreibung des Schlüsselworts. Auf bestimmten Plattformen kann dies aus Leistungsgründen sehr wichtig sein, da es den Compiler wissen lässt, dass er, sobald er Daten über diesen Zeiger in ein Register geladen hat, dies nicht noch einmal tun muss. Ohne diese Garantie muss der Compiler die Daten jedes Mal über einen Zeiger neu laden, wenn ein anderer möglicherweise Alias-Zeiger durchgeschrieben wird, was zu einem ernsthaften Pipeline-Stall namens a führen kann Laden-Hit-Speichern.

const und restrict sind unterschiedliche Konzepte, und das ist nicht der Fall const impliziert restrict. Alle const sagt, dass Sie nicht durch diesen Zeiger schreiben werden im Rahmen dieser Funktion. EIN const Zeiger kann immer noch Alias ​​sein. Betrachten Sie zum Beispiel:

int foo( const int *a, int * b )
{
   *b *= 2;
   return *a + *b; // induces LHS: *a must be read back immediately
                   // after write has cleared the store queue
}

Während Sie nicht direkt anschreiben können a In dieser Funktion wäre es für Sie vollkommen legal, foo wie folgt aufzurufen:

int x = 3;
foo( &x, &x );  // returns 12

restrict ist eine andere Garantie: ein Versprechen, dass a != b in allen Anrufen zu foo().

Ich habe über die geschrieben restrict Schlüsselwort und seine Auswirkungen auf die Leistung ausführlichund so hat Mike Acton. Obwohl wir von einem bestimmten In-Order-PowerPC sprechen, existiert das Load-Hit-Store-Problem auch auf dem x86, aber die Out-of-Order-Ausführung des x86 macht es schwieriger, diesen Stillstand in einem Profil zu isolieren.

Und nur um es zu betonen: Das ist es nicht eine geheimnisvolle oder vorzeitige Optimierung, wenn Sie sich überhaupt um Leistung kümmern. restrict kann bei richtiger Anwendung zu wirklich erheblichen Beschleunigungen führen.

  • Ich glaube nicht, dass das Beispiel unbedingt dazu führt *b wird sofort zurückgelesen (gcc mit -O3 macht das, was Sie wollen: liest a und b vom Stack in edx bzw. ecx und tut es dann mov (%ecx),%eax, add %eax,%eax, mov %eax,(%exc), mov (%edx),%ecx, pop %ebp, add %ecx,%eax, ret). Wenn *a wurden natürlich auch in der funktion modifiziert das wäre eine andere geschichte.

    – Steve Jessop

    1. Oktober 2009 um 23:28 Uhr

  • mov %eax,(%exc), mov (%edx),%ecx ist der Load-Hit-Store genau dort. Ich meinte allerdings *a statt *b.

    – Absturz

    1. Oktober 2009 um 23:42 Uhr

  • Das bleibt also stehen, auch wenn ecx und edx unterschiedliche Werte haben? Das wusste ich nicht. Das Hinzufügen von “restrict” ändert überhaupt nichts an der Ausgabe von gcc 3, es wird nicht einmal früher von edx/a geladen. Es scheint also, dass mit diesem Compiler die LHS unvermeidlich ist, egal was Sie tun.

    – Steve Jessop

    1. Oktober 2009 um 23:52 Uhr

  • Es hält (innerhalb der CPU-Pipeline) an, wenn edx und ecx den gleichen Wert haben. Es tut nicht, wenn die eine Ladung den anderen Speicher nicht trifft. Aus diesem Grund sind LHS heimtückisch: Sie können sie nicht erkennen, wenn Sie sich den Code in einer Funktion ansehen. Das Problem liegt im Aliasing der Parameter, die Ihnen übergeben wurden. Was einschränken sollte do ist in diesem Fall mov (%ecx),%eax ; mov (%edx),%ebx ; %ebx hinzufügen, %ebx ; füge %eax, %ebx hinzu; zurück . Und ja, GCC emittiert mehr als seinen Anteil an WTFs.

    – Absturz

    1. Oktober 2009 um 23:55 Uhr

  • Das schwerwiegendere Beispiel für das Problem wäre, wenn ich zB schreiben würde *a += 3 Vor dem *b *= 2; In diesem Fall müsste eine Ladung zwangsläufig auf ein Lager treffen auch wenn die Zeiger kein Alias ​​waren weil der Compiler *a zurückladen müsste, nachdem er *b berührt hat; und *a wurde gerade geschrieben.

    – Absturz

    2. Oktober 2009 um 0:01 Uhr

Benutzer-Avatar
Chris Dodd

Die beste “Intuition” über das Schlüsselwort “restrict” ist, dass es eine Garantie (vom Programmierer an den Compiler) ist, dass während der Lebensdauer des Zeigers auf den Speicher, auf den über diesen Zeiger zugegriffen wird, NUR über diesen Zeiger und nicht über einen anderen Zeiger zugegriffen wird oder Referenz oder globale Adresse. Daher ist es wichtig, dass es sich um einen Zeiger handelt, da es sich um eine Eigenschaft sowohl des Zeigers als auch des Speichers handelt und die beiden miteinander verbunden werden, bis der Zeiger den Gültigkeitsbereich verlässt.

Benutzer-Avatar
Jerry Sarg

Das meiste, was Sie wissen, ist falsch!

const tut nicht garantieren, dass sich nichts hinter dem Rücken des Compilers ändert. Alles, was es tut, ist zu stoppen Sie vom Schreiben an diese Stelle. Etwas anderes kann jedoch möglicherweise immer noch an diesen Ort schreiben, sodass der Compiler NICHT davon ausgehen kann, dass er konstant ist.

Wie andere gesagt haben, geht es beim Restriktionsqualifizierer um Aliasing. Tatsächlich gab es während der ersten Runde der C-Standardisierung einen Vorschlag für ein “noalias”-Schlüsselwort. Leider war der Vorschlag ziemlich schlecht geschrieben – er veranlasste Dennis Ritchie zum einzigen Mal, sich in diesen Prozess einzumischen, als er einen Brief schrieb, in dem es hieß: „Noalias müssen gehen. Dies ist nicht verhandelbar. “

Unnötig zu erwähnen, dass ‘noalias’ kein Teil von C wurde. Als es an der Zeit war, es noch einmal zu versuchen, war der Vorschlag so gut geschrieben, dass restriktiv in den Standard aufgenommen wurde – und obwohl noalias wahrscheinlich ein aussagekräftigerer Name gewesen wäre dafür war dieser Name so verdorben, dass ich bezweifle, dass irgendjemand auch nur daran gedacht hat, ihn zu verwenden.

In jedem Fall besteht die Hauptabsicht von „restrict“ darin, dem Compiler mitzuteilen, dass es keinen Alias ​​für dieses Element geben wird. Ein Grund dafür ist, Dinge vorübergehend in Registern speichern zu können. Betrachten Sie zum Beispiel Folgendes:

void f(int *a, int *b, int *c) { 
    for (int i=0; i<*a; i++)
        *b += c[i];
}

Der Compiler möchte eigentlich i in ein Register setzen und *a in ein Register laden. Wenn es also an der Zeit ist zu entscheiden, ob eine weitere Iteration der Schleife ausgeführt werden soll, vergleicht er einfach die Werte in diesen Registern miteinander. Leider kann es das nicht — wenn jemand, der diese Funktion verwendet hat, völlig verrückt war und sie mit a==b aufgerufen hat, ist dieser neue Wert jedes Mal, wenn sie in *b innerhalb der Schleife schreibt, auch der Wert von *a — also muss es bei jeder Iteration der Schleife *a aus dem Speicher lesen, nur für den Fall wer auch immer es genannt hat, war völlig verrückt. Die Verwendung von “restrict” teilt dem Compiler mit, dass er Code generieren kann, vorausgesetzt, dass a und b immer unterschiedlich sind, sodass das Schreiben in *a niemals *b ändert (oder umgekehrt).

  • eigentlich dürfen sich Werte hinter den Compilern überhaupt nicht ändern, egal ob const vorhanden ist oder nicht; Wenn sich die Werte ändern, müssen Sie die hinzufügen volatile Qualifikation

    – Christoph

    2. Oktober 2009 um 6:19 Uhr

  • Ja und nein – flüchtig bedeutet, dass Sie möchten, dass der Compiler Code generiert, der die Möglichkeit berücksichtigt, dass er sich hinter Ihrem Rücken ändert.

    – Jerry Sarg

    2. Oktober 2009 um 21:48 Uhr

  • Ich frage mich, wie “Noalias” funktioniert hätte und gegen welche Aspekte Ritchie am meisten Einwände hatte? Ich würde denken, dass C von einer “_cacheable(lvalue)”-Direktive profitiert hätte, die angeben würde, dass alle Lesevorgänge des Lvalue oder der davon abgeleiteten Lvalues ​​innerhalb der Anweisung beliebig zu einem früheren Zeitpunkt innerhalb der Anweisung sequenziert werden können und Schreibvorgänge beliebig sein können zu einem späteren Zeitpunkt innerhalb der Anweisung sequenziert werden. Globale Deklaration gegeben int x;und innerhalb einer Funktion int * restrict px=&x; foo(*px); foo(*px); ein Compiler laden könnte x einmal in ein vom Angerufenen gespeichertes Register, übergeben Sie es an foound dann…

    – Superkatze

    16. August 2015 um 22:30 Uhr


  • …weitergeben foo erneut, ohne es neu laden zu müssen, aber es sei denn, ein Compiler hat das erkannt px war konstant und erkannte die Auswirkungen von restrictkönnte der generierte Maschinencode für den letzteren Ansatz langsamer werden, als er es gewesen wäre x wurden einfach doppelt geladen. EIN _cacheable(x) Direktive hätte für Menschen einfacher zu lesen und für den Compiler zu verarbeiten sein können.

    – Superkatze

    16. August 2015 um 22:32 Uhr

Benutzer-Avatar
DigitalRoss

Ihr Verständnis ist weitgehend richtig. Das restrict Der Qualifizierer gibt einfach an, dass auf die Daten zugegriffen wird, die von einem so qualifizierten Zeiger stammen nur Zugriff durch genau diesen Zeiger. Es gilt sowohl für Lese- als auch für Schreibvorgänge.

Der Compiler kümmert sich nicht um gleichzeitige Threads, er würde Code nicht anders generieren, und Sie können Ihre eigenen Daten nach Belieben überschreiben. Aber es muss wissen, welche Zeigeroperationen welchen globalen Speicher verändern können.

Restrict bringt auch eine API-Warnung für Menschen mit sich, dass eine bestimmte Funktion unter der Annahme von Parametern ohne Alias ​​implementiert wird.

Seitens des Compilers ist keine Verriegelung durch den Benutzer notwendig. Es möchte nur sicherstellen, dass es die Daten korrekt liest, die vorhanden waren soll per Code geschlagen werden der Compiler sollte generierenfalls es keine gibt restrict Qualifikation. Hinzufügen restrict befreit es von dieser Sorge.

Beachten Sie schließlich, dass der Compiler auf den höheren Optimierungsebenen wahrscheinlich bereits mögliches Aliasing basierend auf Datentypen analysiert restrict ist vor allem für Funktionen mit mehreren Zeigern auf denselben Datentyp wichtig. Sie können eine Lehre aus diesem Thema ziehen und sicherstellen, dass jedes absichtliche Aliasing, das Sie verwenden, über a erfolgt union.

Wir sehen restrict in Aktion:

void move(int *a, int *b) {     void move(int *__restrict a, int *__restrict b) {
    a[0] = b[0];                    a[0] = b[0];
    a[1] = b[0];                    a[1] = b[0];
}                               }
    movl    (%edx), %eax            movl    (%edx), %edx
    movl    %eax, (%ecx)            movl    %edx, (%eax)
    movl    (%edx), %eax            movl    %edx, 4(%eax)
    movl    %eax, 4(%ecx)

In der rechten Spalte mit restrictmusste der Compiler nicht erneut lesen b[0] aus der erinnerung. Es konnte lesen b[0] und im Register halten %edx, und speichern Sie das Register dann einfach zweimal im Speicher. In der linken Spalte wusste es nicht, ob der Laden zu a kann sich geändert haben b.

Jemand, der mit dem Standard besser vertraut ist, könnte wahrscheinlich eine bessere Antwort geben, aber ich werde es versuchen.

“Daten werden nicht hinter dem Rücken des Compilers geändert” klingt für mich eher nach dem Gegenteil von “flüchtig”.

“const” bedeutet, dass die Daten vor den Augen des Programmierers nicht geändert werden; Das heißt, sie kann die Daten nicht über den als “const” gekennzeichneten Signifikanten ändern (ich schreibe “signifier”, weil in int const *pider Name pi ist nicht konstant, aber *pi ist). Die Daten können möglicherweise über einen anderen Signifikator geändert werden (nicht konstante Daten können schließlich als konstante Daten an eine Funktion übergeben werden).

Dass “eingeschränkt” Zeiger qualifiziert, ist der Schlüssel. Zeiger sind die einzige Möglichkeit, Daten in C zu aliasieren, also sind sie die einzige Möglichkeit, auf einige Daten über zwei verschiedene Namen zuzugreifen. Bei „restrict“ geht es darum, den Datenzugriff auf einen Zugriffspfad zu beschränken.

Benutzer-Avatar
Mark Ruschakoff

Dies könnte ein Beispiel aus einer sein äußerst schmale Domäne, aber die Nios II-Plattform von Altera ist ein Soft-Core-Mikrocontroller, den Sie innerhalb eines FPGA anpassen können. Dann können Sie innerhalb des C-Quellcodes für dieses Mikro ein C-zu-Hardware-Tool verwenden, um innere Schleifen mit benutzerdefinierter Hardware zu beschleunigen, anstatt in Software.

Darin verwenden Sie die __restrict__ Schlüsselwort (das mit dem von C99 identisch ist restrict) ermöglicht es dem C2H-Tool, die Hardwarebeschleunigung des Zeigerbetriebs richtig zu optimieren parallel zu statt nacheinander. Zumindest in diesem Fall die restrict ist einfach nicht für den menschlichen Verzehr bestimmt. Siehe auch Sun’s Seite auf restrictwo die erste Zeile sagt

Verwendung der restrict Ein geeigneter Qualifizierer in C-Programmen kann es dem Compiler ermöglichen, erheblich schnellere ausführbare Dateien zu erstellen.

Wenn jemand daran interessiert ist, mehr über C2H zu lesen, dieses PDF diskutiert die Optimierung von C2H-Ergebnissen. Der Abschnitt auf __restrict__ steht auf Seite 20.

1088660cookie-checkWas können Menschen aus dem einschränkenden Qualifizierer machen?

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

Privacy policy