9

私は1GBを超える多くの大きなファイルを保存するプログラムを持っていfwriteますfwrite. その結果、書き込みには 1 時間以上かかる場合があります。この時間のほとんどは、syscall のオーバーヘッド (または少なくとも fwrite のライブラリ関数) によるものと思われます。にも同様の問題がありfreadます。

これらの書き込みと読み取りをインライン関数でバッファリングする既存の/ライブラリ関数を知っている人はいますか?それとも、これはあなた自身の別のロールですか?

4

7 に答える 7

4

わかりました、まあ、それは面白かったです。実際のコードを書いて速度を確認しようと思いました。そして、ここにあります。C++ DevStudio 2010 Express を使用してコンパイルされています。ここにはかなりのコードがあります。データを書き込む 5 つの方法の時間を計ります。

  • 単純に fwrite を呼び出す
  • バッファを使用し、より大きなバッファを使用して fwrite の呼び出しを減らす
  • Win32 API を単純に使用する
  • バッファーを使用し、より大きなバッファーを使用して Win32 への呼び出しを減らす
  • Win32 を使用するが、出力をダブル バッファリングし、非同期書き込みを使用する

上記のいずれかで少しばかげたことをしていないことを確認してください。

プログラムは、コードのタイミングを計るために QueryPerformanceCounter を使用し、ファイルが閉じられた後にタイミングを終了して、保留中の内部バッファー データを含めようとします。

私のマシン (古い WinXP SP3 ボックス) での結果:-

  • fwrite 自体は一般的に最速ですが、サイズと反復が適切であれば、バッファ付きバージョンがそれを上回ることがあります。
  • Naive Win32 は大幅に遅い
  • バッファリングされた Win32 は速度を 2 倍にしますが、それでも fwrite に簡単に打ち負かされます
  • 非同期書き込みは、バッファリングされたバージョンよりも大幅に優れているわけではありません. おそらく、誰かが私のコードをチェックして、非同期 IO を実際に使用したことがないので、私が愚かなことをしていないことを確認することができます。

設定によっては、異なる結果が得られる場合があります。

コードを自由に編集して改善してください。

    #define _CRT_SECURE_NO_WARNINGS

    #include <stdio.h>
    #include <memory.h>
    #include <Windows.h>

    const int
        // how many times fwrite/my_fwrite is called
        c_iterations = 10000000,
        // the size of the buffer used by my_fwrite
        c_buffer_size = 100000;

    char 
        buffer1 [c_buffer_size],
        buffer2 [c_buffer_size],
        *current_buffer = buffer1;

    int
        write_ptr = 0;

    __int64
        write_offset = 0;

    OVERLAPPED
        overlapped = {0};

    // write to a buffer, when buffer full, write the buffer to the file using fwrite
    void my_fwrite (void *ptr, int size, int count, FILE *fp)
    {
        const int
            c = size * count;

        if (write_ptr + c > c_buffer_size)
        {
            fwrite (buffer1, write_ptr, 1, fp);
            write_ptr = 0;
        }

        memcpy (&buffer1 [write_ptr], ptr, c);
        write_ptr += c;
    }

    // write to a buffer, when buffer full, write the buffer to the file using Win32 WriteFile
    void my_fwrite (void *ptr, int size, int count, HANDLE fp)
    {
        const int
            c = size * count;

        if (write_ptr + c > c_buffer_size)
        {
            DWORD
                written;

            WriteFile (fp, buffer1, write_ptr, &written, 0);
            write_ptr = 0;
        }

        memcpy (&buffer1 [write_ptr], ptr, c);
        write_ptr += c;
    }

    // write to a double buffer, when buffer full, write the buffer to the file using 
    // asynchronous WriteFile (waiting for previous write to complete)
    void my_fwrite (void *ptr, int size, int count, HANDLE fp, HANDLE wait)
    {
        const int
            c = size * count;

        if (write_ptr + c > c_buffer_size)
        {
            WaitForSingleObject (wait, INFINITE);

            overlapped.Offset = write_offset & 0xffffffff;
            overlapped.OffsetHigh = write_offset >> 32;
            overlapped.hEvent = wait;

            WriteFile (fp, current_buffer, write_ptr, 0, &overlapped);
            write_offset += write_ptr;
            write_ptr = 0;
            current_buffer = current_buffer == buffer1 ? buffer2 : buffer1;
        }

        memcpy (current_buffer + write_ptr, ptr, c);
        write_ptr += c;
    }

    int main ()
    {
        // do lots of little writes
        FILE
            *f1 = fopen ("f1.bin", "wb");

        LARGE_INTEGER
            f1_start,
            f1_end;

        QueryPerformanceCounter (&f1_start);

        for (int i = 0 ; i < c_iterations ; ++i)
        {
            fwrite (&i, sizeof i, 1, f1);
        }

        fclose (f1);

        QueryPerformanceCounter (&f1_end);

        // do a few big writes
        FILE
            *f2 = fopen ("f2.bin", "wb");

        LARGE_INTEGER
            f2_start,
            f2_end;

        QueryPerformanceCounter (&f2_start);

        for (int i = 0 ; i < c_iterations ; ++i)
        {
            my_fwrite (&i, sizeof i, 1, f2);
        }

        if (write_ptr)
        {
            fwrite (buffer1, write_ptr, 1, f2);
            write_ptr = 0;
        }

        fclose (f2);

        QueryPerformanceCounter (&f2_end);

        // use Win32 API, without buffer
        HANDLE
            f3 = CreateFile (TEXT ("f3.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

        LARGE_INTEGER
            f3_start,
            f3_end;

        QueryPerformanceCounter (&f3_start);

        for (int i = 0 ; i < c_iterations ; ++i)
        {
            DWORD
                written;

            WriteFile (f3, &i, sizeof i, &written, 0);
        }

        CloseHandle (f3);

        QueryPerformanceCounter (&f3_end);

        // use Win32 API, with buffer
        HANDLE
            f4 = CreateFile (TEXT ("f4.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, 0);

        LARGE_INTEGER
            f4_start,
            f4_end;

        QueryPerformanceCounter (&f4_start);

        for (int i = 0 ; i < c_iterations ; ++i)
        {
            my_fwrite (&i, sizeof i, 1, f4);
        }

        if (write_ptr)
        {
            DWORD
                written;

            WriteFile (f4, buffer1, write_ptr, &written, 0);
            write_ptr = 0;
        }

        CloseHandle (f4);

        QueryPerformanceCounter (&f4_end);

        // use Win32 API, with double buffering
        HANDLE
            f5 = CreateFile (TEXT ("f5.bin"), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH, 0),
            wait = CreateEvent (0, false, true, 0);

        LARGE_INTEGER
            f5_start,
            f5_end;

        QueryPerformanceCounter (&f5_start);

        for (int i = 0 ; i < c_iterations ; ++i)
        {
            my_fwrite (&i, sizeof i, 1, f5, wait);
        }

        if (write_ptr)
        {
            WaitForSingleObject (wait, INFINITE);

            overlapped.Offset = write_offset & 0xffffffff;
            overlapped.OffsetHigh = write_offset >> 32;
            overlapped.hEvent = wait;

            WriteFile (f5, current_buffer, write_ptr, 0, &overlapped);
            WaitForSingleObject (wait, INFINITE);
            write_ptr = 0;
        }

        CloseHandle (f5);

        QueryPerformanceCounter (&f5_end);

        CloseHandle (wait);

        LARGE_INTEGER
            freq;

        QueryPerformanceFrequency (&freq);

        printf ("  fwrites without buffering = %dms\n", (1000 * (f1_end.QuadPart - f1_start.QuadPart)) / freq.QuadPart);
        printf ("     fwrites with buffering = %dms\n", (1000 * (f2_end.QuadPart - f2_start.QuadPart)) / freq.QuadPart);
        printf ("    Win32 without buffering = %dms\n", (1000 * (f3_end.QuadPart - f3_start.QuadPart)) / freq.QuadPart);
        printf ("       Win32 with buffering = %dms\n", (1000 * (f4_end.QuadPart - f4_start.QuadPart)) / freq.QuadPart);
        printf ("Win32 with double buffering = %dms\n", (1000 * (f5_end.QuadPart - f5_start.QuadPart)) / freq.QuadPart);
    }
于 2012-11-28T10:04:55.910 に答える
1

First and foremost: small fwrites() are slower, because each fwrite has to test the validity of its parameters, do the equivalent of flockfile(), possibly fflush(), append the data, return success: this overhead adds up -- not so much as tiny calls to write(2), but it's still noticeable.

Proof:

#include <stdio.h>
#include <stdlib.h>

static void w(const void *buf, size_t nbytes)
{
    size_t n;
    if(!nbytes)
        return;
    n = fwrite(buf, 1, nbytes, stdout);
    if(n >= nbytes)
        return;
    if(!n) {
        perror("stdout");
        exit(111);
    }
    w(buf+n, nbytes-n);
}

/* Usage: time $0 <$bigfile >/dev/null */
int main(int argc, char *argv[])
{
    char buf[32*1024];
    size_t sz;

    sz = atoi(argv[1]);
    if(sz > sizeof(buf))
        return 111;
    if(sz == 0)
        sz = sizeof(buf);
    for(;;) {
        size_t r = fread(buf, 1, sz, stdin);
        if(r < 1)
            break;
        w(buf, r);
    }
    return 0;
}

That being said, you could do what many commenters suggested, ie add your own buffering before fwrite: it's very trivial code, but you should test if it really gives you any benefit.

If you don't want to roll your own, you can use eg the buffer interface in skalibs, but you'll probably take longer to read the docs than to write it yourself (imho).

于 2012-11-27T22:50:07.100 に答える
0

stdio の FILE * レイヤーのポイントは、バッファリングを行うことです。これにより、システム コールのオーバーヘッドを節約できます。他の人が指摘したように、まだ問題になる可能性があるのは、かなり小さいライブラリ呼び出しのオーバーヘッドです。もう 1 つの問題は、ディスク上のさまざまな場所に同時に書き込むことです。(ディスクが回転し、ヘッドがランダム書き込みの適切な場所に到達するまでにおよそ 8 ミリ秒かかります。)

ライブラリ呼び出しのオーバーヘッドが問題であると判断した場合は、ベクターを使用して独自の単純なバッファリングを展開し、定期的にベクターをファイルにフラッシュすることをお勧めします。

大量の書き込みがディスク全体に分散していることが問題である場合は、setvbuf() を使用してバッファ サイズを大きくしてみてください。可能であれば、1 ファイルあたり約 4MB の数値を試してください。

于 2012-11-27T16:59:31.930 に答える