Pokud víte, že budou používat linux> 2.6.17, splice()
je způsob, jak provést nulové kopírování v linuxu:
//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);
Bohužel nemůžete použít sendfile()
zde, protože cíl není zásuvka. (Název sendfile()
pochází z send()
+ "soubor").
Pro nulovou kopii můžete použít splice()
jak navrhuje @Dave. (Kromě toho, že to nebude nulová kopie; bude to „jedna kopie“ z mezipaměti stránek zdrojového souboru do mezipaměti stránek cílového souboru.)
Nicméně... (a) splice()
je specifický pro Linux; a (b) téměř jistě můžete stejně dobře používat přenosná rozhraní, pokud je používáte správně.
Stručně řečeno, použijte open()
+ read()
+ write()
s malým dočasný buffer. Doporučuji 8K. Váš kód by tedy vypadal nějak takto:
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);
}
Pomocí této smyčky budete kopírovat 8 kB z mezipaměti stránek in_fd do mezipaměti CPU L1 a poté je zapsat z mezipaměti L1 do mezipaměti stránek out_fd. Poté přepíšete tuto část mezipaměti L1 dalším 8K blokem ze souboru a tak dále. Čistým výsledkem je, že data v buf
ve skutečnosti se nikdy neuloží do hlavní paměti (s výjimkou možná jednou na konci); z hlediska systémové RAM je to stejně dobré jako použití "nulové kopie" splice()
. Navíc je dokonale přenosný na jakýkoli systém POSIX.
Všimněte si, že malá vyrovnávací paměť je zde klíčová. Typické moderní CPU mají 32K nebo tak pro datovou mezipaměť L1, takže pokud uděláte vyrovnávací paměť příliš velkou, bude tento přístup pomalejší. Možná mnohem, mnohem pomaleji. Udržujte tedy vyrovnávací paměť v rozsahu „několik kilobajtů“.
Samozřejmě, pokud váš diskový subsystém není velmi rychlý, šířka pásma paměti pravděpodobně není vaším omezujícím faktorem. Takže bych doporučil posix_fadvise
aby jádro vědělo, co chystáte:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
To dá linuxovému jádru náznak, že jeho mašinérie pro čtení napřed by měla být velmi agresivní.
Také bych doporučil použít posix_fallocate
k předběžnému přidělení úložiště pro cílový soubor. To vám předem řekne, zda vám dojde disk. A pro moderní jádro s moderním souborovým systémem (jako XFS) to pomůže snížit fragmentaci v cílovém souboru.
Poslední věc, kterou bych doporučil, je mmap
. Je to obvykle nejpomalejší přístup ze všech díky TLB thrashingu. (Nejnovější jádra s "transparentními obrovskými stránkami" by to mohla zmírnit; nedávno jsem to nezkoušel. Ale určitě to bývalo velmi špatné. Takže bych se obtěžoval pouze testováním mmap
pokud máte spoustu času na benchmark a velmi nedávné jádro.)
[Aktualizovat]
V komentářích je nějaká otázka, zda splice
z jednoho souboru do druhého je nulová kopie. Vývojáři linuxového jádra tomu říkají „kradení stránek“. Obě manuálové stránky pro splice
a komentáře ve zdrojovém kódu jádra říkají, že SPLICE_F_MOVE
flag by měl tuto funkci poskytovat.
Bohužel podpora pro SPLICE_F_MOVE
byl vytržen v 2.6.21 (v roce 2007) a nikdy nebyl nahrazen. (Komentáře ve zdrojích jádra nebyly nikdy aktualizovány.) Pokud prohledáte zdroje jádra, najdete SPLICE_F_MOVE
není ve skutečnosti nikde odkazováno. Poslední zpráva, kterou jsem našel (z roku 2008), říká, že „čeká na náhradu“.
Pointa je, že splice
z jednoho souboru do druhého volá memcpy
přesunout data; není nulová kopie. To není o moc lepší, než můžete udělat v uživatelském prostoru pomocí read
/write
s malými vyrovnávací paměti, takže byste se mohli držet standardních přenosných rozhraní.
Pokud se někdy do linuxového jádra přidá „krádež stránek“, pak výhody splice
by bylo mnohem větší. (A i dnes, když je cílem soket, získáte skutečnou nulovou kopii, takže splice
atraktivnější.) Ale pro účely této otázky splice
vás moc nekoupí.