Tôi đang điều tra các điểm nóng về hiệu suất trong một ứng dụng dành 50% thời gian của nó trong memmove (3). Ứng dụng sẽ chèn hàng triệu số nguyên 4 byte vào các mảng đã sắp xếp và sử dụng memmove để chuyển dữ liệu "sang phải" nhằm tạo khoảng trống cho giá trị được chèn.
Kỳ vọng của tôi là việc sao chép bộ nhớ cực kỳ nhanh, và tôi rất ngạc nhiên khi dành quá nhiều thời gian cho bộ nhớ. Nhưng sau đó tôi có ý tưởng rằng memmove chậm vì nó di chuyển các vùng chồng chéo, phải được thực hiện theo một vòng lặp chặt chẽ, thay vì sao chép các trang bộ nhớ lớn. Tôi đã viết một microbenchmark nhỏ để tìm hiểu xem liệu có sự khác biệt về hiệu suất giữa memcpy và memmove hay không, mong rằng memcpy sẽ thắng.
Tôi đã chạy điểm chuẩn của mình trên hai máy (core i5, core i7) và thấy rằng memmove thực sự nhanh hơn memcpy, trên core i7 cũ hơn thậm chí còn nhanh hơn gần gấp đôi! Bây giờ tôi đang tìm kiếm lời giải thích.
Đây là điểm chuẩn của tôi. Nó sao chép 100 mb với memcpy, và sau đó di chuyển khoảng 100 mb với memmove; nguồn và đích chồng chéo. Nhiều "khoảng cách" khác nhau cho nguồn và đích được thử. Mỗi bài test chạy 10 lần, thời gian in trung bình.
https://gist.github.com/cruppstahl/78a57cdf937bca3d062c
Đây là kết quả trên Core i5 (Linux 3.5.0-54-generic # 81 ~ precision1-Ubuntu SMP x86_64 GNU / Linux, gcc là 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5). Con số trong ngoặc là khoảng cách (kích thước khoảng cách) giữa nguồn và đích:
memcpy 0.0140074
memmove (002) 0.0106168
memmove (004) 0.01065
memmove (008) 0.0107917
memmove (016) 0.0107319
memmove (032) 0.0106724
memmove (064) 0.0106821
memmove (128) 0.0110633
Memmove được thực hiện dưới dạng mã trình hợp dịch được tối ưu hóa SSE, sao chép từ sau ra trước. Nó sử dụng tính năng tìm nạp trước phần cứng để tải dữ liệu vào bộ đệm và sao chép 128 byte vào các thanh ghi XMM, sau đó lưu trữ chúng tại đích.
( memcpy-ssse3-back.S , dòng 1650 ff)
L(gobble_ll_loop):
prefetchnta -0x1c0(%rsi)
prefetchnta -0x280(%rsi)
prefetchnta -0x1c0(%rdi)
prefetchnta -0x280(%rdi)
sub $0x80, %rdx
movdqu -0x10(%rsi), %xmm1
movdqu -0x20(%rsi), %xmm2
movdqu -0x30(%rsi), %xmm3
movdqu -0x40(%rsi), %xmm4
movdqu -0x50(%rsi), %xmm5
movdqu -0x60(%rsi), %xmm6
movdqu -0x70(%rsi), %xmm7
movdqu -0x80(%rsi), %xmm8
movdqa %xmm1, -0x10(%rdi)
movdqa %xmm2, -0x20(%rdi)
movdqa %xmm3, -0x30(%rdi)
movdqa %xmm4, -0x40(%rdi)
movdqa %xmm5, -0x50(%rdi)
movdqa %xmm6, -0x60(%rdi)
movdqa %xmm7, -0x70(%rdi)
movdqa %xmm8, -0x80(%rdi)
lea -0x80(%rsi), %rsi
lea -0x80(%rdi), %rdi
jae L(gobble_ll_loop)
Tại sao memmove nhanh hơn memcpy? Tôi mong đợi memcpy sao chép các trang bộ nhớ, sẽ nhanh hơn nhiều so với việc lặp lại. Trong trường hợp xấu nhất, tôi sẽ mong đợi memcpy nhanh như memmove.
Tái bút: Tôi biết rằng tôi không thể thay thế memmove bằng memcpy trong mã của mình. Tôi biết rằng mẫu mã kết hợp C và C ++. Câu hỏi này thực sự chỉ dành cho mục đích học thuật.
CẬP NHẬT 1
Tôi đã chạy một số biến thể của bài kiểm tra, dựa trên các câu trả lời khác nhau.
- Khi chạy memcpy hai lần, thì lần chạy thứ hai nhanh hơn lần chạy thứ nhất.
- Khi "chạm" vào vùng đệm đích của memcpy (
memset(b2, 0, BUFFERSIZE...)
) thì lần chạy đầu tiên của memcpy cũng nhanh hơn. - memcpy vẫn chậm hơn memmove một chút.
Đây là kết quả:
memcpy 0.0118526
memcpy 0.0119105
memmove (002) 0.0108151
memmove (004) 0.0107122
memmove (008) 0.0107262
memmove (016) 0.0108555
memmove (032) 0.0107171
memmove (064) 0.0106437
memmove (128) 0.0106648
Kết luận của tôi: dựa trên nhận xét từ @Oliver Charlesworth, hệ điều hành phải cam kết bộ nhớ vật lý ngay sau khi bộ đệm đích memcpy được truy cập lần đầu tiên (nếu ai đó biết cách "chứng minh" điều này thì hãy thêm câu trả lời! ). Ngoài ra, như @Mats Petersson đã nói, memmove thân thiện với bộ nhớ cache hơn memcpy.
Cảm ơn vì tất cả những câu trả lời và nhận xét tuyệt vời!