Heim  >  Artikel  >  Backend-Entwicklung  >  Das obskure Schlüsselwort „restrict“ in C

Das obskure Schlüsselwort „restrict“ in C

WBOY
WBOYOriginal
2024-09-11 06:36:02379Durchsuche

The Obscure “restrict” Keyword in C

Einführung

Unter anderem hat C99 das Schlüsselwort „restrict“ hinzugefügt, um einem Programmierer die Möglichkeit zu geben, anzugeben, dass ein Zeiger der einzige Zeiger auf ein bestimmtes Objekt in einem Bereich ist, und dem Compiler folglich einen „Hinweis“ zu geben ” dass es zusätzliche Optimierungen durchführen kann, wenn über diesen Zeiger auf das Objekt zugegriffen wird.

Das Problem

Um das Problem zu veranschaulichen, das die Einschränkung lösen sollte, betrachten Sie eine Funktion wie:

void update_ptrs( int *p, int *q, int const *v ) {
  *p += *v;
  *q += *v;
}

für den der Compiler x86-64-Code wie folgt generiert:

mov eax, [rdx]  ; tmp = *v   // 1
add [rdi], eax  ; *p += tmp
mov eax, [rdx]  ; tmp = *v   // 3
add [rsi], eax  ; *q += tmp

Sie fragen sich vielleicht, warum Zeile 3 generiert wird, da sie mit Zeile 1 überflüssig zu sein scheint. Das Problem ist, dass der Compiler nicht wissen kann, dass Sie so etwas nicht getan haben:

int x = 1, v = 2;
update_ptrs( &v, &x, &v );   // x = 5, v = 4

In update_ptrs() würden p und v Alias das gleiche int sein, daher muss der Compiler auf Nummer sicher gehen und davon ausgehen, dass sich der Wert von *v zwischen den Lesevorgängen ändern kann. daher die zusätzliche mov-Anweisung.

Im Allgemeinen verfälschen Zeiger in C die Optimierung, da der Compiler nicht wissen kann, ob sich zwei Zeiger gegenseitig aliasen. Bei leistungskritischem Code könnte das Eliminieren von Speicherlesevorgängen ein großer Gewinn sein, wenn der Compiler dies sicher tun könnte.

Die Lösung

Um das oben genannte Problem zu lösen, wurde „restrict“ zu C hinzugefügt, damit Sie angeben können, dass ein bestimmter Zeiger der einzige Zeiger auf ein Objekt im Gültigkeitsbereich des Zeigers ist, d. h. kein anderer Zeiger im selben Bereich Aliase es.

Um „restrict“ zu verwenden, fügen Sie es zwischen dem * und dem Namen des Zeigers in einer Deklaration ein. Ein update_ptrs(), das zur Verwendung von „restrict“ umgeschrieben wurde, wäre:

void update_ptrs_v2( int *restrict p, int *restrict q,
                     int const *restrict v ) {
  *p += *v;
  *q += *v;
}

(Lesen Sie von rechts nach links, z. B. ist v ein eingeschränkter Zeiger auf eine Konstante int; oder verwenden Sie cdecl.)

Durch das Hinzufügen von „restrict“ kann der Compiler jetzt Code generieren wie:

mov eax, [rdx]  ; tmp = *v
add [rdi], eax  ; *p += tmp
add [rsi], eax  ; *q += tmp

Jetzt konnte der Compiler die vorherige Zeile 3 der zusätzlichen mov-Anweisung eliminieren.

Das vielleicht bekannteste Beispiel für die Verwendung von „Restrict“ ist die Standardbibliotheksfunktion memcpy(). Dies ist der schnellste Weg, einen Teil des Speichers zu kopieren, wenn sich die Quell- und Zieladresse nicht überschneiden. Die etwas langsamere memmove()-Funktion gibt es für den Einsatz, wenn sich die Adressen nicht überlappen.

Fallstricke

Missbrauch von „restrict“ führt zu undefiniertem Verhalten, beispielsweise durch die Übergabe von Zeigern, die sich gegenseitig auslösen, an update_ptrs_v2() oder memcpy(). In einigen Fällen kann der Compiler Sie warnen, aber nicht in allen Fällen. Verlassen Sie sich also nicht darauf, dass der Compiler Missbrauch erkennt.

Beachten Sie, dass die Einschränkung für einen bestimmten Bereich gilt. Das Zuweisen eines eingeschränkten Zeigers zu einem anderen im selben Bereich führt zu undefiniertem Verhalten:

void f( int *restrict d, int *restrict s ) {
  int *restrict p = s;    // undefined behavior

Sie können jedoch problemlos einen eingeschränkten Zeiger einem uneingeschränkten Zeiger zuweisen:

void f( int *restrict d, int *restrict s ) {
  int *p = s;             // OK

Auch wenn p uneingeschränkt ist, kann der Compiler dennoch dieselben Optimierungen durchführen.

Es ist auch in Ordnung, einen eingeschränkten Zeiger in einem inneren Bereich einem anderen in einem äußeren Bereich zuzuweisen (aber nicht umgekehrt):

void f( int *restrict d, int *restrict s ) {
  {                       // inner scope
    int *restrict p = s;  // OK
    // ...
    s = p;                // undefined behavior
  }
}

Wann (und wann nicht) die Verwendung eingeschränkt werden sollte

Zuerst sollten Sie auf jeden Fall ein Profil Ihres Codes erstellen (und sich vielleicht sogar den generierten Assembler-Code ansehen), um zu sehen, ob die Verwendung von „restrict“ tatsächlich zu einer erheblichen Leistungsverbesserung führt, um das Risiko potenzieller Fallstricke zu rechtfertigen. Die Diagnose von Fehlern, die durch den Missbrauch von Einschränkungen verursacht werden, ist sehr schwierig.

Zweitens ist es sicherer, wenn die Verwendung von „restrict“ auf die Implementierung einer Funktion beschränkt ist, bei der der Speicher, auf den über eingeschränkte Zeiger zugegriffen wird, von Sie zugewiesen wurde. Zum Beispiel gegeben:

void safer( unsigned n ) {
  n += n % 2 != 0;  // make even by rounding up
  int *const array = malloc( n * sizeof(unsigned) );
  unsigned *restrict half_1st = array;
  unsigned *restrict half_2nd = array + n/2;
  // ...
  free( array );
}

Der Code könnte sicher auf der ersten und zweiten Hälfte des Arrays arbeiten, da sie sich nicht überlappen (vorausgesetzt, Sie greifen nie auf half_1st[n/2] oder darüber hinaus zu).

Drittens ist die Verwendung von „restrict“ in den Parametern einer Funktion möglicherweise weniger sicher. Vergleichen Sie beispielsweise „safer()“ mit „update_ptrs_v2()“, wobei der Aufrufer die Zeiger steuert. Soweit Sie wissen, hat der Anrufer es falsch verstanden und Hinweise auf diesen Alias ​​übergeben.

Verschiedenes

Nur Zeiger auf Objekte (oder void) können mit „restrict:“ qualifiziert werden:

restrict int x;       // error: can't restrict object
int restrict *p;      // error: pointer to restrict object
int (*restrict f)();  // error: pointer-to-function

Sie können „restrict“ für Strukturmitglieder verwenden, zum Beispiel:

struct node {
   void *restrict data;
   struct node *restrict left;
   struct node *restrict right;
};

besagt, dass Daten der einzige Zeiger auf diese Daten sind und dass links und rechts niemals auf denselben Knoten zeigen. Die Verwendung von „restrict“ für Strukturmitglieder ist jedoch sehr ungewöhnlich.

Schließlich verfügt C++ nicht über eine Einschränkung. Warum nicht? Es gibt eine lange Antwort, aber die TL;DR-Version lautet:

  • Es kann eine Quelle schwer zu findender Fehler sein, die das C++-Komitee nicht aus C importieren wollte.
  • Die verstärkte Verwendung von Zeigern in C++, z. B. this, macht die sichere Verwendung von „restrict“ noch schwieriger.

Viele Compiler haben jedoch __restrict__ als Erweiterung.

Abschluss

In begrenzten Fällen kann die Verwendung von „Restrict“ zu Leistungsverbesserungen führen, es gibt jedoch mehrere erhebliche Fallstricke. Wenn Sie die Verwendung von „Restrict“ in Betracht ziehen, profilieren Sie zuerst Ihren Code.

Mit Bedacht verwenden.

Das obige ist der detaillierte Inhalt vonDas obskure Schlüsselwort „restrict“ in C. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn