Подтвердить что ты не робот

Самый эффективный способ скопировать файл в Linux

Я работаю в независимом файловом менеджере ОС, и я рассматриваю наиболее эффективный способ скопировать файл для Linux. Windows имеет встроенную функцию CopyFileEx(), но из того, что я заметил, такой стандартной функции для Linux нет. Поэтому я предполагаю, что мне придется реализовать свои собственные. Очевидным способом является fopen/fread/fwrite, но есть ли лучший (более быстрый) способ сделать это? Я также должен иметь возможность останавливаться каждый раз, чтобы я мог обновить счет "скопированных до сих пор" для меню прогресса файла.

4b9b3361

Ответ 1

К сожалению, вы не можете использовать sendfile() здесь, потому что назначение не является сокетом. (Имя sendfile() происходит от send() + "файла" ).

Для нулевой копии вы можете использовать splice(), как было предложено @Dave. (За исключением того, что он не будет нулевой копией, он будет "одной копией" из кеша страницы исходного файла в кеш страницы страницы целевого файла.)

Однако... (a) splice() зависит от Linux; и (б) вы почти наверняка можете сделать так же, используя портативные интерфейсы, если вы используете их правильно.

Короче говоря, используйте open() + read() + write() с небольшим временным буфером. Я предлагаю 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 result = read(in_fd, &buf[0], sizeof(buf));
    if (!result) break;
    assert(result > 0);
    assert(write(out_fd, &buf[0], result) == result);
}

С помощью этого цикла вы будете копировать 8K из кеша страницы in_fd в кеш процессора L1, а затем записывать его из кеша L1 в кеш страницы out_fd. Затем вы перезапишете эту часть кеша L1 следующим блоком 8K из файла и так далее. Конечным результатом является то, что данные в buf никогда вообще не будут храниться в основной памяти (за исключением, может быть, один раз в конце); с точки зрения ОЗУ системы, это так же хорошо, как использование "нулевой копии" splice(). Кроме того, он отлично переносится в любую систему POSIX.

Обратите внимание, что здесь используется небольшой буфер. Типичные современные процессоры имеют 32K или около того для кэша данных L1, поэтому, если вы сделаете буфер слишком большим, этот подход будет медленнее. Возможно много, гораздо медленнее. Поэтому держите буфер в диапазоне "несколько килобайт".

Конечно, если ваша дисковая подсистема очень быстрая, пропускная способность памяти, вероятно, не является вашим ограничивающим фактором. Поэтому я бы рекомендовал posix_fadvise, чтобы ядро ​​узнало, что вы делаете:

posix_fadvise(in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);

Это даст подсказку ядру Linux о том, что его механизм с открытым исходным кодом должен быть очень агрессивным.

Я также предложил бы использовать posix_fallocate для предопределения хранилища для целевого файла. Это покажет вам, будет ли у вас закончиться диск. А для современного ядра с современной файловой системой (например, XFS) это поможет уменьшить фрагментацию в целевом файле.

Последнее, что я бы рекомендовал, - mmap. Это, как правило, самый медленный подход во всем, благодаря избиению TLB. (Очень свежие ядра с "прозрачными огромными страницами" могли бы смягчить это: я не пробовал в последнее время, но он, конечно, был очень плохим. Поэтому я бы только испытал тестирование mmap, если у вас есть много времени для тестирования и очень недавнего ядра.)

[Обновление]

В комментариях есть вопрос о том, является ли splice из одного файла в другой нулевой копией. Разработчики ядра Linux называют эту "кражу страницы". Как справочная страница для splice, так и комментариев в источнике ядра говорят, что флаг SPLICE_F_MOVE должен обеспечивать эту функциональность.

К сожалению, поддержка SPLICE_F_MOVE была yanked в 2.6.21 (назад в 2007) и никогда не заменялась. (Комментарии в источниках ядра никогда не обновлялись.) Если вы ищите исходные файлы ядра, вы обнаружите, что SPLICE_F_MOVE на самом деле не ссылается нигде. последнее сообщение, которое я могу найти (с 2008 года), говорит, что оно "ждет замены".

В нижней строке указано, что splice из одного файла в другой вызывает memcpy для перемещения данных; это не нуль-копия. Это не намного лучше, чем вы можете сделать в пользовательском пространстве с помощью read/write с небольшими буферами, поэтому вы можете также придерживаться стандартных портативных интерфейсов.

Если "кража страницы" когда-либо добавляется обратно в ядро ​​Linux, преимущества splice будут намного больше. (И даже сегодня, когда пункт назначения является сокетом, вы получаете истинную нулевую копию, делая splice более привлекательным.) Но для целей этого вопроса splice не покупает вас очень много.

Ответ 2

Используйте open/read/write — они избегают буферизации уровня libc, выполняемой fopen и друзьями.

В качестве альтернативы, если вы используете GLib, вы можете использовать его функцию g_copy_file.

Наконец, что может быть быстрее, но оно должно быть проверено, чтобы быть уверенным: используйте open и mmap для карты памяти для входного файла, затем write из области памяти в выходной файл. Вероятно, вы захотите оставить open/read/write в качестве резервной копии, так как этот метод ограничивается размером адресного пространства вашего процесса.

Изменить: оригинальный ответ предложил сопоставить оба файла; @bdonlan сделал отличное предложение в комментарии, чтобы отобразить только карту.

Ответ 3

Если вы знаете, что они будут использовать linux > 2.6.17, splice() - это способ сделать нуль-копию в linux:

 //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);

Ответ 4

Возможно, вы захотите сравнить команду dd