2013-01-23 32 views
12

So sánh đọc các file bằng cách sử dụng ba kỹ thuật sau đây:Tại sao C <stdio.h> FILE * fread() nhanh hơn Win32 ReadFile()?

  1. C <stdio.h>FILE*
  2. Win32 CreateFile()/ReadFile()
  3. lập bản đồ bộ nhớ Win32

tôi lưu ý rằng # 1 là nhanh hơn so với # 2, và # 3 là nhanh nhất.

ví dụ: Sắp xếp từ nhanh nhất đến chậm nhất, để xử lý một tập tin thử nghiệm 900MB, tôi có những kết quả này:

Win32 bộ nhớ bản đồ: 821,308 ms

tập tin

C (FILE *): 1779,83 ms

Win32 tệp (CreateFile): 3649.67 ms

Tại sao kỹ thuật C <stdio.h> nhanh hơn truy cập Win32 ReadFile()? Tôi hy vọng các API Win32 nguyên bản có ít hơn trên chi phí so với CRT. Tôi đang thiếu gì ở đây?

Mã nguồn C++ thử nghiệm tương thích sau.


EDIT

tôi lặp đi lặp lại các cuộc thử nghiệm với 4KB đọc bộ đệm và sử dụng ba file khác nhau (với cùng một nội dung) để tránh bộ nhớ đệm hiệu ứng mà có thể làm sai lệch đo hiệu suất, và bây giờ kết quả như mong đợi.
Ví dụ, đối với một tập tin của khoảng 400 MB kết quả là:

  1. lập bản đồ bộ nhớ Win32: 305,908 ms

  2. Win32 file (CreateFile): 451,402 ms

  3. C tệp (FILE *): 460.579 ms


//////////////////////////////////////////////////////////////////////////////// 
// Test file reading using C FILE*, Win32 CreateFile and Win32 memory mapping. 
//////////////////////////////////////////////////////////////////////////////// 


#include <stdio.h> 
#include <stdlib.h> 
#include <algorithm> 
#include <exception> 
#include <iostream> 
#include <stdexcept> 
#include <vector> 
#include <Windows.h> 


//------------------------------------------------------------------------ 
//      Performance (speed) measurement 
//------------------------------------------------------------------------ 

long long counter() 
{ 
    LARGE_INTEGER li; 
    QueryPerformanceCounter(&li); 
    return li.QuadPart; 
} 

long long frequency() 
{ 
    LARGE_INTEGER li; 
    QueryPerformanceFrequency(&li); 
    return li.QuadPart; 
} 

void print_time(const long long start, const long long finish, 
    const char * const s) 
{ 
    std::cout << s << ": " << (finish - start) * 1000.0/frequency() << " ms\n"; 
} 


//------------------------------------------------------------------------ 
//      RAII handle wrappers 
//------------------------------------------------------------------------ 

struct c_file_traits 
{ 
    typedef FILE* type; 

    static FILE* invalid_value() 
    { 
     return nullptr; 
    } 

    static void close(FILE* f) 
    { 
     fclose(f); 
    } 
}; 

struct win32_file_traits 
{ 
    typedef HANDLE type; 

    static HANDLE invalid_value() 
    { 
     return INVALID_HANDLE_VALUE; 
    } 

    static void close(HANDLE h) 
    { 
     CloseHandle(h); 
    } 
}; 

struct win32_handle_traits 
{ 
    typedef HANDLE type; 

    static HANDLE invalid_value() 
    { 
     return nullptr; 
    } 

    static void close(HANDLE h) 
    { 
     CloseHandle(h); 
    } 
}; 

template <typename Traits> 
class handle 
{ 
public: 
    typedef typename Traits::type type; 

    handle() 
     : _h(Traits::invalid_value()) 
    { 
    } 

    explicit handle(type h) 
     : _h(h) 
    { 
    } 

    ~handle() 
    { 
     close(); 
    } 

    bool valid() const 
    { 
     return (_h != Traits::invalid_value()); 
    } 

    type get() const 
    { 
     return _h; 
    } 

    void close() 
    { 
     if (valid()) 
      Traits::close(_h); 

     _h = Traits::invalid_value(); 
    } 

    void reset(type h) 
    { 
     if (h != _h) 
     { 
      close(); 
      _h = h; 
     } 
    } 


private: // Ban copy 
    handle(const handle&); 
    handle& operator=(const handle&); 

private: 
    type _h; // wrapped raw handle 
}; 

typedef handle<c_file_traits> c_file_handle; 
typedef handle<win32_file_traits> win32_file_handle; 
typedef handle<win32_handle_traits> win32_handle; 


//------------------------------------------------------------------------ 
//    File reading tests using various techniques 
//------------------------------------------------------------------------ 

unsigned long long count_char_using_c_file(const std::string& filename, const char ch) 
{ 
    unsigned long long char_count = 0; 

#pragma warning(push) 
#pragma warning(disable: 4996) // fopen use is OK 
    c_file_handle file(fopen(filename.c_str(), "rb")); 
#pragma warning(pop) 

    if (!file.valid()) 
     throw std::runtime_error("Can't open file."); 

    std::vector<char> read_buffer(4*1024); // 4 KB 
    bool has_more_data = true; 
    while (has_more_data) 
    { 
     size_t read_count = fread(read_buffer.data(), 1, read_buffer.size(), file.get()); 
     for (size_t i = 0; i < read_count; i++) 
     { 
      if (read_buffer[i] == ch) 
       char_count++; 
     } 

     if (read_count < read_buffer.size()) 
      has_more_data = false; 
    } 

    return char_count; 
} 


unsigned long long count_char_using_win32_file(const std::string& filename, const char ch) 
{ 
    unsigned long long char_count = 0; 

    win32_file_handle file(::CreateFileA(
     filename.c_str(), 
     GENERIC_READ, 
     FILE_SHARE_READ, 
     nullptr, 
     OPEN_EXISTING, 
     FILE_FLAG_SEQUENTIAL_SCAN, 
     nullptr 
     ) 
     ); 
    if (!file.valid()) 
     throw std::runtime_error("Can't open file."); 

    std::vector<char> read_buffer(4*1024); // 4 KB 
    bool has_more_data = true; 
    while (has_more_data) 
    { 
     DWORD read_count = 0; 
     if (!ReadFile(file.get(), read_buffer.data(), read_buffer.size(), &read_count, nullptr)) 
      throw std::runtime_error("File read error using ReadFile()."); 

     for (size_t i = 0; i < read_count; i++) 
     { 
      if (read_buffer[i] == ch) 
       char_count++; 
     } 

     if (read_count < sizeof(read_buffer)) 
      has_more_data = false; 
    } 

    return char_count; 
} 


// Memory-map a file. 
class file_map 
{ 
public: 
    explicit file_map(const std::string& filename) 
     : _view(nullptr), _length(0) 
    { 
     _file.reset(::CreateFileA(
      filename.c_str(), 
      GENERIC_READ, 
      FILE_SHARE_READ, 
      nullptr, 
      OPEN_EXISTING, 
      FILE_ATTRIBUTE_NORMAL, 
      nullptr)); 
     if (!_file.valid()) 
      return; 

     LARGE_INTEGER file_size; 
     if (!GetFileSizeEx(_file.get(), &file_size)) 
      return; 

     if (file_size.QuadPart == 0) 
      return; 

     _mapping.reset(::CreateFileMapping(
      _file.get(), nullptr, 
      PAGE_READONLY, 
      0, 
      0, 
      nullptr) 
      ); 
     if (!_mapping.valid()) 
      return; 

     _view = reinterpret_cast<char*> 
      (::MapViewOfFile(_mapping.get(), FILE_MAP_READ, 0, 0, 0)); 
     if (!_view) 
      return; 

     _length = file_size.QuadPart; 
    } 

    ~file_map() 
    { 
     if (_view) 
      UnmapViewOfFile(_view); 
    } 

    bool valid() const 
    { 
     return (_view != nullptr); 
    } 

    const char * begin() const 
    { 
     return _view; 
    } 

    const char * end() const 
    { 
     return begin() + length(); 
    } 

    unsigned long long length() const 
    { 
     return _length; 
    } 

private: // ban copy 
    file_map(const file_map&); 
    file_map& operator=(const file_map&); 

private: 
    win32_file_handle _file; 
    win32_handle  _mapping; 
    char*    _view; 
    unsigned long long _length; // in bytes 
}; 


unsigned long long count_char_using_memory_mapping(const std::string& filename, const char ch) 
{ 
    unsigned long long char_count = 0; 

    file_map view(filename); 
    if (!view.valid()) 
     throw std::runtime_error("Can't create memory-mapping of file."); 

    for (auto it = view.begin(); it != view.end(); ++it) 
    { 
     if (*it == ch) 
     { 
      char_count++; 
     } 
    } 

    return char_count; 
} 


template <typename TestFunc> 
void run_test(const char * message, TestFunc test, const std::string& filename, const char ch) 
{ 
    const long long start = counter(); 
    const unsigned long long char_count = test(filename, ch); 
    const long long finish = counter(); 
    print_time(start, finish, message); 
    std::cout << "Count of \'" << ch << "\' : " << char_count << "\n\n"; 
} 


int main(int argc, char* argv[]) 
{ 
    static const int kExitOk = 0; 
    static const int kExitError = 1; 

    if (argc != 3) 
    { 
     std::cerr << argv[0] << " <char> <filename>.\n"; 
     std::cerr << "Counts occurrences of ASCII character <char>\n"; 
     std::cerr << "in the <filename> file.\n\n"; 
     return kExitError; 
    } 

    const char ch = *(argv[1]); 
    const std::string filename = argv[2]; 

    try 
    { 
     // Execute tests on THREE different files with the same content, 
     // to avoid caching effects. 
     // (file names have incremental number suffix). 
     run_test("C <stdio.h> file (FILE*)", count_char_using_c_file, filename + "1", ch); 
     run_test("Win32 file (CreateFile)", count_char_using_win32_file, filename + "2", ch); 
     run_test("Win32 memory mapping", count_char_using_memory_mapping, filename + "3", ch); 

     return kExitOk; 
    } 
    catch (const std::exception& e) 
    { 
     std::cerr << "\n*** ERROR: " << e.what() << '\n'; 
     return kExitError; 
    } 
} 

//////////////////////////////////////////////////////////////////////////////// 
+8

Để giảm chi phí đầu vào syscall, triển khai 'FILE' đang thực hiện bộ đệm riêng. Không có gì quá huyền diệu về nó. –

+7

Lưu ý nhanh: Nếu bạn thực hiện các kiểm tra back-to-back trên tệp SAME, kết quả kiểm tra thứ hai sẽ bị lệch bởi dữ liệu tệp/bộ đệm siêu dữ liệu. Windows lưu trữ dữ liệu tệp cũng như phần cứng bộ điều khiển đĩa. Tôi không thể nói từ một đọc nhanh của mã cho dù stating trên áp dụng, nhưng nó thường là nguyên nhân của những thứ như thế này. –

+1

Điều gì sẽ xảy ra nếu bạn tăng kích thước bộ đệm trong 'count_char_using_win32_file()'? – NPE

Trả lời

11

Chỉ cần chạy một số xét nghiệm trên máy tính của tôi cho thấy rằng việc tăng kích thước bộ đệm thực sự làm tăng hiệu suất:

C <stdio.h> file (FILE*): 1431.93 ms 
Bufsize: 0 
Count of 'x' : 3161882 

Win32 file (CreateFile): 2289.45 ms 
Bufsize: 1024 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1714.5 ms 
Bufsize: 2048 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1479.16 ms 
Bufsize: 4096 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1328.25 ms 
Bufsize: 8192 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1256.1 ms 
Bufsize: 16384 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1223.54 ms 
Bufsize: 32768 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1224.84 ms 
Bufsize: 65536 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1212.4 ms 
Bufsize: 131072 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1238.09 ms 
Bufsize: 262144 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1209.2 ms 
Bufsize: 524288 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1223.67 ms 
Bufsize: 1048576 
Count of 'x' : 3161882 

Win32 file (CreateFile): 1349.98 ms 
Bufsize: 2097152 
Count of 'x' : 3161882 

Win32 memory mapping: 796.281 ms 
Bufsize: 0 
Count of 'x' : 3161882 

Một số bước trong Visual Studio 2012 debugger cho thấy rằng kích thước bộ đệm của phương pháp FILE * là 4096 byte , ít nhất là trên máy của tôi. (Và như những người khác đã nói nó gọi ReadFile quá trừ khi bạn đang đọc từ một giao diện điều khiển.)

Nó cũng thú vị là bộ đệm lớn làm chậm hiệu suất. Di chuyển toán tử new bên ngoài thử nghiệm cũng không giải quyết được sự cố.

Đầu tiên kiểm tra ánh xạ bộ nhớ khá chậm đối với tôi vì tôi chạy nó trong chế độ gỡ lỗi. Tôi đã cập nhật tất cả các kết quả với việc biên dịch chế độ phát hành. Lập bản đồ bộ nhớ đã trở thành người đầu tiên.

+2

Ánh xạ bộ nhớ chậm là chạy nó trong chế độ gỡ lỗi từ Visual Studio. Xây dựng trong chế độ phát hành. – paddy

+0

@paddy: Tất nhiên tôi giả sử chúng tôi đang chạy các thử nghiệm được xây dựng trong chế độ phát hành ... –

+0

Vâng tôi nhận ra điều đó. Tôi chỉ nhận thấy rằng nếu tôi xây dựng chương trình của bạn trong chế độ gỡ lỗi, tôi thấy ánh xạ bộ nhớ chậm trên cùng một thứ tự như được mô tả trong câu trả lời này. – paddy

4

Bạn có chắc là bạn đang thử nghiệm đúng cách không?
Bạn đang tính toán vị trí đĩa, thời gian tìm kiếm, lưu trữ tệp, v.v ...?

stdio và win32 cuối cùng cũng thực hiện các cuộc gọi giống với nhân Windows để mở tệp.

mmap làm những việc hơi khác nhau, bởi vì nó có thể dự trữ thực sự đọc các dữ liệu cho đến khi nó được sử dụng - nếu bạn có một tập tin cố định kích thước và hiệu suất vấn đề, mmap là một lựa chọn tốt

9

Việc truy cập đĩa nhanh nhất tôi đã đã từng đạt được bằng cách sử dụng ReadFile. Nhưng tôi đặc biệt mở tệp bằng cờ để đáp ứng các yêu cầu truy cập đĩa và bộ nhớ đệm của tôi. Nếu bạn chỉ sử dụng nó đúng nguyên văn, sự so sánh là một chút xấu hổ.

Bạn nên đọc thêm về chức năng này, cũng như CreateFile. Bạn sẽ tìm thấy bạn có thể đọc dữ liệu trong (bội số của) các khối có kích thước theo khu vực tới bộ nhớ liên kết theo ngành. Sau đó, bạn sẽ ra ngoài thực hiện fread.

Như những người khác đã nói, fread đang làm bộ đệm của riêng nó. Việc triển khai bộ đệm của bạn với ReadFile vẫn cần hoạt động.

Kiểm tra MSDN. ALl thông tin là có. Cụ thể, ở đây:

+1

Tôi cần truy cập đọc đơn giản ** tuần tự ** và mở tệp bằng cờ 'FILE_FLAG_SEQUENTIAL_SCAN'. Bạn có thể cụ thể hơn về đề xuất của mình không? Tôi nên sử dụng cờ nào để truy cập đọc tuần tự tối ưu? –

3

Khi sử dụng tập tin bộ nhớ ánh xạ, không có cần phải sao chép nội dung của tập tin vào ứng dụng của bạn - nó được ánh xạ trong như một phần của bộ nhớ ảo trực tiếp từ hệ điều hành, vì vậy khi bạn truy cập vào nội dung tệp, nó chỉ cần được đọc thẳng vào một trang đi vào bộ nhớ được ánh xạ.

Nếu bạn thực hiện công việc một cách chính xác khi sử dụng API Win32, nó sẽ nhanh hơn C stdio, vì có ít chi phí trong cuộc gọi. Tuy nhiên, nó là khá có thể là bạn không nhận được sự cân bằng lý tưởng giữa hệ thống gọi trên không và "quá lớn một bộ đệm, do đó, đọc mất nhiều thời gian hơn cần thiết". Tôi khuyên bạn nên thử với 4K hoặc 8K (thậm chí có thể là 32K) làm bộ đệm trong chức năng API Win32 của bạn - có kích thước bộ đệm là bội số của 4K là lý tưởng vì trang bộ nhớ (thường là) 4KB. Ít cuộc gọi đến API khiến chi phí thấp hơn, nhưng bạn không muốn đi quá xa.

[Tôi đã thực hiện một số thử nghiệm như thế này trên Linux vào ngày khác, và tìm thấy kết quả tương tự - và từ kinh nghiệm của tôi ở đó: sử dụng các tệp khác nhau cho mỗi bài kiểm tra, nếu không, bộ nhớ đệm hệ thống tập tin sẽ giúp kiểm tra chạy sau!].

Các vấn đề liên quan