Nó là tốt hơn để sử dụng std :: memcpy () hoặc std :: copy () về hiệu suất?


163

Là tốt hơn để sử dụng memcpynhư hiển thị dưới đây hoặc tốt hơn là sử dụng std::copy()về mặt hiệu suất? Tại sao?

char *bits = NULL;
...

bits = new (std::nothrow) char[((int *) copyMe->bits)[0]];
if (bits == NULL)
{
    cout << "ERROR Not enough memory.\n";
    exit(1);
}

memcpy (bits, copyMe->bits, ((int *) copyMe->bits)[0]);

Lưu ý rằng charcó thể được ký hoặc không dấu, tùy thuộc vào việc thực hiện. Nếu số lượng byte có thể> = 128, thì hãy sử dụng unsigned charcho mảng byte của bạn. (Các (int *)diễn viên cũng sẽ an toàn hơn (unsigned int *).)
Dan Breslau

13
Tại sao bạn không sử dụng std::vector<char>? Hay kể từ khi bạn nói bits, std::bitset?
GManNickG

2
Trên thực tế, bạn có thể vui lòng giải thích cho tôi những gì (int*) copyMe->bits[0]không?
dùng3728501

4
không chắc chắn tại sao thứ gì đó có vẻ như là một mớ hỗn độn với rất ít bối cảnh quan trọng được cung cấp là ở mức +81, nhưng này. @ user3728501 tôi đoán là sự khởi đầu của bộ đệm giữ một intkích thước chính tả, nhưng đó có vẻ như là một công thức cho thảm họa được xác định thực hiện, giống như rất nhiều thứ khác ở đây.
underscore_d

2
Trong thực tế, (int *)diễn viên đó chỉ là hành vi không xác định thuần túy, không được xác định theo thực hiện. Cố gắng thực hiện kiểu đánh lừa thông qua dàn diễn viên vi phạm các quy tắc răng cưa nghiêm ngặt và do đó hoàn toàn không được xác định bởi Tiêu chuẩn. (Ngoài ra, trong C ++ mặc dù không phải C, bạn cũng không thể gõ chữ thông qua một uniontrong hai.) Khá nhiều ngoại lệ duy nhất là nếu bạn chuyển đổi sang một biến thể của char*, nhưng phụ cấp không đối xứng.
gạch dưới

Câu trả lời:


207

Tôi sẽ đi ngược lại với sự khôn ngoan chung ở đây std::copysẽ có một sự mất mát hiệu suất nhẹ, gần như không thể nhận ra. Tôi vừa làm một bài kiểm tra và thấy rằng điều đó không đúng: Tôi đã nhận thấy sự khác biệt về hiệu suất. Tuy nhiên, người chiến thắng là std::copy.

Tôi đã viết một triển khai C ++ SHA-2. Trong thử nghiệm của mình, tôi băm 5 chuỗi bằng cả bốn phiên bản SHA-2 (224, 256, 384, 512) và tôi lặp 300 lần. Tôi đo thời gian bằng Boost.timer. Bộ đếm vòng lặp 300 đó là đủ để ổn định hoàn toàn kết quả của tôi. Tôi đã chạy thử nghiệm 5 lần mỗi lần, xen kẽ giữa memcpyphiên bản và std::copyphiên bản. Mã của tôi tận dụng việc lấy dữ liệu càng nhiều khối càng tốt (nhiều triển khai khác hoạt động với char/ char *, trong khi tôi hoạt động với T/ T *(trong đó Tloại lớn nhất trong triển khai của người dùng có hành vi tràn chính xác), vì vậy truy cập bộ nhớ nhanh vào loại lớn nhất tôi có thể là trung tâm cho hiệu suất của thuật toán của tôi. Đây là những kết quả của tôi:

Thời gian (tính bằng giây) để hoàn thành chạy thử nghiệm SHA-2

std::copy   memcpy  % increase
6.11        6.29    2.86%
6.09        6.28    3.03%
6.10        6.29    3.02%
6.08        6.27    3.03%
6.08        6.27    3.03%

Tổng tốc độ tăng trung bình của std :: copy trên memcpy: 2,99%

Trình biên dịch của tôi là gcc 4.6.3 trên Fedora 16 x86_64. Cờ tối ưu hóa của tôi là -Ofast -march=native -funsafe-loop-optimizations.

Mã cho việc triển khai SHA-2 của tôi.

Tôi cũng quyết định chạy thử nghiệm triển khai MD5. Kết quả kém ổn định hơn nhiều, vì vậy tôi quyết định thực hiện 10 lần chạy. Tuy nhiên, sau vài lần thử đầu tiên, tôi đã nhận được kết quả rất khác nhau từ lần chạy này đến lần tiếp theo, vì vậy tôi đoán có một số hoạt động HĐH đang diễn ra. Tôi quyết định làm lại từ đầu.

Cùng cài đặt trình biên dịch và cờ. Chỉ có một phiên bản MD5 và nó nhanh hơn SHA-2, vì vậy tôi đã thực hiện 3000 vòng trên một bộ 5 chuỗi thử nghiệm tương tự.

Đây là 10 kết quả cuối cùng của tôi:

Thời gian (tính bằng giây) để hoàn thành chạy thử nghiệm MD5

std::copy   memcpy      % difference
5.52        5.56        +0.72%
5.56        5.55        -0.18%
5.57        5.53        -0.72%
5.57        5.52        -0.91%
5.56        5.57        +0.18%
5.56        5.57        +0.18%
5.56        5.53        -0.54%
5.53        5.57        +0.72%
5.59        5.57        -0.36%
5.57        5.56        -0.18%

Tổng tốc độ giảm trung bình của std :: copy trên memcpy: 0,11%

Mã cho việc thực hiện MD5 của tôi

Những kết quả này cho thấy có một số tối ưu hóa mà std :: copy được sử dụng trong các thử nghiệm SHA-2 std::copycủa tôi không thể sử dụng trong các thử nghiệm MD5 của tôi. Trong các thử nghiệm SHA-2, cả hai mảng được tạo trong cùng một hàm gọi là std::copy/memcpy . Trong các thử nghiệm MD5 của tôi, một trong các mảng được truyền vào hàm dưới dạng tham số hàm.

Tôi đã làm thêm một chút thử nghiệm để xem những gì tôi có thể làm để làm cho std::copynhanh hơn một lần nữa. Câu trả lời hóa ra rất đơn giản: bật tối ưu hóa thời gian liên kết. Đây là những kết quả của tôi khi LTO được bật (tùy chọn -flto in gcc):

Thời gian (tính bằng giây) để hoàn thành chạy thử nghiệm MD5 với -flto

std::copy   memcpy      % difference
5.54        5.57        +0.54%
5.50        5.53        +0.54%
5.54        5.58        +0.72%
5.50        5.57        +1.26%
5.54        5.58        +0.72%
5.54        5.57        +0.54%
5.54        5.56        +0.36%
5.54        5.58        +0.72%
5.51        5.58        +1.25%
5.54        5.57        +0.54%

Tổng tốc độ tăng trung bình của std :: copy trên memcpy: 0,72%

Tóm lại, dường như không có hình phạt về hiệu suất khi sử dụng std::copy. Trong thực tế, dường như có một hiệu suất đạt được.

Giải thích kết quả

Vậy tại sao có thể std::copytăng hiệu suất?

Đầu tiên, tôi không hy vọng nó sẽ chậm hơn cho bất kỳ triển khai nào, miễn là tối ưu hóa nội tuyến được bật. Tất cả các trình biên dịch nội tuyến tích cực; nó có thể là tối ưu hóa quan trọng nhất vì nó cho phép rất nhiều tối ưu hóa khác. std::copycó thể (và tôi nghi ngờ tất cả các triển khai trong thế giới thực làm) phát hiện ra rằng các đối số có thể sao chép một cách tầm thường và bộ nhớ được trình bày tuần tự. Điều này có nghĩa là trong trường hợp xấu nhất, khi memcpyhợp pháp, std::copysẽ không thực hiện tồi tệ hơn. Việc triển khai tầm thường của std::copyviệc trì hoãn đó memcpyphải đáp ứng tiêu chí của nhà soạn nhạc của bạn là "luôn luôn nội tuyến này khi tối ưu hóa tốc độ hoặc kích thước".

Tuy nhiên, std::copycũng giữ nhiều thông tin hơn. Khi bạn gọi std::copy, chức năng giữ nguyên các loại. memcpyhoạt động trên void *, loại bỏ gần như tất cả các thông tin hữu ích. Ví dụ, nếu tôi chuyển vào một mảng std::uint64_t, trình biên dịch hoặc trình triển khai thư viện có thể có thể tận dụng sự liên kết 64 bit với std::copy, nhưng có thể khó thực hiện hơn với memcpy. Nhiều triển khai các thuật toán như công việc này bằng cách đầu tiên làm việc trên phần không được phân bổ ở đầu phạm vi, sau đó là phần được căn chỉnh, sau đó là phần không được phân bổ ở cuối. Nếu tất cả được đảm bảo để được căn chỉnh, thì mã trở nên đơn giản và nhanh hơn và dễ dàng hơn cho bộ dự đoán nhánh trong bộ xử lý của bạn để có được chính xác.

Tối ưu hóa sớm?

std::copylà ở một vị trí thú vị. Tôi hy vọng nó sẽ không bao giờ chậm hơn memcpyvà đôi khi nhanh hơn với bất kỳ trình biên dịch tối ưu hóa hiện đại nào. Hơn nữa, bất cứ điều gì bạn có thể memcpy, bạn có thể std::copy. memcpykhông cho phép bất kỳ sự trùng lặp trong bộ đệm, trong khi std::copyhỗ trợ chồng lên nhau theo một hướng (với std::copy_backwardcho một hướng khác của chồng lên nhau). memcpychỉ hoạt động trên con trỏ, std::copyhoạt động trên bất kỳ lặp ( std::map, std::vector, std::deque, hoặc riêng kiểu tùy chỉnh của tôi). Nói cách khác, bạn chỉ nên sử dụng std::copykhi bạn cần sao chép khối dữ liệu xung quanh.


35
Tôi muốn nhấn mạnh rằng điều này không có nghĩa std::copylà nhanh hơn 2,99% hoặc 0,72% hoặc -0,11% so với memcpythời gian này để toàn bộ chương trình thực hiện. Tuy nhiên, tôi thường cảm thấy rằng điểm chuẩn trong mã thực là hữu ích hơn so với điểm chuẩn trong mã giả. Toàn bộ chương trình của tôi có sự thay đổi về tốc độ thực hiện. Hiệu quả thực sự của chỉ hai sơ đồ sao chép sẽ có sự khác biệt lớn hơn được hiển thị ở đây khi được thực hiện riêng lẻ, nhưng điều này cho thấy rằng chúng có thể có sự khác biệt có thể đo lường được trong mã thực tế.
David Stone

2
Tôi muốn không đồng ý với kết quả của bạn, nhưng kết quả là kết quả: /. Tuy nhiên, một câu hỏi (tôi biết đó là một thời gian dài trước đây và bạn không nhớ nghiên cứu, vì vậy chỉ cần bình luận theo cách bạn nghĩ), có lẽ bạn đã không nhìn vào mã lắp ráp;
ST3

2
Theo tôi memcpystd::copycó các cách triển khai khác nhau, vì vậy trong một số trường hợp, trình biên dịch tối ưu hóa mã xung quanh và mã sao chép bộ nhớ thực tế dưới dạng một đoạn mã không thể thiếu. Nói cách khác, đôi khi người ta tốt hơn người khác và thậm chí nói cách khác, quyết định sử dụng là tối ưu hóa sớm hay thậm chí ngu ngốc, bởi vì trong mọi tình huống bạn phải thực hiện nghiên cứu mới và, hơn nữa, các chương trình thường được phát triển, vì vậy sau đó một số thay đổi nhỏ lợi thế của chức năng so với khác có thể bị mất.
ST3

3
@ ST3: Tôi sẽ tưởng tượng rằng trong trường hợp xấu nhất, std::copylà một hàm nội tuyến tầm thường chỉ gọi memcpykhi nó hợp pháp. Nội tuyến cơ bản sẽ loại bỏ bất kỳ sự khác biệt hiệu suất tiêu cực. Tôi sẽ cập nhật bài viết với một chút giải thích về lý do tại sao std :: copy có thể nhanh hơn.
David Stone

7
Phân tích rất nhiều thông tin. Re Tổng giảm trung bình tốc độ của std :: copy trên memcpy: 0,11% , trong khi con số là chính xác, kết quả không có ý nghĩa thống kê. Khoảng tin cậy 95% cho chênh lệch về phương tiện là (-0.013s, 0,025), bao gồm 0. Như bạn đã chỉ ra có sự khác biệt từ các nguồn khác và với dữ liệu của bạn, có lẽ bạn sẽ nói hiệu suất là như nhau. Để tham khảo, hai kết quả còn lại có ý nghĩa thống kê - cơ hội bạn thấy sự khác biệt về số lần cực kỳ ngẫu nhiên này là khoảng 1 trên 100 triệu (lần đầu tiên) và 1 trên 20.000 (lần cuối).
TooTone 11/03/2016

78

Tất cả các trình biên dịch mà tôi biết sẽ thay thế một đơn giản std::copybằng một memcpykhi nó phù hợp, hoặc thậm chí tốt hơn, vector hóa bản sao để nó thậm chí còn nhanh hơn mộtmemcpy .

Trong mọi trường hợp: hồ sơ và tìm ra chính mình. Các trình biên dịch khác nhau sẽ làm những việc khác nhau và hoàn toàn có thể nó sẽ không thực hiện chính xác những gì bạn yêu cầu.

Xem bản trình bày này về tối ưu hóa trình biên dịch (pdf).

Đây là những gì GCC làm cho đơn giản std::copycủa loại POD.

#include <algorithm>

struct foo
{
  int x, y;    
};

void bar(foo* a, foo* b, size_t n)
{
  std::copy(a, a + n, b);
}

Đây là phần tháo gỡ (chỉ -Otối ưu hóa), hiển thị lệnh gọi tới memmove:

bar(foo*, foo*, unsigned long):
    salq    $3, %rdx
    sarq    $3, %rdx
    testq   %rdx, %rdx
    je  .L5
    subq    $8, %rsp
    movq    %rsi, %rax
    salq    $3, %rdx
    movq    %rdi, %rsi
    movq    %rax, %rdi
    call    memmove
    addq    $8, %rsp
.L5:
    rep
    ret

Nếu bạn thay đổi chữ ký hàm thành

void bar(foo* __restrict a, foo* __restrict b, size_t n)

sau đó memmovetrở thành một memcpycải tiến hiệu suất nhẹ. Lưu ý rằng memcpychính nó sẽ được vector hóa nặng.


1
Làm thế nào tôi có thể làm hồ sơ. Công cụ nào để sử dụng (trong windows và linux)?
dùng576670

5
@Konrad, bạn đã đúng. Nhưng memmovekhông nên nhanh hơn - thay vào đó, nó nên chậm hơn chậm hơn vì phải tính đến khả năng hai phạm vi dữ liệu trùng nhau. Tôi nghĩ rằng std::copycho phép dữ liệu chồng chéo, và vì vậy nó phải gọi memmove.
Charles Salvia

2
@Konrad: Nếu memmove luôn nhanh hơn memcpy, thì memcpy sẽ gọi memmove. Những gì std :: copy thực sự có thể gửi đến (nếu có bất cứ điều gì) được xác định theo triển khai, vì vậy không hữu ích khi đề cập đến các chi tiết cụ thể mà không đề cập đến việc thực hiện.
Fred Nurk

1
Mặc dù, một chương trình đơn giản để tái tạo hành vi này, được biên dịch với -O3 trong GCC cho tôi thấy a memcpy. Nó khiến tôi tin rằng GCC sẽ kiểm tra xem có bộ nhớ trùng nhau hay không.
jweyrich

1
@Konrad: tiêu chuẩn std::copycho phép chồng chéo theo một hướng nhưng không phải theo hướng khác. Đầu của đầu ra không thể nằm trong phạm vi đầu vào, nhưng đầu của đầu vào được phép nằm trong phạm vi đầu ra. Điều này hơi kỳ lạ, bởi vì thứ tự của các bài tập được xác định và một cuộc gọi có thể là UB mặc dù hiệu ứng của các bài tập đó, theo thứ tự đó, được xác định. Nhưng tôi cho rằng sự hạn chế cho phép tối ưu hóa vector.
Steve Jessop

24

Luôn sử dụng std::copymemcpychỉ giới hạn ở các cấu trúc POD kiểu C và trình biên dịch có thể sẽ thay thế các cuộc gọi đến std::copybằngmemcpy nếu mục tiêu này là trong thực tế POD.

Thêm vào đó, std::copycó thể được sử dụng với nhiều loại iterator, không chỉ con trỏ. std::copylinh hoạt hơn để không mất hiệu suất và là người chiến thắng rõ ràng.


Tại sao bạn muốn sao chép xung quanh các vòng lặp?
Atmocreations

3
Bạn không sao chép các trình vòng lặp, mà là phạm vi được xác định bởi hai trình vòng lặp. Chẳng hạn, std::copy(container.begin(), container.end(), destination);sẽ sao chép nội dung của container(mọi thứ giữa beginend) vào bộ đệm được chỉ định bởi destination. std::copykhông yêu cầu shenanigans như &*container.begin()hoặc &container.back() + 1.
David Stone

16

Về lý thuyết, memcpycó thể có một lợi thế nhỏ , không thể chấp nhận được , vô hạn , hiệu suất, chỉ vì nó không có cùng yêu cầu như std::copy. Từ trang người đàn ông của memcpy:

Để tránh tràn, kích thước của các mảng được chỉ ra bởi cả tham số nguồn và tham số nguồn, phải có ít nhất là byte byte và không được chồng lấp (đối với các khối bộ nhớ chồng chéo, memmove là cách tiếp cận an toàn hơn).

Nói cách khác, memcpycó thể bỏ qua khả năng chồng chéo dữ liệu. (Truyền các mảng chồng chéo sang memcpyhành vi không xác định.) Vì vậy, memcpykhông cần kiểm tra rõ ràng tình trạng này, trong khi std::copycó thể được sử dụng miễn là OutputIteratortham số không nằm trong phạm vi nguồn. Lưu ý đây không phải giống như nói rằng phạm vi nguồn và phạm vi đích không thể trùng nhau.

Vì vậy, vì std::copycó một số yêu cầu khác nhau, về lý thuyết, nó nên chậm hơn một chút (với sự nhấn mạnh cực độ vào hơi ), vì nó có thể sẽ kiểm tra các mảng C chồng chéo, hoặc nếu không thì ủy thác việc sao chép các mảng C sangmemmove , cần thực hiện kiểm tra. Nhưng trong thực tế, bạn (và hầu hết các trình biên dịch) có thể thậm chí sẽ không phát hiện ra bất kỳ sự khác biệt nào.

Tất nhiên, nếu bạn không làm việc với POD , dù sao bạn cũng không thể sử dụng memcpy.


7
Điều này đúng cho std::copy<char>. Nhưng std::copy<int>có thể giả định rằng đầu vào của nó là liên kết nội bộ. Điều đó sẽ tạo ra một sự khác biệt lớn hơn nhiều, bởi vì nó ảnh hưởng đến mọi yếu tố. Chồng chéo là kiểm tra một lần.
MSalters

2
@MSalters, đúng, nhưng hầu hết các triển khai của memcpytôi đã thấy kiểm tra căn chỉnh và cố gắng sao chép các từ thay vì theo từng byte.
Charles Salvia

1
std :: copy () cũng có thể bỏ qua bộ nhớ chồng lấp. Nếu bạn muốn hỗ trợ bộ nhớ chồng lấp, bạn phải tự viết logic để gọi std :: Reverse_copy () trong các tình huống thích hợp.
Cygon

2
Có một lập luận ngược lại có thể được đưa ra: khi đi qua memcpygiao diện, nó sẽ mất thông tin căn chỉnh. Do đó, memcpyphải thực hiện kiểm tra căn chỉnh tại thời điểm chạy để xử lý các bắt đầu và kết thúc không được phân bổ. Những kiểm tra có thể rẻ nhưng chúng không miễn phí. Trong khi đó std::copycó thể tránh các kiểm tra và vector hóa. Ngoài ra, trình biên dịch có thể chứng minh rằng các mảng nguồn và đích không trùng nhau và một lần nữa vector hóa mà không cần người dùng phải chọn giữa memcpymemmove.
Maxim Egorushkin

11

Quy tắc của tôi rất đơn giản. Nếu bạn đang sử dụng C ++ thì thích thư viện C ++ chứ không phải C :)


40
C ++ được thiết kế rõ ràng để cho phép sử dụng các thư viện C. Đây không phải là một tai nạn. Thường sử dụng std :: copy tốt hơn memcpy trong C ++, nhưng điều này không liên quan gì đến cái nào là C, và kiểu lập luận đó thường là cách tiếp cận sai.
Fred Nurk

2
@FredNurk Thông thường bạn muốn tránh khu vực C yếu trong đó C ++ cung cấp giải pháp thay thế an toàn hơn.
Phil1970

@ Phil1970 Tôi không chắc chắn rằng C ++ an toàn hơn nhiều trong trường hợp này. Chúng ta vẫn phải vượt qua các trình vòng lặp hợp lệ không vượt quá, v.v. Tôi đoán có thể sử dụng std::end(c_arr)thay vì c_arr + i_hope_this_is_the_right_number_of elementsan toàn hơn? và có lẽ quan trọng hơn, rõ ràng hơn. Và đó sẽ là điểm tôi nhấn mạnh trong trường hợp cụ thể này: std::copy()là thành ngữ hơn, dễ bảo trì hơn nếu các loại trình lặp thay đổi sau đó, dẫn đến cú pháp rõ ràng hơn, v.v.
underscore_d

1
@underscore_d std::copyan toàn hơn vì nó sao chép chính xác dữ liệu đã truyền trong trường hợp chúng không phải là kiểu POD. memcpysẽ vui vẻ sao chép một std::stringđối tượng sang một byte đại diện mới theo byte.
Jens

3

Chỉ là một bổ sung nhỏ: Chênh lệch tốc độ giữa memcpy()std::copy()có thể thay đổi khá nhiều tùy thuộc vào việc tối ưu hóa được bật hay tắt. Với g ++ 6.2.0 và không tối ưu hóa memcpy()sẽ thắng rõ ràng:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy            17 ns         17 ns   40867738
bm_stdcopy           62 ns         62 ns   11176219
bm_stdcopy_n         72 ns         72 ns    9481749

Khi tối ưu hóa được bật ( -O3), mọi thứ lại trông khá giống nhau:

Benchmark             Time           CPU Iterations
---------------------------------------------------
bm_memcpy             3 ns          3 ns  274527617
bm_stdcopy            3 ns          3 ns  272663990
bm_stdcopy_n          3 ns          3 ns  274732792

Mảng càng lớn thì hiệu ứng càng ít được chú ý, nhưng ngay cả tại N=1000 memcpy() nhanh hơn gấp đôi khi không tối ưu hóa.

Mã nguồn (yêu cầu Google Điểm chuẩn):

#include <string.h>
#include <algorithm>
#include <vector>
#include <benchmark/benchmark.h>

constexpr int N = 10;

void bm_memcpy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    memcpy(r.data(), a.data(), N * sizeof(int));
  }
}

void bm_stdcopy(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy(a.begin(), a.end(), r.begin());
  }
}

void bm_stdcopy_n(benchmark::State& state)
{
  std::vector<int> a(N);
  std::vector<int> r(N);

  while (state.KeepRunning())
  {
    std::copy_n(a.begin(), N, r.begin());
  }
}

BENCHMARK(bm_memcpy);
BENCHMARK(bm_stdcopy);
BENCHMARK(bm_stdcopy_n);

BENCHMARK_MAIN()

/* EOF */

18
Đo hiệu suất với tối ưu hóa bị vô hiệu hóa là ... tốt ... khá vô nghĩa ... Nếu bạn quan tâm đến hiệu suất, bạn sẽ không biên dịch mà không tối ưu hóa.
bolov

3
@bolov Không phải lúc nào. Một chương trình tương đối nhanh dưới gỡ lỗi là trong một số trường hợp quan trọng cần phải có.
Acorn

2

Nếu bạn thực sự cần hiệu suất sao chép tối đa (mà bạn có thể không), hãy sử dụng cả hai .

rất nhiều việc có thể được thực hiện để tối ưu hóa việc sao chép bộ nhớ - thậm chí nhiều hơn nếu bạn sẵn sàng sử dụng nhiều luồng / lõi cho nó. Xem, ví dụ:

Điều gì thiếu / tối ưu phụ trong triển khai memcpy này?

cả câu hỏi và một số câu trả lời đều đề xuất triển khai hoặc liên kết đến việc triển khai.


4
chế độ dành cho người đi bộ: với cảnh báo thông thường rằng " không sử dụng cả hai " có nghĩa là nếu bạn đã chứng minh rằng bạn có một tình huống / yêu cầu rất cụ thể mà chức năng Tiêu chuẩn do việc triển khai của bạn cung cấp không đủ nhanh ; mặt khác, mối quan tâm thông thường của tôi là những người chưa được chứng minh rằng đã bị bỏ qua việc tối ưu hóa mã sao chép sớm thay vì các phần thường hữu ích hơn trong chương trình của họ.
gạch dưới

-2

Hồ sơ cho thấy tuyên bố: std::copy()luôn luôn nhanh memcpy()hoặc nhanh là sai.

Hệ thống của tôi:

HP-Compaq-dx7500-Microtower 3.13.0-24-chung # 47-Ubuntu SMP Thứ Sáu ngày 2 tháng 5 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU / Linux.

gcc (Ubuntu 4.8.2-19ubfox1) 4.8.2

Mã (ngôn ngữ: c ++):

    const uint32_t arr_size = (1080 * 720 * 3); //HD image in rgb24
    const uint32_t iterations = 100000;
    uint8_t arr1[arr_size];
    uint8_t arr2[arr_size];
    std::vector<uint8_t> v;

    main(){
        {
            DPROFILE;
            memcpy(arr1, arr2, sizeof(arr1));
            printf("memcpy()\n");
        }

        v.reserve(sizeof(arr1));
        {
            DPROFILE;
            std::copy(arr1, arr1 + sizeof(arr1), v.begin());
            printf("std::copy()\n");
        }

        {
            time_t t = time(NULL);
            for(uint32_t i = 0; i < iterations; ++i)
                memcpy(arr1, arr2, sizeof(arr1));
            printf("memcpy()    elapsed %d s\n", time(NULL) - t);
        }

        {
            time_t t = time(NULL);
            for(uint32_t i = 0; i < iterations; ++i)
                std::copy(arr1, arr1 + sizeof(arr1), v.begin());
            printf("std::copy() elapsed %d s\n", time(NULL) - t);
        }
    }

g ++ -O0 -o test_stdcopy test_stdcopy.cpp

memcpy () profile: main: 21: now: 1422969084: 04859 đã trôi qua: 2650 us
std :: copy () profile: main: 27: now: 1422969084: 04862 trôi qua: 2745 chúng tôi
memcpy () đã trôi qua 44 s std :: ) trôi qua 45 giây

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

memcpy () profile: main: 21: now: 1422969601: 04939 đã trôi qua: 2385 us
std :: copy () profile: main: 28: now: 1422969601: 04941 đã trôi qua: 2690 chúng tôi
memcpy () đã trôi qua ) trôi qua 43 giây

Red Alert chỉ ra rằng mã sử dụng memcpy từ mảng sang mảng và std :: sao chép từ mảng sang vector. Đó là một lý do cho memcpy nhanh hơn.

Vì có

v.reserve (sizeof (mảng1));

sẽ không có sự khác biệt trong việc sao chép vào vector hoặc mảng.

Mã được cố định để sử dụng mảng cho cả hai trường hợp. memcpy vẫn nhanh hơn:

{
    time_t t = time(NULL);
    for(uint32_t i = 0; i < iterations; ++i)
        memcpy(arr1, arr2, sizeof(arr1));
    printf("memcpy()    elapsed %ld s\n", time(NULL) - t);
}

{
    time_t t = time(NULL);
    for(uint32_t i = 0; i < iterations; ++i)
        std::copy(arr1, arr1 + sizeof(arr1), arr2);
    printf("std::copy() elapsed %ld s\n", time(NULL) - t);
}

memcpy()    elapsed 44 s
std::copy() elapsed 48 s 

1
sai, hồ sơ của bạn cho thấy rằng sao chép vào một mảng nhanh hơn so với sao chép vào một vectơ. Đề ra.
Thông báo đỏ

Tôi có thể sai, nhưng trong ví dụ đã sửa của bạn, với memcpy, không phải bạn sao chép Array2 vào Array1, trong khi với std :: copy, bạn đang sao chép Array1 vào Array2? ... Những gì bạn có thể làm là tạo nhiều, xen kẽ thử nghiệm (một lần một đợt memcpy, một lần một đợt std :: copy, sau đó quay lại lần nữa với memcopy, v.v., nhiều lần.). Sau đó, tôi sẽ sử dụng clock () thay vì time (), vì ai biết PC của bạn có thể làm gì ngoài chương trình đó. Chỉ hai xu của tôi, mặc dù ... :-)
paercebal

7
Vì vậy, việc chuyển std::copytừ một vectơ sang một mảng bằng cách nào đó thực hiện memcpymất gần gấp đôi thời gian? Dữ liệu này rất đáng ngờ. Tôi đã biên dịch mã của bạn bằng gcc với -O3 và lắp ráp được tạo giống nhau cho cả hai vòng. Vì vậy, bất kỳ sự khác biệt về thời gian bạn quan sát trên máy của bạn chỉ là ngẫu nhiên.
Báo động đỏ
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.