Welche Verbesserungen bietet `__builtin_malloc()` von GCC gegenüber dem einfachen `malloc()`?

Lesezeit: 4 Minuten

Ich bin kürzlich auf die eingebauten Funktionen von GCC für einige der Speicherverwaltungsfunktionen der C-Bibliothek aufmerksam gemacht worden, insbesondere __builtin_malloc() und verwandte integrierte Funktionen (siehe https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html). Beim Lernen über __builtin_malloc()ich habe mich gefragt, wie es funktionieren könnte, um Leistungsverbesserungen über die Ebene bereitzustellen malloc() verwandte Bibliotheksroutinen.

Wenn die Funktion beispielsweise erfolgreich ist, muss sie einen Block bereitstellen, der durch einen Aufruf von plain freigegeben werden kann free() da der Zeiger möglicherweise durch ein Modul freigegeben wird, das ohne kompiliert wurde __builtin_malloc() oder __builtin_free() aktiviert (oder irre ich mich, und wenn __builtin_malloc() verwendet wird, müssen die Builtins global verwendet werden?). Daher muss das zugewiesene Objekt etwas sein, das mit den Datenstrukturen so einfach verwaltet werden kann malloc() und free() bewältigen.

Ich kann keine Details finden, wie __builtin_malloc() funktioniert oder was es genau tut (ich bin kein Compiler-Entwickler, also ist das Durchforsten des GCC-Quellcodes nicht in meinem Steuerhaus). In einigen einfachen Tests, in denen ich versucht habe, anzurufen __builtin_malloc() direkt, es endet einfach damit, im Objektcode als Aufruf von plain ausgegeben zu werden malloc(). Es kann jedoch Feinheiten oder Plattformdetails geben, die ich in diesen einfachen Tests nicht anführe.

Welche Arten von Leistungsverbesserungen können __builtin_malloc() Stellen Sie über einen Anruf zu Plain bereit malloc()? Tut __builtin_malloc() haben eine Abhängigkeit von den recht komplexen Datenstrukturen der glibc malloc() Implementierung verwenden? Oder umgekehrt, tut glibc’s malloc()/free() haben Sie etwas Code, um mit Blöcken umzugehen, die zugewiesen werden könnten __builtin_malloc()?

Grundsätzlich, wie funktioniert es?

Benutzer-Avatar
Jonathon Reinhart

Ich glaube, es gibt keine spezielle GCC-interne Implementierung von __builtin_malloc(). Vielmehr existiert es nur als Built-in, damit es unter bestimmten Umständen wegoptimiert werden kann.

Nehmen Sie dieses Beispiel:

#include <stdlib.h>
int main(void)
{
    int *p = malloc(4);
    *p = 7;
    free(p);
    return 0;
}

Wenn wir eingebaute Funktionen deaktivieren (mit -fno-builtins) und sehen Sie sich die generierte Ausgabe an:

$ gcc -fno-builtins -O1 -Wall -Wextra builtin_malloc.c && objdump -d -Mintel a.out

0000000000400580 <main>:
  400580:   48 83 ec 08             sub    rsp,0x8
  400584:   bf 04 00 00 00          mov    edi,0x4
  400589:   e8 f2 fe ff ff          call   400480 <malloc@plt>
  40058e:   c7 00 07 00 00 00       mov    DWORD PTR [rax],0x7
  400594:   48 89 c7                mov    rdi,rax
  400597:   e8 b4 fe ff ff          call   400450 <free@plt>
  40059c:   b8 00 00 00 00          mov    eax,0x0
  4005a1:   48 83 c4 08             add    rsp,0x8
  4005a5:   c3                      ret    

Ruft an malloc/free werden erwartungsgemäß emittiert.

Allerdings durch Zulassen malloc eingebaut sein,

$ gcc -O1 -Wall -Wextra builtin_malloc.c && objdump -d -Mintel a.out

00000000004004f0 <main>:
  4004f0:   b8 00 00 00 00          mov    eax,0x0
  4004f5:   c3                      ret    

Alle main() wurde wegoptimiert!

Im Wesentlichen durch das Zulassen malloc Da GCC eingebaut ist, steht es ihm frei, Aufrufe zu eliminieren, wenn sein Ergebnis nie verwendet wird, da es keine zusätzlichen Nebeneffekte gibt.


Es ist derselbe Mechanismus, der “verschwenderische” Aufrufe ermöglicht printf zu Anrufen geändert werden puts:

#include <stdio.h>

int main(void)
{
    printf("hello\n");
    return 0;
}

Builtins deaktiviert:

$ gcc -fno-builtin -O1 -Wall builtin_printf.c && objdump -d -Mintel a.out

0000000000400530 <main>:
  400530:   48 83 ec 08             sub    rsp,0x8
  400534:   bf e0 05 40 00          mov    edi,0x4005e0
  400539:   b8 00 00 00 00          mov    eax,0x0
  40053e:   e8 cd fe ff ff          call   400410 <printf@plt>
  400543:   b8 00 00 00 00          mov    eax,0x0
  400548:   48 83 c4 08             add    rsp,0x8
  40054c:   c3                      ret    

Builtins aktiviert:

gcc -O1 -Wall builtin_printf.c && objdump -d -Mintel a.out

0000000000400530 <main>:
  400530:   48 83 ec 08             sub    rsp,0x8
  400534:   bf e0 05 40 00          mov    edi,0x4005e0
  400539:   e8 d2 fe ff ff          call   400410 <puts@plt>
  40053e:   b8 00 00 00 00          mov    eax,0x0
  400543:   48 83 c4 08             add    rsp,0x8
  400547:   c3                      ret    

  • Interessante und nützliche Erklärung. Das wesentliche Merkmal der eingebauten Version ist also, dass sie dem Compiler ein bekanntes Verhalten garantieren kann, wodurch er wegoptimiert werden kann und vielleicht auch andere Optimierungen erhält …

    – Dan Lenski

    24. September 2014 um 6:36 Uhr

  • @DanLenski So sehe ich das. Nach einer Reihe von Experimenten ist das einzige, womit ich GCC dazu bringen kann, “besonders” zu tun __builtin_malloc ist, es weg zu optimieren. Ich habe versucht, daran vorbeizukommen 0aber die Übergabe des Ergebnisses an eine andere (externe) Funktion führte zu einem Aufruf von malloc emittiert werden.

    – Jonathon Reinhart

    24. September 2014 um 6:38 Uhr


  • Der Compiler weiß wahrscheinlich, dass das Ergebnis von __builtin_malloc keinen Alias ​​mit einem anderen Zeiger hat. Angenommen, Ihre Funktion hat int* p als Parameter und ruft int* q = malloc (sizeof int) auf; *q = 1; dann weiß der Compiler, dass diese Zuweisung *p nicht verändert hat.

    – gnasher729

    24. September 2014 um 9:34 Uhr

  • Was Sie in Ihrer Antwort andeuten, aber was explizit angegeben werden sollte, ist ein Aufruf einer deklarierten Funktion malloc wird normalerweise in einen Anruf übersetzt __builtin_mallocund ein Anruf bei __builtin_malloc wird normalerweise in einen Aufruf einer externen Bibliotheksfunktion übersetzt malloc. Sie beschreiben den zweiten Teil davon, aber der erste Teil ist auch relevant: Er ist wichtig, weil er zeigt, dass der Benutzer fast nie wirklich buchstabieren muss __builtin_malloc: Es funktioniert auch, wenn der Benutzer einfach nur alt verwendet malloc.

    Benutzer743382

    25. September 2014 um 9:11 Uhr

1370830cookie-checkWelche Verbesserungen bietet `__builtin_malloc()` von GCC gegenüber dem einfachen `malloc()`?

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

Privacy policy