จะดีกว่าถ้าใช้ std :: memcpy () หรือ std :: copy () ในแง่ของประสิทธิภาพ?


163

จะดีกว่าที่จะใช้memcpyตามที่แสดงด้านล่างหรือดีกว่าที่จะใช้std::copy()ในแง่ของประสิทธิภาพ? ทำไม?

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]);

โปรดทราบว่าcharสามารถลงนามหรือไม่ได้ลงนามขึ้นอยู่กับการใช้งาน หากจำนวนไบต์สามารถเป็น> = 128 ให้ใช้unsigned charสำหรับอาร์เรย์ไบต์ของคุณ (ผู้(int *)แสดงจะปลอดภัยเหมือน(unsigned int *)กัน)
Dan Breslau

13
ทำไมคุณไม่ใช้std::vector<char>? หรือหลังจากที่คุณพูดว่าbits, std::bitset?
GManNickG

2
ที่จริงแล้วคุณช่วยอธิบายให้ฉันฟังได้(int*) copyMe->bits[0]ไหม
3728501

4
ไม่แน่ใจว่าทำไมบางสิ่งบางอย่างที่ดูเหมือนว่าเป็นระเบียบกับบริบทที่สำคัญเล็ก ๆ น้อย ๆ ที่ให้ไว้ที่ +81 แต่เฮ้ @ user3728501 ฉันเดาว่าจุดเริ่มต้นของบัฟเฟอร์ถือเป็นintขนาดที่กำหนด แต่ดูเหมือนว่าจะเป็นสูตรสำหรับภัยพิบัติที่กำหนดโดยการนำไปปฏิบัติเช่นเดียวกับสิ่งอื่น ๆ อีกมากมายที่นี่
underscore_d

2
ในความเป็นจริงการ(int *)โยนนั้นเป็นเพียงพฤติกรรมบริสุทธิ์ที่ไม่ได้กำหนดไม่ได้กำหนดการใช้งาน การพยายามพิมพ์การสะกดคำผ่านนักแสดงเป็นการละเมิดกฎนามแฝงที่เข้มงวดและด้วยเหตุนี้มาตรฐานจึงไม่ได้กำหนดอย่างสมบูรณ์ (นอกจากนี้ใน C ++ แม้จะไม่ได้ C คุณไม่สามารถพิมพ์-เล่นสำนวนผ่านunionทั้ง.) สวยมากยกเว้นอย่างเดียวคือถ้าคุณกำลังแปลงจะแตกต่างของchar*แต่ค่าเผื่อไม่สมมาตร
underscore_d

คำตอบ:


207

ฉันจะต่อต้านภูมิปัญญาทั่วไปที่นี่ซึ่งstd::copyจะมีการสูญเสียประสิทธิภาพเล็กน้อยที่มองไม่เห็น ฉันเพิ่งทดสอบและพบว่าไม่เป็นความจริง: ฉันสังเกตเห็นความแตกต่างด้านประสิทธิภาพ อย่างไรก็ตามผู้ชนะคือstd::copyอย่างไรก็ตามผู้ชนะคือ

ฉันเขียนการใช้ C ++ SHA-2 ในการทดสอบของฉันฉันแฮช 5 สายโดยใช้ SHA-2 ทั้งสี่รุ่น (224, 256, 384, 512) และฉันวนซ้ำ 300 ครั้ง ฉันวัดเวลาโดยใช้ Boost.timer ตัวนับ 300 วนนั้นเพียงพอที่จะทำให้ผลลัพธ์ของฉันคงที่อย่างสมบูรณ์ ฉันรันการทดสอบ 5 ครั้งแต่ละครั้งสลับกันระหว่างmemcpyรุ่นกับstd::copyรุ่น รหัสของฉันใช้ประโยชน์จากการจับข้อมูลในกลุ่มก้อนใหญ่ที่สุดเท่าที่จะเป็นไปได้ (การใช้งานอื่น ๆ จำนวนมากทำงานด้วยchar/ char *ในขณะที่ฉันทำงานด้วยT/ T *(ซึ่งTเป็นประเภทที่ใหญ่ที่สุดในการใช้งานของผู้ใช้ที่มีพฤติกรรมล้น) ประเภทที่ใหญ่ที่สุดที่ฉันสามารถเป็นศูนย์กลางของประสิทธิภาพของอัลกอริทึมของฉันเหล่านี้คือผลลัพธ์ของฉัน:

เวลา (เป็นวินาที) เพื่อให้การทดสอบ 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%

ความเร็วเฉลี่ยที่เพิ่มขึ้นโดยเฉลี่ยของ std :: copy over memcpy: 2.99%

คอมไพเลอร์ของฉันคือ gcc 4.6.3 ใน Fedora 16 x86_64 -Ofast -march=native -funsafe-loop-optimizationsธงเพิ่มประสิทธิภาพของฉัน

รหัสสำหรับการใช้งาน SHA-2 ของฉัน

ฉันตัดสินใจทำการทดสอบการใช้งาน MD5 ของฉันเช่นกัน ผลลัพธ์มีความเสถียรน้อยลงมากฉันเลยตัดสินใจวิ่ง 10 ครั้ง อย่างไรก็ตามหลังจากความพยายามครั้งแรกของฉันฉันได้รับผลลัพธ์ที่แตกต่างกันอย่างมากจากการวิ่งครั้งต่อไปดังนั้นฉันคาดว่าจะมีกิจกรรม OS บางประเภทเกิดขึ้น ฉันตัดสินใจที่จะเริ่มต้นใหม่

การตั้งค่าคอมไพเลอร์และแฟล็กเดียวกัน มี MD5 เพียงเวอร์ชันเดียวเท่านั้นและเร็วกว่า SHA-2 ดังนั้นฉันจึงทดสอบ 3000 ลูปในชุดทดสอบ 5 ชุดที่คล้ายกัน

นี่คือผลลัพธ์ 10 ข้อสุดท้ายของฉัน:

เวลา (เป็นวินาที) เพื่อให้การทดสอบ 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%

ความเร็วเฉลี่ยลดลงโดยเฉลี่ยของ std :: copy over memcpy: 0.11%

รหัสสำหรับการนำ MD5 ไปใช้

ผลลัพธ์เหล่านี้แนะนำว่ามีการเพิ่มประสิทธิภาพบางอย่างที่ std :: copy ใช้ในการทดสอบ SHA-2 ของฉันที่std::copyไม่สามารถใช้ในการทดสอบ MD5 ของฉันได้ ในการทดสอบ SHA-2 อาร์เรย์ทั้งสองถูกสร้างขึ้นในฟังก์ชั่นเดียวกับที่เรียกว่าstd::copy/memcpy /ในการทดสอบ MD5 ของฉันหนึ่งในอาร์เรย์ถูกส่งผ่านไปยังฟังก์ชั่นเป็นพารามิเตอร์ฟังก์ชั่น

ฉันทำการทดสอบอีกเล็กน้อยเพื่อดูว่าฉันสามารถทำอะไรได้บ้างเพื่อทำให้std::copyเร็วขึ้นอีกครั้ง คำตอบกลายเป็นเรื่องง่าย: เปิดการเพิ่มประสิทธิภาพเวลาลิงค์ นี่คือผลลัพธ์ของฉันเมื่อเปิดใช้งาน LTO (ตัวเลือก -flto เป็น gcc):

เวลา (เป็นวินาที) เพื่อให้การทดสอบ MD5 เสร็จสมบูรณ์ด้วย -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%

การเพิ่มความเร็วเฉลี่ยโดยเฉลี่ยของ std :: คัดลอกไปที่ memcpy: 0.72%

std::copyโดยสรุปมีไม่ปรากฏเป็นโทษประสิทธิภาพสำหรับการใช้ ในความเป็นจริงดูเหมือนว่าจะมีประสิทธิภาพเพิ่มขึ้น

คำอธิบายของผลลัพธ์

เหตุใดจึงอาจstd::copyเพิ่มประสิทธิภาพ

ครั้งแรกฉันจะไม่คาดหวังว่ามันจะช้าลงสำหรับการดำเนินการใด ๆ ตราบใดที่การเพิ่มประสิทธิภาพของการเปิดอินไลน์ คอมไพเลอร์ทั้งหมดอินไลน์อย่างจริงจัง มันอาจเป็นการปรับให้เหมาะสมที่สำคัญที่สุดเพราะช่วยให้การปรับแต่งอื่น ๆ มีประสิทธิภาพมากขึ้น std::copyสามารถ (และฉันสงสัยว่าการใช้งานจริงทั้งหมดทำ) ตรวจพบว่าข้อโต้แย้งนั้นสามารถคัดลอกได้เล็กน้อยและหน่วยความจำนั้นเรียงตามลำดับ ซึ่งหมายความว่าในกรณีที่เลวร้ายที่สุดเมื่อmemcpyถูกกฎหมายstd::copyควรดำเนินการไม่เลว การใช้งานเล็กน้อยของstd::copydefers ที่memcpyควรเป็นไปตามเกณฑ์ของคอมไพเลอร์ของคุณ "เสมอแบบอินไลน์นี้เมื่อเพิ่มประสิทธิภาพสำหรับความเร็วหรือขนาด"

อย่างไรก็ตามstd::copyยังเก็บข้อมูลได้มากขึ้น เมื่อคุณโทรstd::copyฟังก์ชั่นจะรักษาประเภทไว้เหมือนเดิม memcpyทำงานในvoid *ซึ่งจะทิ้งข้อมูลที่เป็นประโยชน์เกือบทั้งหมด ตัวอย่างเช่นถ้าฉันส่งผ่านอาร์เรย์std::uint64_tผู้รวบรวมหรือไลบรารี implementer อาจจะสามารถใช้ประโยชน์จากการจัดตำแหน่ง 64 บิตด้วยstd::copyแต่มันอาจเป็นเรื่องยากที่จะทำเช่นmemcpyนั้น การใช้อัลกอริธึมหลายอย่างเช่นงานนี้โดยการทำงานครั้งแรกในส่วนที่ไม่มีการจัดแนวที่จุดเริ่มต้นของช่วงจากนั้นส่วนที่จัดชิดแล้วส่วนที่ไม่ได้จัดแนวในตอนท้าย หากมีการรับประกันว่าจะจัดแนวทั้งหมดรหัสนั้นจะง่ายขึ้นและเร็วขึ้นและง่ายขึ้นสำหรับตัวพยากรณ์สาขาในโปรเซสเซอร์ของคุณเพื่อให้ถูกต้อง

การเพิ่มประสิทธิภาพก่อนวัย?

std::copyอยู่ในตำแหน่งที่น่าสนใจ ฉันคาดหวังว่ามันจะไม่ช้ากว่าmemcpyและบางครั้งก็เร็วขึ้นด้วยคอมไพเลอร์การเพิ่มประสิทธิภาพที่ทันสมัย นอกจากนี้สิ่งที่คุณสามารถคุณสามารถmemcpy ไม่อนุญาตให้มีการทับซ้อนกันในบัฟเฟอร์ในขณะที่รองรับการทับซ้อนในทิศทางเดียว ( สำหรับทิศทางอื่นของการทับซ้อน) ทำงานเฉพาะในตัวชี้ทำงานบน iterators ใด ๆ ( , , หรือประเภทที่กำหนดเองของตัวเอง) กล่าวอีกนัยหนึ่งคุณควรใช้เมื่อคุณต้องการคัดลอกข้อมูลจำนวนหนึ่งstd::copymemcpystd::copystd::copy_backwardmemcpystd::copystd::mapstd::vectorstd::dequestd::copy


35
ฉันต้องการเน้นย้ำว่านี่ไม่ได้หมายความว่าstd::copyเร็วกว่า 2.99% หรือ 0.72% หรือ -0.11% memcpyเวลาเหล่านี้มีไว้สำหรับโปรแกรมทั้งหมดที่จะดำเนินการ อย่างไรก็ตามโดยทั่วไปฉันรู้สึกว่ามาตรฐานในรหัสจริงมีประโยชน์มากกว่ามาตรฐานในรหัสปลอม โปรแกรมทั้งหมดของฉันมีการเปลี่ยนแปลงความเร็วในการทำงาน เอฟเฟ็กต์ที่แท้จริงของเพียงสองชุดคัดลอกจะมีความแตกต่างมากกว่าที่นี่เมื่อนำมาแยก แต่นี่แสดงให้เห็นว่าพวกเขาสามารถมีความแตกต่างที่วัดได้ในโค้ดจริง
David Stone

2
ฉันไม่เห็นด้วยกับสิ่งที่คุณค้นพบ แต่ผลลัพธ์เป็นผลลัพธ์: / อย่างไรก็ตามคำถามหนึ่ง (ฉันรู้ว่ามันเป็นเวลานานแล้วและคุณจำการวิจัยไม่ได้ดังนั้นเพียงแค่แสดงความคิดเห็นในแบบที่คุณคิด) คุณอาจไม่ได้มองเข้าไปในรหัสการชุมนุม
ลำดับที่ 3

2
ในความคิดของฉันmemcpyและstd::copyมีการใช้งานที่แตกต่างกันดังนั้นในบางกรณีคอมไพเลอร์ปรับรหัสรอบและรหัสการคัดลอกหน่วยความจำที่เกิดขึ้นจริงเป็นส่วนหนึ่งของรหัส คำอื่น ๆบางครั้งคำหนึ่งดีกว่าอีกคำและแม้แต่ในคำอื่น ๆ การตัดสินใจที่จะใช้คือการคลอดก่อนกำหนดหรือการเพิ่มประสิทธิภาพที่โง่เง่าเพราะในทุกสถานการณ์ที่คุณต้องทำวิจัยใหม่และยิ่งไปกว่านั้นโปรแกรมมักจะถูกพัฒนาดังนั้นหลังจาก การเปลี่ยนแปลงเล็กน้อยของข้อได้เปรียบของฟังก์ชั่นอื่น ๆ อาจจะหายไป
ST3

3
@ ST3: ฉันจะจินตนาการว่าในกรณีที่เลวร้ายที่สุดstd::copyเป็นฟังก์ชั่นอินไลน์เล็กน้อยที่เพิ่งโทรmemcpyเมื่อถูกกฎหมาย การอินไลน์พื้นฐานจะกำจัดความแตกต่างของประสิทธิภาพเชิงลบใด ๆ ฉันจะอัปเดตโพสต์พร้อมคำอธิบายเล็กน้อยว่าทำไม std :: copy อาจเร็วกว่า
David Stone

7
การวิเคราะห์ข้อมูลมาก Re ความเร็วเฉลี่ยลดลงโดยเฉลี่ยทั้งหมดใน std :: คัดลอกไปที่ memcpy: 0.11%ในขณะที่จำนวนนั้นถูกต้องผลลัพธ์ไม่ได้มีนัยสำคัญทางสถิติ ช่วงความมั่นใจ 95% สำหรับความแตกต่างหมายถึงคือ (-0.013s, 0.025) ซึ่งรวมถึงศูนย์ ตามที่คุณชี้ให้เห็นว่ามีการเปลี่ยนแปลงจากแหล่งข้อมูลอื่นและด้วยข้อมูลของคุณคุณอาจจะบอกว่าประสิทธิภาพนั้นเหมือนกัน สำหรับการอ้างอิงผลลัพธ์อีกสองรายการนั้นมีนัยสำคัญทางสถิติ - โอกาสที่คุณจะเห็นความแตกต่างในช่วงเวลาที่รุนแรงโดยโอกาสนี้อยู่ที่ 1 ใน 100 ล้าน (ครั้งแรก) และ 1 ใน 20,000 (สุดท้าย)
TooTone

78

คอมไพเลอร์ทั้งหมดที่ฉันรู้จักจะแทนที่วิstd::copyด้วยตัวอักษร a memcpyเมื่อมันเหมาะสมหรือดีกว่าทำให้ vectorize การคัดลอกเพื่อที่จะเร็วกว่าmemcpyคัดลอกเพื่อที่มันจะได้เร็วกว่า

ไม่ว่าในกรณีใด: โปรไฟล์และค้นหาด้วยตัวคุณเอง คอมไพเลอร์ต่าง ๆ จะทำสิ่งที่แตกต่างและเป็นไปได้มากที่มันจะไม่ทำสิ่งที่คุณถาม

ดู การนำเสนอนี้เกี่ยวกับการเพิ่มประสิทธิภาพของคอมไพเลอร์ (pdf)

นี่คือสิ่งที่ GCC ทำเพื่อง่ายๆstd::copyประเภท POD อย่างง่าย

#include <algorithm>

struct foo
{
  int x, y;    
};

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

นี่คือการถอดแยกชิ้นส่วน (ที่มี-Oการปรับให้เหมาะสมเท่านั้น) โดยแสดงการเรียกไปที่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

หากคุณเปลี่ยนฟังก์ชั่นลายเซ็นเป็น

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

จากนั้นmemmoveกลายเป็น a memcpyสำหรับการปรับปรุงประสิทธิภาพเล็กน้อย โปรดทราบว่าmemcpyตัวเองจะถูก vectorised อย่างหนัก


1
ฉันจะทำโปรไฟล์ได้อย่างไร เครื่องมืออะไรที่ใช้ (ใน windows และ linux)
user576670

5
@ Konrad คุณถูกต้อง แต่memmoveไม่ควรจะเร็วกว่า - แต่ควรช้าลงเล็กน้อยเนื่องจากต้องคำนึงถึงความเป็นไปได้ที่ช่วงข้อมูลทั้งสองซ้อนทับกัน ผมคิดว่าใบอนุญาตที่ทับซ้อนกันของข้อมูลและดังนั้นจึงมีการเรียกร้องstd::copy memmove
Charles Salvia

2
@ Konrad: หาก memmove เร็วกว่า memcpy เสมอ memcpy จะเรียก memmove สิ่งที่ std :: copy จริงอาจส่งไปยัง (ถ้ามี) ถูกกำหนดให้นำไปปฏิบัติดังนั้นจึงไม่มีประโยชน์ที่จะกล่าวถึงเฉพาะเจาะจงโดยไม่พูดถึงการนำไปใช้
Fred Nurk

1
แม้ว่าโปรแกรมง่ายๆที่จะทำให้เกิดปัญหานี้รวบรวมกับ -O3 ภายใต้ GCC memcpyแสดงให้เห็นว่าฉัน มันทำให้ฉันเชื่อว่า GCC ตรวจสอบว่ามีหน่วยความจำทับซ้อนกันหรือไม่
jweyrich

1
@ Konrad: มาตรฐานstd::copyยอมให้เหลื่อมกันในทิศทางเดียว แต่ไม่ใช่อีกทิศทางหนึ่ง จุดเริ่มต้นของเอาต์พุตไม่สามารถอยู่ในช่วงอินพุตได้ แต่อนุญาตให้เริ่มต้นอินพุตได้ภายในช่วงเอาต์พุต นี่เป็นเรื่องแปลกเล็กน้อยเนื่องจากมีการกำหนดลำดับของการมอบหมายและการโทรอาจเป็น UB ถึงแม้ว่าจะมีการกำหนดเอฟเฟกต์ของการมอบหมายเหล่านั้นตามลำดับ แต่ฉันคิดว่าข้อ จำกัด อนุญาตการเพิ่มประสิทธิภาพ vectorization
Steve Jessop

24

ใช้เสมอstd::copyเนื่องจากmemcpyถูก จำกัด เฉพาะโครงสร้าง POD แบบ C เท่านั้นและคอมไพเลอร์อาจแทนที่การโทรstd::copyด้วยmemcpyหากเป้าหมายเป็น POD จริง

นอกจากนี้std::copyยังสามารถใช้กับตัววนซ้ำหลายประเภทไม่เพียงแค่พอยน์เตอร์ std::copyมีความยืดหยุ่นมากขึ้นสำหรับการไม่สูญเสียประสิทธิภาพและเป็นผู้ชนะที่ชัดเจน


ทำไมคุณควรคัดลอกตัววนซ้ำไปมา
Atmocreations

3
คุณไม่ได้คัดลอกตัววนซ้ำ แต่เป็นช่วงที่กำหนดโดยตัววนซ้ำสองตัว ยกตัวอย่างเช่นstd::copy(container.begin(), container.end(), destination);จะคัดลอกเนื้อหาของcontainer(ทุกอย่างระหว่างbeginและend) destinationลงในบัฟเฟอร์ที่ระบุโดย std::copyไม่จำเป็นต้องมีแง่เหมือนหรือ&*container.begin() &container.back() + 1
David Stone

16

ในทางทฤษฎีmemcpyอาจจะมีเล็กน้อย , มองไม่เห็น , เล็กstd::copyได้เปรียบประสิทธิภาพเพียงเพราะมันไม่ได้มีความต้องการเช่นเดียวกับ จากหน้าคนของmemcpy:

เพื่อหลีกเลี่ยงการล้นขนาดของอาร์เรย์ที่ชี้โดยพารามิเตอร์ปลายทางและแหล่งที่มาจะต้องมีอย่างน้อย num ไบต์และไม่ควรทับซ้อนกัน (สำหรับบล็อกหน่วยความจำที่ทับซ้อนกัน memmove เป็นวิธีที่ปลอดภัยกว่า)

ในคำอื่น ๆmemcpyสามารถละเว้นความเป็นไปได้ของข้อมูลที่ทับซ้อนกัน (การผ่านอาร์เรย์ที่ทับซ้อนกันไปmemcpyเป็นพฤติกรรมที่ไม่ได้กำหนด) ดังนั้นmemcpyไม่จำเป็นต้องตรวจสอบเงื่อนไขนี้อย่างชัดเจนในขณะที่std::copyสามารถใช้งานได้ตราบใดที่OutputIteratorพารามิเตอร์ไม่ได้อยู่ในช่วงแหล่งที่มา โปรดทราบว่านี่ไม่เหมือนกับการบอกว่าช่วงต้นทางและช่วงปลายทางไม่สามารถทับซ้อนกันได้

ดังนั้นเนื่องจากstd::copyมีข้อกำหนดที่แตกต่างกันบ้างในทางทฤษฎีมันควรจะช้ากว่าเล็กน้อย (โดยเน้นที่สุด ๆไปเล็กน้อย ) เนื่องจากมันอาจจะตรวจสอบ C-arrays ที่ทับซ้อนกันmemmoveที่ต้องการที่จะดำเนินการ ตรวจสอบ แต่ในทางปฏิบัติคุณ (และโปรไฟล์ส่วนใหญ่) อาจไม่ได้ตรวจพบความแตกต่าง

แน่นอนถ้าคุณไม่ได้ทำงานกับPODคุณก็ไม่สามารถใช้งานmemcpyได้


7
std::copy<char>นี้เป็นจริงสำหรับ แต่std::copy<int>สามารถสันนิษฐานได้ว่าอินพุตนั้นอยู่ในแนวเดียวกัน นั่นจะสร้างความแตกต่างที่ยิ่งใหญ่กว่าเพราะมันมีผลกับทุกองค์ประกอบ การทับซ้อนเป็นการตรวจสอบครั้งเดียว
MSalters

2
@Malters, จริง แต่การใช้งานส่วนใหญ่ของmemcpyฉันได้เห็นการตรวจสอบการจัดตำแหน่งและพยายามที่จะคัดลอกคำมากกว่า byte byte byte
Charles Salvia

1
std :: copy () สามารถละเว้นหน่วยความจำที่ทับซ้อนกันได้เช่นกัน หากคุณต้องการรองรับหน่วยความจำที่ทับซ้อนกันคุณต้องเขียนตรรกะด้วยตัวคุณเองเพื่อเรียก std :: reverse_copy () ในสถานการณ์ที่เหมาะสม
Cygon

2
มีอาร์กิวเมนต์ตรงข้ามที่สามารถทำได้คือ: เมื่อผ่านmemcpyอินเตอร์เฟสมันจะสูญเสียข้อมูลการจัดตำแหน่ง ดังนั้นmemcpyจะต้องทำการตรวจสอบการจัดตำแหน่งในเวลาทำงานเพื่อจัดการจุดเริ่มต้นและสิ้นสุดที่ไม่ได้จัดแนว เช็คเหล่านั้นอาจมีราคาถูก แต่ไม่ฟรี ในขณะที่std::copyสามารถหลีกเลี่ยงการตรวจสอบและ vectorize เหล่านี้ นอกจากนี้คอมไพเลอร์อาจพิสูจน์ได้ว่าต้นทางและปลายทางอาร์เรย์ไม่ทับซ้อนและอีกครั้ง vectorize โดยที่ผู้ใช้ไม่ต้องเลือกระหว่างและmemcpy memmove
Maxim Egorushkin

11

กฎของฉันเรียบง่าย หากคุณใช้ C ++ ชอบไลบรารี่ C ++ ไม่ใช่ C :)


40
C ++ ได้รับการออกแบบอย่างชัดเจนเพื่ออนุญาตให้ใช้ไลบรารี C นี่ไม่ใช่อุบัติเหตุ มันมักจะดีกว่าที่จะใช้ std :: copy มากกว่า memcpy ใน C ++ แต่สิ่งนี้ไม่เกี่ยวข้องกับสิ่งที่หนึ่งคือ C และอาร์กิวเมนต์ชนิดนั้นมักจะเป็นวิธีที่ผิด
Fred Nurk

2
@FredNurk โดยปกติคุณต้องการหลีกเลี่ยงจุดอ่อนของ C โดยที่ C ++ เป็นทางเลือกที่ปลอดภัยกว่า
Phil1970

@ Phil1970 ฉันไม่แน่ใจว่า C ++ ปลอดภัยกว่าในกรณีนี้มาก เรายังต้องผ่านตัววนซ้ำที่ถูกต้องที่ไม่ย่ำยี ฯลฯ ฉันคิดว่าการใช้std::end(c_arr)แทนc_arr + i_hope_this_is_the_right_number_of elementsปลอดภัยกว่าได้หรือไม่ และอาจสำคัญกว่าชัดเจนกว่า และนั่นคือจุดที่ฉันเน้นในกรณีเฉพาะนี้: std::copy()เป็นสำนวนมากขึ้นบำรุงรักษามากขึ้นหากประเภทของตัววนซ้ำเปลี่ยนไปในภายหลังนำไปสู่ไวยากรณ์ที่ชัดเจน ฯลฯ
underscore_d

1
@underscore_d std::copyปลอดภัยกว่าเพราะคัดลอกข้อมูลที่ส่งผ่านอย่างถูกต้องในกรณีที่ไม่ใช่ประเภท POD memcpyจะคัดลอกstd::stringวัตถุอย่างมีความสุขไปสู่การเป็นตัวแทนไบต์ใหม่
Jens

3

เพียงเล็กน้อยเพิ่มเติม: ความแตกต่างความเร็วระหว่างmemcpy()และstd::copy()สามารถแตกต่างกันเล็กน้อยขึ้นอยู่กับว่าการเพิ่มประสิทธิภาพจะเปิดใช้งานหรือปิดการใช้งาน ด้วย g ++ 6.2.0 และไม่มีการปรับแต่งให้memcpy()ชนะอย่างชัดเจน:

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

เมื่อเปิดใช้งานการปรับให้เหมาะสม ( -O3) ทุกอย่างจะดูเหมือนกันอีกครั้ง:

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

อาร์เรย์ยิ่งใหญ่ขึ้นเอฟเฟกต์ที่สังเกตเห็นได้ก็จะน้อยลง แต่ถึงแม้N=1000 memcpy()จะเร็วกว่าถึงสองเท่าเมื่อเปิดใช้งานการปรับให้เหมาะสม

ซอร์สโค้ด (ต้องใช้ Google Benchmark):

#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
การวัดประสิทธิภาพด้วยการเพิ่มประสิทธิภาพถูกปิดใช้งานคือ ... ดี ... ไม่มีจุดหมายสวยมาก ... หากคุณสนใจในประสิทธิภาพการทำงานคุณจะไม่สามารถรวบรวมได้หากไม่มีการเพิ่มประสิทธิภาพ
bolov

3
@bolov ไม่เสมอไป โปรแกรมที่ค่อนข้างเร็วภายใต้การดีบักนั้นในบางกรณีสำคัญที่ต้องมี
โอ๊ก

2

หากคุณต้องการประสิทธิภาพการคัดลอกสูงสุดจริง ๆ (ซึ่งคุณอาจไม่ได้) ให้ใช้ทั้งสองอย่างอย่าง

มีจำนวนมากที่สามารถทำได้เพื่อเพิ่มประสิทธิภาพการคัดลอกหน่วยความจำ - ยิ่งถ้าคุณยินดีที่จะใช้หลายเธรด / แกนสำหรับมัน ดูตัวอย่าง:

มีอะไรหายไป / ย่อยที่ดีที่สุดในการใช้ memcpy นี้?

ทั้งคำถามและคำตอบบางข้อได้แนะนำการใช้งานหรือลิงค์ไปยังการใช้งาน


4
โหมดอวดรู้: มีข้อแม้ปกติว่า " ใช้ไม่ของพวกเขา " หมายความว่าถ้าคุณได้รับการพิสูจน์ว่าคุณมีสถานการณ์ที่เฉพาะเจาะจงสูง / ความต้องการที่ค่าฟังก์ชั่นมาตรฐานการให้บริการโดยการดำเนินการของคุณเร็วพอ ; ไม่เช่นนั้นความกังวลตามปกติของฉันคือผู้ที่ไม่ได้รับการพิสูจน์ว่าได้รับข้อมูลที่ผิดพลาดจากการคัดลอกโค้ดให้เหมาะสมที่สุดแทนที่จะเป็นส่วนที่มีประโยชน์มากกว่าของโปรแกรม
underscore_d

-2

การทำโปรไฟล์แสดงให้เห็นว่าคำสั่ง: std::copy()รวดเร็วเสมอmemcpy()หรือเร็วกว่าเป็นเท็จ

ระบบของฉัน:

HP-Compaq-dx7500-Microtower 3.13.0-24-generic # 47-Ubuntu SMP ศุกร์ 2 พฤษภาคม 23:30:00 UTC 2014 x86_64 x86_64 x86_64 GNU / Linux

gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

รหัส (ภาษา: 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 (): main: 21: ตอนนี้: 1422969084: 04859 ที่ผ่านไป: 2650 us
std :: คัดลอก () โปรไฟล์: main: 27: ตอนนี้: 1422969084: 04862 ที่ผ่านไป: 2745 us
memcpy () el 44 44 std :: คัดลอก () ) ผ่านไป 45 วินาที

g ++ -O3 -o test_stdcopy test_stdcopy.cpp

โปรไฟล์ memcpy (): main: 21: ตอนนี้: 1422969601: 04939 ที่ผ่านไป: 2385 us
std :: copy () โปรไฟล์: main: 28: ตอนนี้: 1422969601: 04941 ที่ผ่านไป: 2690 us
memcpy () elapsed 27 s std :: คัดลอก () ) ผ่านไป 43 วินาที

Red Alert ชี้ให้เห็นว่ารหัสใช้ memcpy จาก array ไปยัง array และ std :: copy จาก array ไปยัง vector นั่นเป็นเหตุผลที่ทำให้ memcpy เร็วขึ้น

เนื่องจากมี

v.reserve (sizeof (arr1));

จะไม่มีความแตกต่างในการคัดลอกไปยังเวกเตอร์หรืออาร์เรย์

รหัสได้รับการแก้ไขเพื่อใช้อาร์เรย์สำหรับทั้งสองกรณี memcpy ยังเร็วกว่า:

{
    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
ผิดโปรไฟล์ของคุณแสดงให้เห็นว่าการคัดลอกไปยังอาร์เรย์นั้นเร็วกว่าการคัดลอกลงในเวกเตอร์ ปิดหัวข้อ
Red Alert

ฉันอาจจะผิด แต่ในตัวอย่างที่ถูกต้องของคุณด้วย memcpy คุณไม่ได้คัดลอก arr2 ไปที่ arr1 ในขณะที่ std :: copy คุณกำลังคัดลอก arr1 ไปที่ arr2? ... คุณสามารถทำอะไรได้หลายอย่างสลับกัน การทดลอง (ครั้งหนึ่งชุด memcpy ครั้งหนึ่งชุด std :: คัดลอกจากนั้นกลับมาอีกครั้งด้วย memcopy ฯลฯ หลายครั้ง) จากนั้นฉันจะใช้นาฬิกา () แทนเวลา () เพราะใครจะรู้ว่าพีซีของคุณสามารถทำอะไรได้บ้างนอกเหนือจากโปรแกรมนั้น เพียงแค่สองเซ็นต์ของฉันแม้ว่า ... :-)
paercebal

7
ดังนั้นการเปลี่ยนstd::copyจากเวกเตอร์เป็นอาร์เรย์ทำให้memcpyใช้เวลานานเกือบสองเท่าเลยเหรอ? ข้อมูลนี้เป็นที่น่าสงสัยอย่างมาก ฉันรวบรวมรหัสของคุณโดยใช้ gcc กับ -O3 และชุดประกอบที่สร้างขึ้นนั้นเหมือนกันสำหรับลูปทั้งสอง ดังนั้นความแตกต่างในเวลาที่คุณสังเกตเห็นบนเครื่องของคุณจึงเป็นเรื่องบังเอิญ
Red Alert
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.