К сожалению, вы не можете использовать sendfile()
здесь, потому что пункт назначения не является сокетом. (Имя sendfile()
происходит от send()
+ "файл").
Для нулевого копирования вы можете использовать splice()
, как предложено @Dave. (За исключением того, что это не будет нулевая копия; это будет "одна копия" из кэша страниц исходного файла в кэш страницы конечного файла.)
Однако ... (a) splice()
зависит от Linux; и (b) вы почти наверняка можете сделать то же самое, используя переносные интерфейсы, при условии, что вы используете их правильно.
Короче говоря, используйте open()
+ read()
+ write()
с временным буфером small . Я предлагаю 8K. Итак, ваш код будет выглядеть примерно так:
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);
}
С помощью этого цикла вы будете копировать 8K из кэша страниц in_fd в кэш L1 ЦП, а затем записывать его из кэша L1 в кэш страниц out_fd. Затем вы перезапишете эту часть кэша L1 с помощью следующего фрагмента 8 КБ из файла и так далее. Конечным результатом является то, что данные в buf
вообще никогда не будут храниться в основной памяти (за исключением, может быть, одного раза в конце); с точки зрения системного ОЗУ, это так же хорошо, как использование «нулевой копии» splice()
. Кроме того, он идеально переносим на любую систему POSIX.
Обратите внимание, что здесь используется небольшой буфер. Типичные современные процессоры имеют 32K или около того для кэша данных L1, поэтому, если вы сделаете буфер слишком большим, этот подход будет медленнее. Возможно, намного, намного медленнее. Поэтому держите буфер в диапазоне «несколько килобайт».
Конечно, если ваша дисковая подсистема не очень быстра, пропускная способность памяти, вероятно, не является вашим ограничивающим фактором. Поэтому я бы порекомендовал posix_fadvise
, чтобы сообщить ядру, что вы делаете:
posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
Это даст подсказку ядру Linux, что его механизм упреждающего чтения должен быть очень агрессивным.
Я бы также предложил использовать posix_fallocate
для предварительного выделения хранилища для файла назначения. Это заранее скажет вам, закончится ли у вас диск. А для современного ядра с современной файловой системой (например, XFS) это поможет уменьшить фрагментацию в конечном файле.
Последнее, что я бы порекомендовал, это mmap
. Обычно это самый медленный подход из-за обмолота TLB. (Очень недавние ядра с «прозрачными огромными страницами» могли бы смягчить это; я недавно не пробовал. Но это, конечно, было очень плохо. Поэтому я бы потрудился протестировать mmap
только если у вас есть достаточно времени для тестирования и совсем недавнего ядра .)
[Update]
В комментариях есть вопрос о том, является ли splice
из одного файла в другой нулевой копией. Разработчики ядра Linux называют это «воровством страниц». И man-страница для splice
, и комментарии в исходном тексте ядра говорят, что флаг SPLICE_F_MOVE
должен обеспечивать эту функциональность.
К сожалению, поддержка SPLICE_F_MOVE
была восстановлена в 2.6.21 (в 2007 году) и никогда не заменялась. (Комментарии в исходных текстах ядра никогда не обновлялись.) При поиске в исходных кодах ядра вы обнаружите, что SPLICE_F_MOVE
фактически нигде не упоминается. Последнее сообщение, которое я могу найти (с 2008 года) говорит, что оно "ожидает замены".
Суть в том, что splice
из одного файла в другой вызывает memcpy
для перемещения данных; это не ноль-копия. Это не намного лучше, чем вы можете сделать в пользовательском пространстве, используя read
/ write
с небольшими буферами, так что вы также можете придерживаться стандартных переносимых интерфейсов.
Если "воровство страниц" когда-либо будет добавлено обратно в ядро Linux, тогда преимущества splice
будут гораздо больше. (И даже сегодня, когда местом назначения является сокет, вы получаете истинную нулевую копию, что делает splice
более привлекательным.) Но для целей этого вопроса splice
не очень-то вас покупает.