Array.Copy เทียบกับ Buffer.BlockCopy


124

Array.CopyและBuffer.BlockCopyทั้งคู่ทำสิ่งเดียวกัน แต่BlockCopyมุ่งเป้าไปที่การคัดลอกอาร์เรย์ดั้งเดิมระดับไบต์อย่างรวดเร็วในขณะที่Copyการใช้งานทั่วไป คำถามของฉันคือ - คุณควรใช้ภายใต้สถานการณ์ใดBlockCopy? คุณควรใช้เมื่อใดก็ได้เมื่อคุณกำลังคัดลอกอาร์เรย์ประเภทดั้งเดิมหรือคุณควรใช้เฉพาะเมื่อคุณกำลังเข้ารหัสเพื่อประสิทธิภาพเท่านั้น? มีอันตรายจากการใช้Buffer.BlockCopyเกินArray.Copyหรือไม่


3
อย่าลืมMarshal.Copy:-) ใช้Array.Copyสำหรับประเภทอ้างอิงประเภทค่าเชิงซ้อนและหากประเภทไม่เปลี่ยนแปลงBuffer.BlockCopyสำหรับ "การแปลง" ระหว่างประเภทค่าอาร์เรย์ไบต์และเวทมนตร์ไบต์ F.ex. การผสมผสานกับStructLayoutมีประสิทธิภาพมากหากคุณรู้ว่าคุณกำลังทำอะไรอยู่ สำหรับผลการดำเนินงานดูเหมือนว่าการโทรที่ไม่มีการจัดการที่จะmemcpy/ cpblkเป็นที่เร็วที่สุดสำหรับที่ - ดูcode4k.blogspot.nl/2010/10/...
atlaste

1
ฉันทำการทดสอบเกณฑ์มาตรฐานด้วยbyte[]. ไม่มีความแตกต่างในเวอร์ชันที่วางจำหน่าย บางครั้งบางครั้งArray.Copyก็Buffer.BlockCopyเร็วขึ้น (เล็กน้อย)
Bitterblue

คำตอบใหม่ที่ครอบคลุมโพสต์ด้านล่าง โปรดทราบว่าในกรณีที่มีขนาดบัฟเฟอร์ขนาดเล็กการคัดลอกแบบวนซ้ำอย่างชัดเจนมักจะดีที่สุด
Special Sauce

ฉันไม่คิดว่าพวกเขาจะทำสิ่งเดียวกันเสมอไป - คุณไม่สามารถใช้ Array ได้ Copy เพื่อคัดลอกอาร์เรย์ของ Int ไปยังอาร์เรย์ของไบต์เช่น
mcmillab

Array.Copyค่อนข้างเป็นเวอร์ชันพิเศษตัวอย่างเช่นสามารถคัดลอกได้เฉพาะอาร์เรย์อันดับเดียวกัน
astrowalker

คำตอบ:


59

เนื่องจากพารามิเตอร์Buffer.BlockCopyเป็นแบบไบต์แทนที่จะเป็นแบบดัชนีคุณจึงมีแนวโน้มที่จะทำให้โค้ดเสียหายมากกว่าที่คุณใช้Array.Copyดังนั้นฉันจะใช้เฉพาะBuffer.BlockCopyในส่วนที่มีความสำคัญต่อประสิทธิภาพของโค้ดของฉัน


9
เห็นด้วยอย่างยิ่ง มีช่องว่างมากเกินไปสำหรับข้อผิดพลาดกับ Buffer.BlockCopy ทำให้มันง่ายและอย่าพยายามบีบน้ำออกจากโปรแกรมของคุณจนกว่าคุณจะรู้ว่าน้ำผลไม้อยู่ที่ไหน (การทำโปรไฟล์)
Stephen

5
จะเกิดอะไรขึ้นถ้าคุณกำลังจัดการกับไบต์ []? มี gotcha อื่น ๆ กับ BlockCopy หรือไม่?
thecoop

4
@thecoop: ถ้าคุณกำลังจัดการกับไบต์ [] ก็คงใช้ได้ดีที่จะใช้ BlockCopy เว้นแต่ว่าคำจำกัดความของ "ไบต์" ในภายหลังจะเปลี่ยนเป็นอย่างอื่นที่ไม่ใช่ไบต์ซึ่งอาจส่งผลเสียในส่วนอื่น ๆ ของ รหัสของคุณต่อไป :) gotcha ที่มีศักยภาพอื่น ๆ เพียงอย่างเดียวคือ BlockCopy ทำเพียงไบต์ตรงดังนั้นจึงไม่คำนึงถึง endianness แต่สิ่งนี้จะเกิดขึ้นในเครื่องที่ไม่ใช่ Windows เท่านั้นและถ้าคุณทำโค้ดผิดพลาดเท่านั้น ที่หนึ่ง. นอกจากนี้อาจมีความแตกต่างแปลก ๆ หากคุณใช้โมโน
MusiGenesis

6
ในการทดสอบของฉันเอง Array.Copy () มีประสิทธิภาพใกล้เคียงกับ Buffer.BlockCopy () มาก Buffer.BlockCopy เร็วขึ้นอย่างต่อเนื่อง <10% สำหรับฉันเมื่อจัดการกับอาร์เรย์ไบต์องค์ประกอบ 640 (ซึ่งเป็นประเภทที่ฉันสนใจมากที่สุด) แต่คุณควรทำการทดสอบของคุณเองด้วยข้อมูลของคุณเองเนื่องจากอาจแตกต่างกันไปขึ้นอยู่กับข้อมูลประเภทข้อมูลขนาดอาร์เรย์และอื่น ๆ ฉันควรทราบว่าทั้งสองวิธีเร็วกว่าการใช้ Array.Clone () ประมาณ 3 เท่าและอาจเร็วกว่าการคัดลอกแบบวนซ้ำถึง 20 เท่า
Ken Smith

3
@KevinMiller: เอ่อUInt16คือสองไบต์ต่อองค์ประกอบ หากคุณส่งอาร์เรย์นี้ไปยัง BlockCopy พร้อมกับจำนวนองค์ประกอบในอาร์เรย์แน่นอนว่าจะคัดลอกอาร์เรย์เพียงครึ่งเดียว สำหรับการทำงานอย่างถูกต้องคุณจะต้องผ่านการจำนวนขององค์ประกอบครั้งขนาดของแต่ละองค์ประกอบ (2) เป็นพารามิเตอร์ยาว msdn.microsoft.com/en-us/library/…และค้นหาINT_SIZEในตัวอย่าง
MusiGenesis

129

โหมโรง

ฉันเข้าปาร์ตี้ช้า แต่มียอดดู 32,000 ครั้งจึงคุ้มค่าที่จะได้รับสิทธิ์นี้ โค้ด microbenchmarking ส่วนใหญ่ในคำตอบที่โพสต์นั้นประสบกับข้อบกพร่องทางเทคนิคที่รุนแรงอย่างน้อยหนึ่งข้อซึ่งรวมถึงการไม่ย้ายการจัดสรรหน่วยความจำออกจากลูปทดสอบ (ซึ่งแนะนำสิ่งประดิษฐ์ GC ที่รุนแรง) ไม่ทดสอบตัวแปรเทียบกับโฟลว์การดำเนินการที่กำหนด, การวอร์มอัพ JIT, และไม่ติดตามความแปรปรวนของการทดสอบภายใน นอกจากนี้คำตอบส่วนใหญ่ไม่ได้ทดสอบผลกระทบของขนาดบัฟเฟอร์ที่แตกต่างกันและประเภทดั้งเดิมที่แตกต่างกัน (เกี่ยวกับระบบ 32 บิตหรือ 64 บิต) เพื่อตอบคำถามนี้ให้ครอบคลุมมากขึ้นฉันได้เชื่อมต่อกับกรอบงานไมโครเบนช์มาร์กที่กำหนดเองที่ฉันพัฒนาขึ้นเพื่อลด "gotchas" ที่พบบ่อยที่สุดเท่าที่จะทำได้ การทดสอบรันในโหมดรีลีส. NET 4.0 ทั้งบนเครื่อง 32 บิตและเครื่อง 64 บิต ผลลัพธ์ถูกเฉลี่ยจากการทดสอบ 20 ครั้งซึ่งแต่ละครั้งมีการทดลอง 1 ล้านครั้งต่อวิธี ประเภทดั้งเดิมที่ทดสอบคือbyte(1 ไบต์), int(4 ไบต์) และdouble(8 ไบต์) มีการทดสอบสามวิธี: Array.Copy(), Buffer.BlockCopy()และการกำหนดต่อดัชนีอย่างง่ายในลูป ข้อมูลมีมากเกินไปที่จะโพสต์ที่นี่ดังนั้นฉันจะสรุปประเด็นสำคัญ

ประเด็นสำคัญ

  • หากความยาวบัฟเฟอร์ของคุณอยู่ที่ประมาณ 75-100 หรือน้อยกว่ารูทีนการคัดลอกลูปแบบชัดแจ้งมักจะเร็วกว่า (ประมาณ 5%) เมื่อเทียบกับประเภทดั้งเดิมทั้ง 3 ประเภทArray.Copy()หรือBuffer.BlockCopy()ทั้ง 3 ประเภทที่ทดสอบบนเครื่อง 32 บิตและ 64 บิต นอกจากนี้รูทีนการคัดลอกแบบลูปอย่างชัดเจนยังมีความแปรปรวนในประสิทธิภาพที่ลดลงอย่างเห็นได้ชัดเมื่อเทียบกับสองทางเลือกอื่น ประสิทธิภาพที่ดีนั้นเกือบจะแน่นอนเนื่องจากตำแหน่งของการอ้างอิงที่ใช้ประโยชน์จากการแคชหน่วยความจำ CPU L1 / L2 / L3 ร่วมกับไม่มีค่าใช้จ่ายในการเรียกเมธอด
    • สำหรับdoubleบัฟเฟอร์บนเครื่อง 32 บิตเท่านั้น :รูทีนการคัดลอกลูปแบบชัดแจ้งดีกว่าทั้งสองทางเลือกสำหรับขนาดบัฟเฟอร์ทั้งหมดที่ทดสอบสูงสุด 100k การปรับปรุงจะดีกว่าวิธีอื่น ๆ 3-5% นี่เป็นเพราะประสิทธิภาพของArray.Copy()และBuffer.BlockCopy()ลดลงโดยสิ้นเชิงเมื่อส่งผ่านความกว้าง 32 บิตดั้งเดิม ดังนั้นฉันจึงถือว่าเอฟเฟกต์เดียวกันนี้จะนำไปใช้กับlongบัฟเฟอร์เช่นกัน
  • สำหรับขนาดบัฟเฟอร์ที่เกิน ~ 100 การคัดลอกแบบลูปอย่างชัดเจนจะช้ากว่าอีก 2 วิธีอย่างรวดเร็ว (โดยมีข้อยกเว้นเพียงข้อเดียวที่ระบุไว้) ความแตกต่างที่เห็นได้ชัดเจนที่สุดคือโดยbyte[]ที่การคัดลอกแบบลูปอย่างชัดเจนอาจช้าลง 7 เท่าหรือมากกว่าในขนาดบัฟเฟอร์ขนาดใหญ่
  • โดยทั่วไปสำหรับทั้ง 3 ประเภทดั้งเดิมที่ทดสอบและในขนาดบัฟเฟอร์ทั้งหมดArray.Copy()และBuffer.BlockCopy()ดำเนินการเกือบจะเหมือนกัน โดยเฉลี่ยแล้วArray.Copy()ดูเหมือนว่าจะมีความได้เปรียบเล็กน้อยประมาณ 2% หรือน้อยกว่าเวลาที่ใช้ (แต่ปกติดีกว่า 0.2% - 0.5%) แม้ว่าจะBuffer.BlockCopy()เอาชนะได้ในบางครั้ง สำหรับเหตุผลที่ไม่รู้จักมีความแปรปรวนระหว่างการทดสอบอย่างเห็นได้ชัดที่สูงกว่าBuffer.BlockCopy() Array.Copy()ผลกระทบนี้ไม่สามารถกำจัดได้แม้ว่าฉันจะพยายามบรรเทาหลายครั้งและไม่มีทฤษฎีที่ใช้งานได้ว่าทำไม
  • เนื่องจากArray.Copy()เป็นวิธีที่ "ฉลาดกว่า" ทั่วไปกว่าและปลอดภัยกว่ามากนอกจากจะเร็วกว่าเล็กน้อยและมีความแปรปรวนน้อยกว่าโดยเฉลี่ยแล้วจึงควรเลือกใช้Buffer.BlockCopy()ในเกือบทุกกรณี กรณีการใช้งานเดียวที่Buffer.BlockCopy()จะดีกว่าอย่างมีนัยสำคัญคือเมื่อประเภทค่าอาร์เรย์ต้นทางและปลายทางแตกต่างกัน (ดังที่ระบุไว้ในคำตอบของ Ken Smith) ในขณะที่สถานการณ์นี้ไม่ได้เป็นเรื่องธรรมดาArray.Copy()สามารถดำเนินการได้ไม่ดีมากที่นี่เนื่องจากการอย่างต่อเนื่อง "ปลอดภัย" Buffer.BlockCopy()ประเภทค่าหล่อเมื่อเทียบกับการหล่อโดยตรงของ
  • หลักฐานเพิ่มเติมจาก StackOverflow ภายนอกที่Array.Copy()จะเร็วกว่าBuffer.BlockCopy()เดียวกันชนิดอาร์เรย์คัดลอกสามารถพบได้ที่นี่

เช่นกันก็ยังปรากฎว่ารอบระยะเวลาในอาร์เรย์ของ 100 คือเมื่อ .NET ที่Array.Clear()แรกเริ่มที่จะเอาชนะการหักบัญชีมอบหมายห่วงอย่างชัดเจนของอาร์เรย์ (การตั้งค่าfalse, 0หรือnull) สิ่งนี้สอดคล้องกับการค้นพบที่คล้ายกันของฉันข้างต้น การวัดประสิทธิภาพแยกต่างหากเหล่านี้ถูกค้นพบทางออนไลน์ที่นี่: manski.net/2012/12/net-array-clear-vs-arrayx-0-performance
Special Sauce

เมื่อคุณพูดขนาดบัฟเฟอร์ คุณหมายถึงไบต์หรือจำนวนองค์ประกอบ?
dmarra

ในคำตอบข้างต้นของฉันทั้ง "ความยาวบัฟเฟอร์" และ "ขนาดบัฟเฟอร์" โดยทั่วไปหมายถึงจำนวนองค์ประกอบ
Special Sauce

ฉันมีตัวอย่างที่ฉันต้องคัดลอกข้อมูลประมาณ 8 ไบต์บ่อยๆไปยังการอ่านบัฟเฟอร์จากซอร์สออฟเซ็ต 5 ไบต์ ฉันพบว่าสำเนาลูปแบบชัดแจ้งนั้นเร็วขึ้นอย่างมากจากนั้นใช้ Buffer.BlockCopy หรือ Array.Copy Loop Results for 1000000 iterations 17.9515ms. Buffer.BlockCopy Results for 1000000 iterations 39.8937ms. Array.Copy Results for 1000000 iterations 45.9059ms อย่างไรก็ตามหากขนาดสำเนา> ~ 20 ไบต์ Explicit loop จะช้าลงอย่างมาก
Tod Cunningham

@TodCunningham ข้อมูล 8 ไบต์? คุณหมายถึงยาวเทียบเท่า? แคสต์และคัดลอกองค์ประกอบเดี่ยว (ตายเร็ว) หรือเพียงแค่คลายการวนซ้ำด้วยตนเอง
astrowalker

67

อีกตัวอย่างหนึ่งของเวลาที่เหมาะสมในการใช้Buffer.BlockCopy()คือเมื่อคุณได้รับอาร์เรย์ของดั้งเดิม (เช่นกางเกงขาสั้น) และจำเป็นต้องแปลงเป็นอาร์เรย์ของไบต์ (เช่นสำหรับการส่งผ่านเครือข่าย) ฉันใช้วิธีนี้บ่อยเมื่อจัดการกับเสียงจาก Silverlight AudioSink มันมีตัวอย่างเป็นshort[]อาร์เรย์ แต่คุณจำเป็นต้องแปลงเป็นอาร์เรย์เมื่อคุณกำลังสร้างแพ็คเก็ตที่คุณส่งไปbyte[] Socket.SendAsync()คุณสามารถใช้BitConverterและวนซ้ำผ่านอาร์เรย์ทีละรายการ แต่เร็วกว่ามาก (ประมาณ 20x ในการทดสอบของฉัน) เพียงแค่ทำสิ่งนี้:

Buffer.BlockCopy(shortSamples, 0, packetBytes, 0, shortSamples.Length * sizeof(short)).  

และเคล็ดลับเดียวกันก็ทำงานในสิ่งที่ตรงกันข้ามเช่นกัน:

Buffer.BlockCopy(packetBytes, readPosition, shortSamples, 0, payloadLength);

สิ่งนี้ใกล้เคียงกับที่คุณได้รับใน C # ที่ปลอดภัยไปจนถึงการ(void *)จัดการหน่วยความจำที่พบได้ทั่วไปใน C และ C ++


6
นั่นเป็นความคิดที่ยอดเยี่ยม - คุณเคยประสบปัญหาเกี่ยวกับความอดทนหรือไม่?
Phillip

ใช่ฉันคิดว่าคุณอาจประสบปัญหานั้นขึ้นอยู่กับสถานการณ์ของคุณ สถานการณ์ของฉันเองมักจะเป็นอย่างใดอย่างหนึ่ง (ก) ฉันจำเป็นต้องสลับไปมาระหว่างไบต์อาร์เรย์และอาร์เรย์แบบสั้นในเครื่องเดียวกันหรือ (b) ฉันรู้ว่าฉันกำลังส่งข้อมูลไปยังเครื่องเดียวกัน endianness และฉันควบคุมด้านระยะไกล แต่ถ้าคุณใช้โปรโตคอลที่เครื่องระยะไกลคาดว่าข้อมูลจะถูกส่งตามลำดับเครือข่ายแทนที่จะเป็นคำสั่งโฮสต์ใช่วิธีนี้จะทำให้คุณมีปัญหา
Ken Smith

เคนยังมีบทความเกี่ยวกับ BlockCopy ในบล็อกของเขา: blog.wouldbetheologian.com/2011/11/…
Drew Noakes

4
โปรดทราบว่าตั้งแต่. Net Core 2.1 คุณสามารถทำได้โดยไม่ต้องคัดลอก MemoryMarshal.AsBytes<T>หรือMemoryMarshal.Cast<TFrom, TTo>ให้คุณตีความลำดับของดั้งเดิมหนึ่งเป็นลำดับของดั้งเดิมอื่น
Timo

16

จากการทดสอบของฉันประสิทธิภาพไม่ใช่เหตุผลที่จะชอบ Buffer.BlockCopy มากกว่า Array.Copy จากการทดสอบ Array.Copy ของฉันเร็วกว่า Buffer.BlockCopy

var buffer = File.ReadAllBytes(...);

var length = buffer.Length;
var copy = new byte[length];

var stopwatch = new Stopwatch();

TimeSpan blockCopyTotal = TimeSpan.Zero, arrayCopyTotal = TimeSpan.Zero;

const int times = 20;

for (int i = 0; i < times; ++i)
{
    stopwatch.Start();
    Buffer.BlockCopy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    blockCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();

    stopwatch.Start();
    Array.Copy(buffer, 0, copy, 0, length);
    stopwatch.Stop();

    arrayCopyTotal += stopwatch.Elapsed;

    stopwatch.Reset();
}

Console.WriteLine("bufferLength: {0}", length);
Console.WriteLine("BlockCopy: {0}", blockCopyTotal);
Console.WriteLine("ArrayCopy: {0}", arrayCopyTotal);
Console.WriteLine("BlockCopy (average): {0}", TimeSpan.FromMilliseconds(blockCopyTotal.TotalMilliseconds / times));
Console.WriteLine("ArrayCopy (average): {0}", TimeSpan.FromMilliseconds(arrayCopyTotal.TotalMilliseconds / times));

ตัวอย่างผลลัพธ์:

bufferLength: 396011520
BlockCopy: 00:00:02.0441855
ArrayCopy: 00:00:01.8876299
BlockCopy (average): 00:00:00.1020000
ArrayCopy (average): 00:00:00.0940000

1
ขออภัยที่คำตอบนี้เป็นความคิดเห็นมากกว่า แต่ยาวเกินไปสำหรับความคิดเห็น เนื่องจากฉันทามติดูเหมือนว่า Buffer.BlockCopy ดีกว่าสำหรับการแสดงฉันคิดว่าทุกคนควรทราบว่าฉันไม่สามารถยืนยันความเห็นพ้องกับการทดสอบได้
Kevin

10
ฉันคิดว่ามีปัญหากับวิธีการทดสอบของคุณ ความแตกต่างของเวลาส่วนใหญ่ที่คุณสังเกตเห็นเป็นผลมาจากการที่แอปพลิเคชันหมุนขึ้นแคชตัวเองเรียกใช้ JIT สิ่งนั้น ลองใช้บัฟเฟอร์ขนาดเล็ก แต่ไม่กี่พันครั้ง จากนั้นทำซ้ำการทดสอบทั้งหมดภายในลูปครึ่งโหลครั้งและสนใจเฉพาะการวิ่งครั้งสุดท้าย การทดสอบของฉันเองมี Buffer.BlockCopy () ทำงานอาจเร็วกว่า Array.Copy () ถึง 5% สำหรับอาร์เรย์ 640 ไบต์ เร็วขึ้นไม่มาก แต่น้อย
Ken Smith

2
ฉันวัดเหมือนกันสำหรับปัญหาผมจะได้เห็นไม่แตกต่างกันระหว่างประสิทธิภาพ Array.Copy () และ Buffer.BlockCopy () หากมีสิ่งใดBlockCopy นำเสนอความไม่ปลอดภัยซึ่งทำให้แอปของฉันเสียไปในกรณีเดียว
gatopeich

1
เช่นเดียวกับการเพิ่ม Array Copy รองรับตำแหน่งต้นทางที่ยาวดังนั้นการแบ่งออกเป็นอาร์เรย์ไบต์ขนาดใหญ่จะไม่ทำให้เกิดข้อยกเว้นนอกช่วง
Alxwest

2
จากการทดสอบที่ฉันเพิ่งทำไป ( bitbucket.org/breki74/tutis/commits/… ) ฉันจะบอกว่าไม่มีความแตกต่างของประสิทธิภาพในทางปฏิบัติระหว่างสองวิธีเมื่อคุณจัดการกับอาร์เรย์ไบต์
Igor Brejc

4

ArrayCopy ฉลาดกว่า BlockCopy จะหาวิธีคัดลอกองค์ประกอบหากต้นทางและปลายทางเป็นอาร์เรย์เดียวกัน

หากเราเติมอาร์เรย์ int ด้วย 0,1,2,3,4 และใช้:

Array.Copy (อาร์เรย์ 0 อาร์เรย์ 1 อาร์เรย์ความยาว - 1);

เราจบลงด้วย 0,0,1,2,3 ตามที่คาดไว้

ลองใช้ BlockCopy และเราจะได้รับ: 0,0,2,3,4 ถ้าฉันกำหนดarray[0]=-1หลังจากนั้นมันจะกลายเป็น -1,0,2,3,4 ตามที่คาดไว้ แต่ถ้าความยาวอาร์เรย์เท่ากันเช่น 6 เราจะได้ -1,256,2,3,4,5 สิ่งที่เป็นอันตราย อย่าใช้ BlockCopy นอกเหนือจากการคัดลอกอาร์เรย์หนึ่งไบต์ไปยังอาร์เรย์อื่น

มีอีกกรณีหนึ่งที่คุณสามารถใช้ได้เฉพาะ Array เท่านั้น Copy: ถ้าขนาดอาร์เรย์ยาวกว่า 2 ^ 31 Array.Copy มีโอเวอร์โหลดพร้อมlongพารามิเตอร์ขนาด BlockCopy ไม่มีสิ่งนั้น


2
ผลการทดสอบของคุณด้วย BlockCopy ไม่ใช่เรื่องที่คาดไม่ถึง เป็นเพราะ Block copy พยายามคัดลอกข้อมูลทีละชิ้นมากกว่าทีละไบต์ ในระบบ 32 บิตจะคัดลอกครั้งละ 4 ไบต์ในระบบ 64 บิตจะคัดลอกครั้งละ 8 ไบต์
Pharap

พฤติกรรมที่ไม่ได้กำหนดที่คาดไว้ดังนั้น
binki

2

ในการพิจารณาข้อโต้แย้งนี้หากไม่มีใครระมัดระวังว่าพวกเขาเขียนเกณฑ์มาตรฐานนี้อย่างไรพวกเขาอาจเข้าใจผิดได้ง่าย ฉันเขียนแบบทดสอบง่ายๆเพื่ออธิบายสิ่งนี้ ในการทดสอบของฉันด้านล่างหากฉันสลับลำดับการทดสอบของฉันระหว่างการเริ่ม Buffer.BlockCopy ก่อนหรือ Array การคัดลอกสิ่งที่ไปก่อนมักจะช้าที่สุด (แม้ว่าจะใกล้เคียงก็ตาม) ซึ่งหมายความว่าด้วยเหตุผลหลายประการที่ฉันจะไม่เข้าไปเพียงแค่เรียกใช้การทดสอบหลาย ๆ ครั้งโดยเฉพาะอย่างใดอย่างหนึ่งหลังจากที่อื่น ๆ จะไม่ให้ผลลัพธ์ที่ถูกต้อง

ฉันใช้วิธีรักษาการทดสอบเหมือนเดิม 1000000 ครั้งสำหรับอาร์เรย์ 1000000 คู่ตามลำดับ อย่างไรก็ตามในตอนนั้นฉันไม่สนใจ 900000 รอบแรกและเฉลี่ยส่วนที่เหลือ ในกรณีนั้นบัฟเฟอร์จะดีกว่า

private static void BenchmarkArrayCopies()
        {
            long[] bufferRes = new long[1000000];
            long[] arrayCopyRes = new long[1000000];
            long[] manualCopyRes = new long[1000000];

            double[] src = Enumerable.Range(0, 1000000).Select(x => (double)x).ToArray();

            for (int i = 0; i < 1000000; i++)
            {
                bufferRes[i] = ArrayCopyTests.ArrayBufferBlockCopy(src).Ticks;
            }

            for (int i = 0; i < 1000000; i++)
            {
                arrayCopyRes[i] = ArrayCopyTests.ArrayCopy(src).Ticks;
            }

            for (int i = 0; i < 1000000; i++)
            {
                manualCopyRes[i] = ArrayCopyTests.ArrayManualCopy(src).Ticks;
            }

            Console.WriteLine("Loop Copy: {0}", manualCopyRes.Average());
            Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Average());
            Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Average());

            //more accurate results - average last 1000

            Console.WriteLine();
            Console.WriteLine("----More accurate comparisons----");

            Console.WriteLine("Loop Copy: {0}", manualCopyRes.Where((l, i) => i > 900000).ToList().Average());
            Console.WriteLine("Array.Copy Copy: {0}", arrayCopyRes.Where((l, i) => i > 900000).ToList().Average());
            Console.WriteLine("Buffer.BlockCopy Copy: {0}", bufferRes.Where((l, i) => i > 900000).ToList().Average());
            Console.ReadLine();
        }

public class ArrayCopyTests
    {
        private const int byteSize = sizeof(double);

        public static TimeSpan ArrayBufferBlockCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            Buffer.BlockCopy(original, 0 * byteSize, copy, 0 * byteSize, original.Length * byteSize);
            watch.Stop();
            return watch.Elapsed;
        }

        public static TimeSpan ArrayCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            Array.Copy(original, 0, copy, 0, original.Length);
            watch.Stop();
            return watch.Elapsed;
        }

        public static TimeSpan ArrayManualCopy(double[] original)
        {
            Stopwatch watch = new Stopwatch();
            double[] copy = new double[original.Length];
            watch.Start();
            for (int i = 0; i < original.Length; i++)
            {
                copy[i] = original[i];
            }
            watch.Stop();
            return watch.Elapsed;
        }
    }

https://github.com/chivandikwa/Random-Benchmarks


5
ฉันไม่เห็นผลการจับเวลาในคำตอบของคุณ โปรดรวมเอาท์พุทคอนโซล
ToolmakerSteve

0

แค่ต้องการเพิ่มกรณีการทดสอบของฉันซึ่งแสดงอีกครั้งว่า BlockCopy ไม่มีประโยชน์ 'PERFORMANCE' เหนือ Array.Copy ดูเหมือนว่าจะมีประสิทธิภาพเหมือนกันภายใต้โหมดรีลีสบนเครื่องของฉัน (ทั้งคู่ใช้เวลาประมาณ 66ms ในการคัดลอกจำนวนเต็ม 50 ล้านตัว) ภายใต้โหมดดีบัก BlockCopy จะเร็วขึ้นเพียงเล็กน้อย

    private static T[] CopyArray<T>(T[] a) where T:struct 
    {
        T[] res = new T[a.Length];
        int size = Marshal.SizeOf(typeof(T));
        DateTime time1 = DateTime.Now;
        Buffer.BlockCopy(a,0,res,0, size*a.Length);
        Console.WriteLine("Using Buffer blockcopy: {0}", (DateTime.Now - time1).Milliseconds);
        return res;
    }

    static void Main(string[] args)
    {
        int simulation_number = 50000000;
        int[] testarray1 = new int[simulation_number];

        int begin = 0;
        Random r = new Random();
        while (begin != simulation_number)
        {
            testarray1[begin++] = r.Next(0, 10000);
        }

        var copiedarray = CopyArray(testarray1);

        var testarray2 = new int[testarray1.Length];
        DateTime time2 = DateTime.Now;
        Array.Copy(testarray1, testarray2, testarray1.Length);
        Console.WriteLine("Using Array.Copy(): {0}", (DateTime.Now - time2).Milliseconds);
    }

3
ไม่มีความผิด แต่ผลการทดสอบของคุณไม่เป็นประโยชน์จริงๆ) ประการแรก "เร็วกว่า 20ms" จะบอกอะไรคุณโดยไม่รู้เวลาโดยรวม คุณยังทำการทดสอบทั้งสองนี้ในลักษณะที่แตกต่างกันมาก กรณี BlockCopy มีการเรียกใช้เมธอดเพิ่มเติมและการจัดสรรอาร์เรย์เป้าหมายของคุณซึ่งคุณไม่มีใน Array.Copy case เนื่องจากความผันผวนของมัลติเธรด (สวิตช์งานที่เป็นไปได้สวิตช์หลัก) คุณจึงได้ผลลัพธ์ที่แตกต่างกันทุกครั้งที่ดำเนินการทดสอบ
Bunny83

@ Bunny83 ขอบคุณสำหรับความคิดเห็น ฉันได้ปรับเปลี่ยนตำแหน่งตัวจับเวลาเล็กน้อยซึ่งควรให้การเปรียบเทียบที่ยุติธรรมกว่าในตอนนี้ และฉันรู้สึกประหลาดใจเล็กน้อยที่ blockcopy ไม่ได้เร็วกว่า array.copy เลย
stt106
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.