C言語: UNIX最速ファイルコピー
Created: Kazuki Ohta, 2006/06/14
Last Update: Kazuki Ohta, 2006/06/14
Last Update: Kazuki Ohta, 2006/06/14
「write(2)の正しい使い方」と同じく、OS演習でやった事の延長線の記事を書いてみる。お題は「UNIX上で大規模ファイルを最速でコピーする方法」だ。一般的に、UNIXでファイルをcopyする際には以下のような方法が有る。
- read -> write
- read -> write with posix_fadvice
- mmap -> mmap -> memcpy -> fsync
- mmap -> mmap -> memcpy -> fsync with madvise
- mmap -> write
- mmap -> write with madvise
実装(0)
以下に挙げる方法のソースコードを全て固めたものを置いておく。実装(1): read -> write based
-DUSE_FADVISEフラグでposix_fadviseを使用するか否かを決める。
#define BUF_SIZE 8192
int main(int argc, char **argv)
{
char buf[BUF_SIZE];
struct stat st_buf;
if (argc < 3) {
fprintf(stderr, "usage: %s src out\n", argv[0]);
exit(EXIT_SUCCESS);
}
char *src = argv[1];
char *dest = argv[2];
assert(strcmp(src, dest) != 0);
int srcfd = open(src, O_RDONLY, 0644);
assert(srcfd >= 0);
#if USE_FADVISE
posix_fadvise(srcfd, 0, 0, POSIX_FADV_SEQUENTIAL);
posix_fadvise(srcfd, 0, 0, POSIX_FADV_NOREUSE);
#endif
/* get permission */
assert(fstat(srcfd, &st_buf) >= 0);
int destfd = open(dest, O_WRONLY | O_CREAT, st_buf.st_mode);
assert(destfd >= 0);
int n = 0;
while ((n = read(srcfd, buf, sizeof(buf))) > 0) {
char *p = &buf[0];
const char * const endp = buf + n;
while (p < endp) {
int num_bytes = write(destfd, p, endp - p);
p += num_bytes;
}
}
assert(n == 0);
fsync(destfd);
close(destfd);
close(srcfd);
exit(EXIT_SUCCESS);
}
実装(2): mmap -> mmap based
-DUSE_MADVISEでmadviseを使うかどうかを決める。
int main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(stderr, "usage: %s from_file to_file", argv[0]);
exit(0);
}
/* from */
int from = open(argv[1], O_RDONLY, 0644);
assert(from >= 0);
struct stat st_buf;
assert(fstat(from, &st_buf) >= 0);
size_t size = st_buf.st_size;
void *from_mmap = mmap(NULL, size, PROT_READ, MAP_PRIVATE, from, 0);
assert(from_mmap >= 0);
#if USE_MADVISE
assert(madvise(from_mmap, size, MADV_SEQUENTIAL) >= 0);
#endif
/* to */
int to = open(argv[2], O_CREAT|O_RDWR, st_buf.st_mode);
assert(to >= 0);
int i = 0;
assert(lseek(to, size - sizeof(int), 0L) >= 0);
assert(write(to, (&i), sizeof(int)) == sizeof(int));
void *to_mmap = mmap(NULL, size, PROT_WRITE, MAP_SHARED, to, 0);
assert(to_mmap >= 0);
#if USE_MADVISE
assert(madvise(to_mmap, size, MADV_SEQUENTIAL) >= 0);
#endif
/* copy */
memcpy(to_mmap, from_mmap, size);
assert(msync(to_mmap, size, MS_SYNC) >= 0);
assert(ftruncate(to, size) >= 0);
return 1;
}
実装(3): mmap -> write based
-DUSE_MADVISEでmadviseを使うかどうかを決める。
int main(int argc, char *argv[])
{
if (argc != 3) {
fprintf(stderr, "usage: %s from_file to_file", argv[0]);
exit(0);
}
/* from */
int from = open(argv[1], O_RDONLY, 0644);
assert(from >= 0);
struct stat st_buf;
assert(fstat(from, &st_buf) >= 0);
size_t size = st_buf.st_size;
void *from_mmap = mmap(NULL, size, PROT_READ, MAP_PRIVATE, from, 0);
assert(from_mmap >= 0);
#if USE_MADVISE
assert(madvise(from_mmap, size, MADV_SEQUENTIAL) >= 0);
#endif
/* to */
int to = open(argv[2], O_CREAT | O_WRONLY, 0666);
assert(to >= 0);
/* copy */
char *p = from_mmap;
const char * const endp = from_mmap + size;
while (p < endp) {
int num_bytes = write(to, p, endp - p);
p += num_bytes;
}
assert(p == endp);
fsync(to);
close(to);
close(from);
return 1;
}
検証
以上のソースを用いて様々なファイルをコピーし、時間を計測してみた。各方式につき3回コピーを行い、一番早いタイムを計測した。ただし、環境によって変わってくるので、検証対象環境について一度プログラムを動かしてみるのが望ましい。環境(1)
- Thikpad X31
- Linux ubuntu 2.6.15-23-386 #1 PREEMPT Tue May 23 13:49:40 UTC 2006 i686 GNU/Linux
- glibc 2.3.6-0ubuntu20
| rw | rw_fadv | mm_sync | mm_sync_madv | mw | mw_madv | |
| 1MB | 0.065 | 0.060 | 0.030 | 0.068 | 0.052 | 0.032 |
| 10MB | 0.654 | 0.669 | 0.641 | 0.638 | 0.601 | 0.658 |
| 100MB | 6.397 | 6.738 | 6.813 | 6.860 | 6.733 | 6.541 |
| 500MB | 1:11.72 | 1:06.76 | 1:32.30 | 1:35.57 | 55.944 | 1:00.63 |
| 1GB | 2:17.60 | 2:09.61 | 3:30.32 | 3:47.09 | 2:47.38 | 2:27.34 |
[ return ]