2017-03-04 18 views
14

Sử dụng chương trình bên dưới Tôi thử kiểm tra tốc độ ghi trên đĩa bằng cách sử dụng std::ofstream.Tại sao đĩa C++ của tôi viết thử nghiệm chậm hơn nhiều so với bản sao tệp đơn giản bằng bash?

Tôi đạt được khoảng 300 MiB/s khi viết 1 tệp GiB.

Tuy nhiên, một bản sao tệp đơn giản bằng cách sử dụng lệnh cp nhanh hơn gấp hai lần.

Chương trình của tôi có đạt đến giới hạn phần cứng hoặc chương trình có thể nhanh hơn không?

#include <chrono> 
#include <iostream> 
#include <fstream> 

char payload[1000 * 1000]; // 1 MB 

void test(int MB) 
{ 
    // Configure buffer 
    char buffer[32 * 1000]; 
    std::ofstream of("test.file"); 
    of.rdbuf()->pubsetbuf(buffer, sizeof(buffer)); 

    auto start_time = std::chrono::steady_clock::now(); 

    // Write a total of 1 GB 
    for (auto i = 0; i != MB; ++i) 
    { 
     of.write(payload, sizeof(payload)); 
    } 

    double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - start_time).count(); 
    double megabytes_per_ns = 1e3/elapsed_ns; 
    double megabytes_per_s = 1e9 * megabytes_per_ns; 
    std::cout << "Payload=" << MB << "MB Speed=" << megabytes_per_s << "MB/s" << std::endl; 
} 

int main() 
{ 
    for (auto i = 1; i <= 10; ++i) 
    { 
     test(i * 100); 
    } 
} 

Output:

Payload=100MB Speed=3792.06MB/s 
Payload=200MB Speed=1790.41MB/s 
Payload=300MB Speed=1204.66MB/s 
Payload=400MB Speed=910.37MB/s 
Payload=500MB Speed=722.704MB/s 
Payload=600MB Speed=579.914MB/s 
Payload=700MB Speed=499.281MB/s 
Payload=800MB Speed=462.131MB/s 
Payload=900MB Speed=411.414MB/s 
Payload=1000MB Speed=364.613MB/s 

Cập nhật

tôi đã thay đổi std::ofstream-fwrite:

#include <chrono> 
#include <cstdio> 
#include <iostream> 

char payload[1024 * 1024]; // 1 MiB 

void test(int number_of_megabytes) 
{ 
    FILE* file = fopen("test.file", "w"); 

    auto start_time = std::chrono::steady_clock::now(); 

    // Write a total of 1 GB 
    for (auto i = 0; i != number_of_megabytes; ++i) 
    { 
     fwrite(payload, 1, sizeof(payload), file); 
    } 
    fclose(file); // TODO: RAII 

    double elapsed_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - start_time).count(); 
    double megabytes_per_ns = 1e3/elapsed_ns; 
    double megabytes_per_s = 1e9 * megabytes_per_ns; 
    std::cout << "Size=" << number_of_megabytes << "MiB Duration=" << long(0.5 + 100 * elapsed_ns/1e9)/100.0 << "s Speed=" << megabytes_per_s << "MiB/s" << std::endl; 
} 

int main() 
{ 
    test(256); 
    test(512); 
    test(1024); 
    test(1024); 
} 

nào cải thiện tốc độ để 668MiB/s cho một tập tin 1 GiB:

Size=256MiB Duration=0.4s Speed=2524.66MiB/s 
Size=512MiB Duration=0.79s Speed=1262.41MiB/s 
Size=1024MiB Duration=1.5s Speed=664.521MiB/s 
Size=1024MiB Duration=1.5s Speed=668.85MiB/s 

Đó là chỉ nhanh như dd:

time dd if=/dev/zero of=test.file bs=1024 count=0 seek=1048576 

real 0m1.539s 
user 0m0.001s 
sys 0m0.344s 
+0

Bạn đang thử nghiệm bản phát hành chương trình của bạn với tối ưu hóa? Bạn đã thử tăng kích thước bộ đệm chưa? –

+0

Không nên là 'double megabyte_per_ns = MB/elapsed_ns; '? – zett42

+0

Ngoài ra, bạn nên mở luồng ở chế độ nhị phân để so sánh nó với các phương pháp viết khác. Sử dụng 'std :: ofstream of (" test.file ", std :: ios :: binary)'. Tôi nhận được hiệu suất rất chặt chẽ giữa 'ofstream' và' fwrite' sau đó (sự khác biệt nằm trong phạm vi sai số đo lường). Trình biên dịch VC++ 2017. – zett42

Trả lời

14

Thứ nhất, bạn không thực sự đo tốc độ đĩa bằng văn bản, nhưng (một phần) tốc độ ghi dữ liệu vào bộ nhớ cache đĩa hệ điều hành. Để thực sự đo tốc độ ghi đĩa, dữ liệu phải được flushed vào đĩa trước khi tính toán thời gian. Nếu không xả nước thì có thể có sự khác biệt tùy thuộc vào kích thước tệp và bộ nhớ có sẵn.

Có vẻ như đã xảy ra sự cố trong quá trình tính toán. Bạn không sử dụng giá trị của MB.

Đồng thời đảm bảo kích thước bộ đệm là công suất hai hoặc ít nhất là bội số của kích thước trang đĩa (4096 byte): char buffer[32 * 1024];. Bạn cũng có thể làm điều đó cho payload. (có vẻ như bạn đã thay đổi từ 1024 đến 1000 trong bản chỉnh sửa mà bạn đã thêm các phép tính).

Không sử dụng luồng để ghi bộ đệm (nhị phân) dữ liệu vào đĩa, nhưng thay vào đó hãy ghi trực tiếp vào tệp, sử dụng FILE*, fopen(), fwrite(), fclose(). Xem this answer để biết ví dụ và một số thời gian.


Để sao chép một tập tin: mở file nguồn trong read-only và, nếu có thể, chuyển tiếp chế độ chỉ, và sử dụng fread(), fwrite():

while fread() from source to buffer 
    fwrite() buffer to destination file 

này sẽ cho bạn một tốc độ tương đương với tốc độ của một bản sao tập tin hệ điều hành (bạn có thể muốn kiểm tra một số kích thước bộ đệm khác nhau).

sức này thể nhanh hơn một chút sử dụng lập bản đồ bộ nhớ:

open src, create memory mapping over the file 
open/create dest, set file size to size of src, create memory mapping over the file 
memcpy() src to dest 

Đối với các file lớn quan nhỏ ánh xạ nên được sử dụng.

7
  1. Streams are slow
  2. cp sử dụng syscalls trực tiếp read(2) hoặc mmap(2).
+0

Câu hỏi được liên kết là 5 tuổi, việc triển khai có thể đã được cải thiện trong thời gian chờ đợi. Các liên kết mẫu mã đã chết. – zett42

4

Tôi muốn cho rằng đó là thứ gì đó thông minh bên trong CP hoặc hệ thống tệp. Nếu nó nằm trong CP thì có thể tập tin bạn đang sao chép có rất nhiều số 0 trong đó và cp đang phát hiện điều này và viết một phiên bản sparse của tệp của bạn. Trang man cho cp nói "Theo mặc định, các tệp SOURCE thưa thớt được phát hiện bởi một heuristic thô và tệp DEST tương ứng cũng được tạo thành thưa thớt." Điều này có thể có nghĩa là một vài điều nhưng một trong số họ là cp có thể làm cho một phiên bản thưa thớt của tập tin của bạn mà sẽ yêu cầu ít đĩa ghi thời gian.

Nếu nó nằm trong hệ thống tệp của bạn thì có thể là Deduplication.

Là ảnh chụp thứ 3, nó cũng có thể là thứ gì đó bên trong hệ điều hành hoặc phần mềm đĩa của bạn đang dịch đọc và ghi vào một số hướng dẫn chuyên dụng. có nghĩa là độ trễ ít hơn).

3

Bạn đang sử dụng kích thước bộ đệm tương đối nhỏ. Bộ đệm nhỏ có nghĩa là hoạt động nhiều hơn mỗi giây, làm tăng chi phí. Hệ thống đĩa có độ trễ nhỏ trước khi chúng nhận được yêu cầu đọc/ghi và bắt đầu xử lý nó; một bộ đệm lớn hơn amortizes rằng chi phí tốt hơn một chút. Một bộ đệm nhỏ hơn cũng có thể có nghĩa là đĩa được dành nhiều thời gian tìm kiếm.

Bạn không phát hành nhiều yêu cầu đồng thời - bạn yêu cầu một lần đọc để kết thúc trước khi bắt đầu tiếp theo. Điều này có nghĩa rằng đĩa có thể có thời gian chết mà nó không làm gì cả. Vì tất cả các ghi phụ thuộc vào tất cả các lần đọc, và các lần đọc của bạn là nối tiếp, bạn đang bỏ đói hệ thống đĩa của các yêu cầu đọc (gấp đôi như vậy, vì các ghi sẽ lấy đi khỏi các lần đọc).

Tổng số byte đọc được yêu cầu trên tất cả yêu cầu đọc phải lớn hơn sản phẩm trì hoãn băng thông của hệ thống đĩa. Nếu đĩa có độ trễ 0,5 ms và hiệu suất 4 GB/giây, thì bạn muốn có 4 GB * 0,5 ms = 2 MB giá trị đọc nổi bật mọi lúc.

Bạn không sử dụng bất kỳ gợi ý nào của hệ điều hành mà bạn đang thực hiện đọc tuần tự.

Để sửa lỗi này:

  • Thay đổi mã của bạn để có nhiều hơn một yêu cầu đọc nổi bật tại mọi thời điểm.
  • Có đủ yêu cầu đọc chưa xử lý sao cho bạn đang chờ đợi ít nhất 2 MB giá trị dữ liệu.
  • Sử dụng cờ posix_fadvise() để giúp lịch trình đĩa OS và tối ưu hóa bộ nhớ cache trang.
  • Cân nhắc sử dụng mmap để cắt giảm chi phí.
  • Sử dụng kích thước bộ đệm lớn hơn cho mỗi yêu cầu đọc để giảm chi phí.

Câu trả lời này có thêm thông tin: https://stackoverflow.com/a/3756466/344638

0

Vấn đề là bạn chỉ định bộ đệm quá nhỏ so với fstream bạn

char buffer[32 * 1000]; 
std::ofstream of("test.file"); 
of.rdbuf()->pubsetbuf(buffer, sizeof(buffer)); 

ứng dụng của bạn chạy ở chế độ người dùng.Để ghi vào đĩa, hệ thống cuộc gọi của dòng lệnh write được thực hiện ở chế độ hạt nhân. Sau đó, write chuyển dữ liệu vào bộ nhớ cache của hệ thống, sau đó vào bộ nhớ cache HDD và sau đó nó sẽ được ghi vào đĩa.

Kích thước bộ đệm này ảnh hưởng đến số lượng cuộc gọi hệ thống (1 cuộc gọi cho mỗi 32 * 1000 byte). Trong khi hệ thống gọi OS phải chuyển ngữ cảnh thực hiện từ chế độ người dùng sang chế độ hạt nhân và sau đó quay lại. Chuyển ngữ cảnh là phí. Trong Linux, nó tương đương khoảng 2500-3500 lệnh CPU đơn giản. Do đó, ứng dụng của bạn dành nhiều thời gian nhất cho CPU trong chuyển đổi ngữ cảnh.

Trong ứng dụng thứ hai của bạn, bạn sử dụng

FILE* file = fopen("test.file", "w"); 

FILE sử dụng bộ đệm lớn hơn theo mặc định, đó là lý do tại sao nó sản xuất mã hiệu quả hơn. Bạn có thể thử chỉ định bộ đệm nhỏ với setvbuf. Trong trường hợp này, bạn sẽ thấy cùng một sự suy giảm hiệu năng.

Xin lưu ý trong trường hợp của bạn, cổ chai không phải là hiệu suất HDD. Đó là bối cảnh chuyển đổi

image

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