c++ - open - fstream read




以理智,安全和有效的方式複 製文件 (4)

我尋找一種複製文件的好方法(二進製或文本)。 我寫了幾個樣本,每個人都工作。 但我希望聽到經驗豐富的程序員的意見。

我錯過了很好的示例並蒐索了一種與C ++一起工作的方式。

ANSI-C-WAY

#include <iostream>
#include <cstdio>    // fopen, fclose, fread, fwrite, BUFSIZ
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    // BUFSIZE default is 8192 bytes
    // BUFSIZE of 1 means one chareter at time
    // good values should fit to blocksize, like 1024 or 4096
    // higher values reduce number of system calls
    // size_t BUFFER_SIZE = 4096;

    char buf[BUFSIZ];
    size_t size;

    FILE* source = fopen("from.ogv", "rb");
    FILE* dest = fopen("to.ogv", "wb");

    // clean and more secure
    // feof(FILE* stream) returns non-zero if the end of file indicator for stream is set

    while (size = fread(buf, 1, BUFSIZ, source)) {
        fwrite(buf, 1, size, dest);
    }

    fclose(source);
    fclose(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " << end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

POSIX-WAY (K&R在“C編程語言”中使用它,更低級別)

#include <iostream>
#include <fcntl.h>   // open
#include <unistd.h>  // read, write, close
#include <cstdio>    // BUFSIZ
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    // BUFSIZE defaults to 8192
    // BUFSIZE of 1 means one chareter at time
    // good values should fit to blocksize, like 1024 or 4096
    // higher values reduce number of system calls
    // size_t BUFFER_SIZE = 4096;

    char buf[BUFSIZ];
    size_t size;

    int source = open("from.ogv", O_RDONLY, 0);
    int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);

    while ((size = read(source, buf, BUFSIZ)) > 0) {
        write(dest, buf, size);
    }

    close(source);
    close(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " << end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

KISS-C ++ - Streambuffer-WAY

#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    dest << source.rdbuf();

    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

COPY-算法-C ++ - WAY

#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <iterator>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    istreambuf_iterator<char> begin_source(source);
    istreambuf_iterator<char> end_source;
    ostreambuf_iterator<char> begin_dest(dest); 
    copy(begin_source, end_source, begin_dest);

    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

OWN-BUFFER-C ++ - WAY

#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    ifstream source("from.ogv", ios::binary);
    ofstream dest("to.ogv", ios::binary);

    // file size
    source.seekg(0, ios::end);
    ifstream::pos_type size = source.tellg();
    source.seekg(0);
    // allocate memory for buffer
    char* buffer = new char[size];

    // copy file    
    source.read(buffer, size);
    dest.write(buffer, size);

    // clean up
    delete[] buffer;
    source.close();
    dest.close();

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

LINUX-WAY //需要kernel> = 2.6.33

#include <iostream>
#include <sys/sendfile.h>  // sendfile
#include <fcntl.h>         // open
#include <unistd.h>        // close
#include <sys/stat.h>      // fstat
#include <sys/types.h>     // fstat
#include <ctime>
using namespace std;

int main() {
    clock_t start, end;
    start = clock();

    int source = open("from.ogv", O_RDONLY, 0);
    int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);

    // struct required, rationale: function stat() exists also
    struct stat stat_source;
    fstat(source, &stat_source);

    sendfile(dest, source, 0, stat_source.st_size);

    close(source);
    close(dest);

    end = clock();

    cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
    cout << "CPU-TIME START " << start << "\n";
    cout << "CPU-TIME END " << end << "\n";
    cout << "CPU-TIME END - START " <<  end - start << "\n";
    cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";

    return 0;
}

環境

  • GNU / LINUX(Archlinux)
  • 內核3.3
  • GLIBC-2.15,LIBSTDC ++ 4.7(GCC-LIBS),GCC 4.7,Coreutils 8.16
  • 使用RUNLEVEL 3(多用戶,網絡,終端,無GUI)
  • INTEL SSD-Postville 80 GB,填充率高達50%
  • 複製270 MB OGG-VIDEO-FILE

重現步驟

 1. $ rm from.ogg
 2. $ reboot                           # kernel and filesystem buffers are in regular
 3. $ (time ./program) &>> report.txt  # executes program, redirects output of program and append to file
 4. $ sha256sum *.ogv                  # checksum
 5. $ rm to.ogg                        # remove copy, but no sync, kernel and fileystem buffers are used
 6. $ (time ./program) &>> report.txt  # executes program, redirects output of program and append to file

結果(使用CPU時間)

Program  Description                 UNBUFFERED|BUFFERED
ANSI C   (fread/frwite)                 490,000|260,000  
POSIX    (K&R, read/write)              450,000|230,000  
FSTREAM  (KISS, Streambuffer)           500,000|270,000 
FSTREAM  (Algorithm, copy)              500,000|270,000
FSTREAM  (OWN-BUFFER)                   500,000|340,000  
SENDFILE (native LINUX, sendfile)       410,000|200,000  

文件大小不會更改。
sha256sum打印結果相同。
視頻文件仍可播放。

問題

  • 你會喜歡什麼方法?
  • 你知道更好的解決方案嗎?
  • 你在我的代碼中看到錯誤嗎?
  • 你知道避免解決方案的理由嗎?

  • FSTREAM(KISS,Streambuffer)
    我非常喜歡這個,因為它非常簡短。 目前為止,我知道operator <<被rdbuf()重載並且不會轉換任何東西。 正確?

謝謝

更新1
我以這種方式改變了所有樣本的源代碼,文件描述符的打開和關閉包含在clock()的測量中。 它們在源代碼中沒有其他重大變化。 結果沒有改變! 我也用時間來檢查我的結果。

更新2
改變了ANSI C樣本: while循環的條件不再調用feof(),而是將fread()移入條件中。 看起來,代碼現在運行速度快10,000個時鐘。

測量更改:以前的結果總是被緩衝,因為我重複了幾次每個程序的舊命令行rm to.ogv && sync && time ./program 。 現在我為每個程序重新啟動系統。 未經緩衝的結果是新的並且毫不奇怪。 未緩衝的結果並沒有真正改變。

如果我不刪除舊副本,程序反應不同。 使用POSIX和SENDFILE覆蓋緩衝的現有文件速度更快,所有其他程序速度更慢。 也許截斷創建的選項會影響此行為。 但用相同的副本覆蓋現有文件並不是真實世界的用例。

使用cp執行複制需要0.44秒未緩沖和0.30秒緩衝。 所以cp比POSIX示例慢一點。 對我來說看起來很好。

也許我還會從boost :: filesystem中添加樣本和mmap()copy_file()結果。

更新3
我也把它放在一個博客頁面上並稍微擴展一下。 包括splice() ,它是Linux內核的一個低級函數。 也許更多的Java示例會隨之而來。 http://www.ttyhoney.com/blog/?page_id=69


Qt有一個複製文件的方法:

#include <QFile>
QFile::copy("originalFile.example","copiedFile.example");

請注意,要使用此功能,您必須安裝Qt (說明位於here )並將其包含在您的項目中(如果您使用的是Windows且您不是管理員,則可以here下載Qt)。 也看到這個答案


以一種理智的方式複製文件:

int main()
{
    std::ifstream  src("from.ogv", std::ios::binary);
    std::ofstream  dst("to.ogv",   std::ios::binary);

    dst << src.rdbuf();
}

這是非常簡單和直觀的閱讀它是值得的額外成本。 如果我們做了很多事情,最好回到操作系統對文件系統的調用。 我確信boost在其文件系統類中有一個拷貝文件方法。

有一個用於與文件系統交互的C方法:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

太多!

由於FILE已被緩衝,“ANSI C”方式緩衝區是多餘的。 (這個內部緩衝區的大小是BUFSIZ實際定義的。)

在經過fstream ,“OWN-BUFFER-C ++ -WAY”會很慢,這會執行大量的虛擬調度,並且會再次維護內部緩衝區或每個流對象。 (“COPY-ALGORITHM-C ++ -WAY”不會遇到這種情況,因為streambuf_iterator類會繞過流層。)

我更喜歡“COPY-ALGORITHM-C ++ -WAY”,但是如果不構建fstream ,只需在不需要實際格式時創建裸std::filebuf實例即可。

對於原始性能,您無法擊敗POSIX文件描述符。 這是醜陋的,但在任何平台上便攜和快速。

Linux方式看起來非常快 - 也許OS在I / O完成之前讓函數返回? 無論如何,這對許多應用程序來說都不夠便攜。

編輯 :啊,“本地Linux”可能會通過交錯讀取和寫入異步I / O來提高性能。 讓命令堆積起來可以幫助磁盤驅動程序決定何時最好查找。 您可以嘗試Boost Asio或pthreads進行比較。 至於“不能擊敗POSIX文件描述符”......如果你對數據做任何事情,那麼這是真的,而不是盲目地複制。


我想說明一點,使用sendfile()的LINUX方法存在一個主要問題,它不能複制超過2GB的文件! 我在這個問題之後實現了它,並且遇到了問題,因為我使用它來複製大小為GB的HDF5文件。

http://man7.org/linux/man-pages/man2/sendfile.2.html

sendfile()將最多傳輸0x7ffff000(2,147,479,552)字節,返回實際傳輸的字節數。 (在32位和64位系統上都是如此。)





file-io