Wenn Sie wissen, dass sie Linux> 2.6.17, splice()
verwenden werden ist der Weg, um unter Linux Zero-Copy zu machen:
//using some default parameters for clarity below. Don't do this in production.
#define splice(a, b, c) splice(a, 0, b, 0, c, 0)
int p[2];
pipe(p);
int out = open(OUTFILE, O_WRONLY);
int in = open(INFILE, O_RDONLY)
while(splice(p[0], out, splice(in, p[1], 4096))>0);
Leider können Sie sendfile()
nicht verwenden hier, weil das Ziel kein Socket ist. (Der Name sendfile()
kommt von send()
+ "Datei").
Für Zero-Copy können Sie splice()
verwenden wie von @Dave vorgeschlagen. (Außer, es wird keine Nullkopie sein; es wird "eine Kopie" vom Seitencache der Quelldatei zum Seitencache der Zieldatei sein.)
Allerdings... (a) splice()
ist Linux-spezifisch; und (b) Sie können mit ziemlicher Sicherheit genauso gut tragbare Schnittstellen verwenden, vorausgesetzt, Sie verwenden sie richtig.
Kurz gesagt, verwenden Sie open()
+ read()
+ write()
mit einem kleinen temporärer Puffer. Ich schlage 8K vor. Ihr Code würde also etwa so aussehen:
int in_fd = open("source", O_RDONLY);
assert(in_fd >= 0);
int out_fd = open("dest", O_WRONLY);
assert(out_fd >= 0);
char buf[8192];
while (1) {
ssize_t read_result = read(in_fd, &buf[0], sizeof(buf));
if (!read_result) break;
assert(read_result > 0);
ssize_t write_result = write(out_fd, &buf[0], read_result);
assert(write_result == read_result);
}
Mit dieser Schleife kopieren Sie 8 KB aus dem in_fd-Seitencache in den CPU-L1-Cache und schreiben sie dann aus dem L1-Cache in den out_fd-Seitencache. Dann überschreiben Sie diesen Teil des L1-Cache mit dem nächsten 8-KByte-Block aus der Datei und so weiter. Das Nettoergebnis ist, dass die Daten in buf
wird überhaupt nie im Hauptspeicher gespeichert (außer vielleicht einmal am Ende); Aus Sicht des System-RAM ist dies genauso gut wie die Verwendung von "Zero-Copy" splice()
. Außerdem ist es perfekt auf jedes POSIX-System portierbar.
Beachten Sie, dass der kleine Puffer hier entscheidend ist. Typische moderne CPUs haben etwa 32 KB für den L1-Datencache. Wenn Sie also den Puffer zu groß machen, wird dieser Ansatz langsamer. Möglicherweise viel, viel langsamer. Halten Sie den Puffer also im Bereich von "wenigen Kilobytes".
Natürlich ist die Speicherbandbreite wahrscheinlich nicht Ihr begrenzender Faktor, es sei denn, Ihr Festplattensubsystem ist sehr, sehr schnell. Daher würde ich posix_fadvise
empfehlen um dem Kernel mitzuteilen, was Sie vorhaben:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
Dies gibt dem Linux-Kernel einen Hinweis darauf, dass seine Read-Ahead-Maschinerie sehr aggressiv sein sollte.
Ich würde auch vorschlagen, posix_fallocate
zu verwenden um den Speicher für die Zieldatei vorab zuzuweisen. Dies wird Ihnen im Voraus sagen, ob Ihnen die Festplatte ausgehen wird. Und für einen modernen Kernel mit einem modernen Dateisystem (wie XFS) hilft es, die Fragmentierung in der Zieldatei zu reduzieren.
Das Letzte, was ich empfehlen würde, ist mmap
. Dank TLB-Thrashing ist es normalerweise der langsamste Ansatz von allen. (Sehr aktuelle Kernel mit "transparenten Hugepages" könnten dies abmildern; ich habe es in letzter Zeit nicht versucht. Aber es war sicherlich früher sehr schlecht. Also würde ich mir nur die Mühe machen, mmap
zu testen wenn Sie viel Zeit zum Benchmarken und einen sehr aktuellen Kernel haben.)
[Aktualisieren]
In den Kommentaren wird gefragt, ob splice
von einer Datei zur anderen ist Zero-Copy. Die Linux-Kernel-Entwickler nennen das „Page-Stealing“. Sowohl die Manpage für splice
und die Kommentare in der Kernel-Quelle sagen, dass SPLICE_F_MOVE
Flag sollte diese Funktionalität bereitstellen.
Leider ist die Unterstützung für SPLICE_F_MOVE
wurde in 2.6.21 (damals 2007) entfernt und nie ersetzt. (Die Kommentare in den Kernelquellen wurden nie aktualisiert.) Wenn Sie die Kernelquellen durchsuchen, finden Sie SPLICE_F_MOVE
wird eigentlich nirgends verwiesen. Die letzte Nachricht, die ich finden kann (von 2008), besagt, dass sie auf einen Ersatz wartet.
Die Quintessenz ist, dass splice
von einer Datei zur anderen ruft memcpy
auf um die Daten zu verschieben; es ist nicht Nullkopie. Das ist nicht viel besser als im Userspace mit read
/write
mit kleinen Puffern, also können Sie genauso gut bei den standardmäßigen, portablen Schnittstellen bleiben.
Wenn "Seitendiebstahl" jemals wieder zum Linux-Kernel hinzugefügt wird, dann die Vorteile von splice
wäre viel größer. (Und selbst heute, wenn das Ziel ein Socket ist, erhalten Sie eine echte Zero-Copy, wodurch splice
attraktiver.) Aber für den Zweck dieser Frage, splice
kauft dir nicht viel.