ファイル記述子fd、オフセット、および長さがあり、 fd で記述されたファイルのオフセットから length バイトを書き込む必要があります(注: NULL
ファイルの最後には決して発生しません)。
s で満たされたバッファを使用しNULL
てループで繰り返し書き込む以外に、それを行う効率的な方法はありますか? sのシーケンスNULL
は最大 16Mo になる可能性があり、現在、サイズ 512 (= ~30k の呼び出しwrite(2)
) のバッファを使用しています。
ファイル記述子fd、オフセット、および長さがあり、 fd で記述されたファイルのオフセットから length バイトを書き込む必要があります(注: NULL
ファイルの最後には決して発生しません)。
s で満たされたバッファを使用しNULL
てループで繰り返し書き込む以外に、それを行う効率的な方法はありますか? sのシーケンスNULL
は最大 16Mo になる可能性があり、現在、サイズ 512 (= ~30k の呼び出しwrite(2)
) のバッファを使用しています。
目的のオフセットでファイルを試しmmap
、必要なサイズで正確にマッピングしてから、単に を呼び出すことができmemset
ます。
編集: @jthill によって投稿されたコードに基づいて、比較を行う方法を示す簡単な例を次に示します。
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void create(int fsize)
{
FILE *fd = fopen("data", "wb");
fseek(fd, fsize - 1, SEEK_SET);
fputc(0, fd);
fclose(fd);
}
void seek_write(const char* data, int wsize, int seek, int dsize)
{
int fd = open("data", O_RDWR);
// Now seek_write
if (lseek(fd, seek, SEEK_SET) != seek)
perror("seek?"), abort();
// Now write in requested blocks..
for (int c = dsize / wsize; c--;)
if (write(fd, data, wsize) != wsize)
perror("write?"), abort();
close(fd);
}
void mmap_memset(int wsize, int seek, int dsize)
{
int fd = open("data", O_RDWR);
void* map = mmap(0, dsize + seek, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED)
perror("mmap?"), abort();
memset((char*)map + seek, 0, dsize);
munmap(map, dsize);
close(fd);
}
int main(int c, char **v)
{
struct timeval start, end;
long long ts1, ts2;
int wsize = c>1 ? atoi(*++v) : 512;
int seek = c>2 ? atoi(*++v) : 0;
int reps = c>3 ? atoi(*++v) : 1000;
int dsize = c>4 ? atoi(*++v) : 16*1024*1024;
int fsize = c>5 ? atoi(*++v) : 32*1024*1024;
// Create the file and grow...
create(fsize);
char *data = mmap(0, wsize, PROT_READ, MAP_ANON | MAP_PRIVATE, 0, 0);
printf("Starting write...\n");
gettimeofday(&start, NULL);
for (int i = 0;i < reps; ++i)
seek_write(data, wsize, seek, dsize);
gettimeofday(&end, NULL);
ts1 = ((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec);
printf("Starting mmap...\n");
gettimeofday(&start, NULL);
for (int i = 0;i < reps; ++i)
mmap_memset(wsize, seek, dsize);
gettimeofday(&end, NULL);
ts2 = ((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec);
printf("write: %lld us, %f us\nmmap: %lld us, %f us", ts1, (double)ts1/reps, ts2, (double)ts2/reps);
}
注:mmap
提供されたオフセットが整列されていない場合 (通常はページ境界上) は好まないため、長さ + オフセットでマップし、単にオフセットから設定できる場合 (または、保証できる場合) はおそらくより良いでしょう適切に配置されたオフセット、これも機能します..)
ご覧のとおり、2 つの操作の違いはlseek
( map + seek
) とwrite
( memset
) です。これは公正な比較だと思います (誰かが何かを修正したい場合は、お気軽に。)
私も ではなく を使用MAP_SHARED
しMAP_PRIVATE
ています。この 2 つには大きな違いがあります。後者はコピー オン ライトを実行するため、はるかに遅くなる可能性があります。
私のそれほど強力ではないシステムでは、次のようになります。
> ./fwrite 4096 1234
> Starting write...
> Starting mmap...
> write: 14767898 us, 14767.898000 us
> mmap: 6619623 us, 6619.623000 us
mmap
+memset
の方が速いことを示していると思いますか?
以下から、16M の I/O が 1 回だけ不適切に実行された場合、20ms を超えています。それはそれ自体で知覚できるほど近づいています。
ポイント:
メモリ内またはディスク上のオフセットはそれほど重要ではないので、calloc
うまくいくはずです(試してみることができます)。
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
int main(int c, char **v)
{
int wsiz = c>1 ? atoi(*++v) : 512;
int seek = c>2 ? atoi(*++v) : 0;
int woff = c>3 ? atoi(*++v) : 0;
int fsiz = c>4 ? atoi(*++v) : 16 * 1024 * 1024;
int reps = c>5 ? atoi(*++v) : 1000;
printf("writesize %d, seek %d, align %d, filesize %d, reps %d\n",
wsiz, seek, woff, fsiz, reps);
if (wsiz<=0|seek<0|woff<0|fsiz<wsiz)
return 1;
int fd = open("data", O_CREAT | O_RDWR, 0700);
char *data = mmap(0, 2*wsiz, PROT_READ, MAP_ANON | MAP_PRIVATE, 0, 0);
for (int t = reps; t--; lseek(fd, seek, 0))
for (int c = fsiz / wsiz; c--;)
if (write(fd, data+woff, wsiz) != wsiz)
perror("write?"), abort();
return close(fd);
}
cc -o bin/wipetest -g -O --std=c11 -march=native -pipe -Wall -Wno-parentheses wipetest.c
------------------------------------------------------
writesize 512, seek 0, align 0, filesize 16777216, reps 1000
real 0m20.727s
user 0m0.513s
sys 0m20.220s
------------------------------------------------------
writesize 4096, seek 0, align 0, filesize 16777216, reps 1000
real 0m3.889s
user 0m0.077s
sys 0m3.687s
------------------------------------------------------
writesize 16777216, seek 0, align 0, filesize 16777216, reps 1000
real 0m3.205s
user 0m0.000s
sys 0m3.203s
------------------------------------------------------
writesize 512, seek 500, align 0, filesize 16777216, reps 1000
real 0m23.829s
user 0m0.463s
sys 0m23.247s
------------------------------------------------------
writesize 4096, seek 500, align 0, filesize 16777216, reps 1000
real 0m5.531s
user 0m0.053s
sys 0m5.480s
------------------------------------------------------
writesize 16777216, seek 500, align 0, filesize 16777216, reps 1000
real 0m3.435s
user 0m0.000s
sys 0m3.433s
------------------------------------------------------
writesize 512, seek 0, align 12, filesize 16777216, reps 1000
real 0m21.478s
user 0m0.537s
sys 0m20.820s
------------------------------------------------------
writesize 4096, seek 0, align 12, filesize 16777216, reps 1000
real 0m3.722s
user 0m0.057s
sys 0m3.667s
------------------------------------------------------
writesize 16777216, seek 0, align 12, filesize 16777216, reps 1000
real 0m3.232s
user 0m0.000s
sys 0m3.233s
------------------------------------------------------
writesize 512, seek 500, align 12, filesize 16777216, reps 1000
real 0m23.775s
user 0m0.550s
sys 0m23.113s
------------------------------------------------------
writesize 4096, seek 500, align 12, filesize 16777216, reps 1000
real 0m5.566s
user 0m0.050s
sys 0m5.517s
------------------------------------------------------
writesize 16777216, seek 500, align 12, filesize 16777216, reps 1000
real 0m3.277s
user 0m0.000s
sys 0m3.277s
splice(2)
Linux では、 からデータをコピーするために使用できます/dev/zero
。
ほとんどの作業はカーネル内で行われるため、これは非常に効率的です。
他のオペレーティング システムでも同様の機能が提供されている場合があります (例: sendfile
)。
アップデート!
fallocate(2)
ファイルの真ん中に穴を開けることができることを忘れていました。
lseek()
位置とwrite()
大きなバッファーに 1 回 (または小さなバッファーを複数回) に移動します。