ถาม: ทำไมฉันถึงเลือกคำตอบนี้
- เลือกคำตอบนี้หากคุณต้องการให้ NET ความเร็วสูงสุดมีความสามารถ
- ไม่ต้องสนใจคำตอบนี้หากคุณต้องการวิธีการโคลนนิ่งที่ง่ายและสะดวกจริงๆ
ในคำอื่น ๆไปกับคำตอบอื่นจนกว่าคุณจะมีคอขวดประสิทธิภาพการทำงานที่ตอบสนองความต้องการการแก้ไขและคุณสามารถพิสูจน์ได้ด้วย Profiler
เร็วกว่าวิธีอื่น 10 เท่า
วิธีการโคลนแบบลึกต่อไปนี้คือ:
- เร็วกว่าสิ่งใด ๆ ที่เกี่ยวข้องกับซีเรียลไลซ์เซชั่น /
- ค่อนข้างใกล้เคียงกับความเร็วสูงสุดตามทฤษฎี. NET ที่มีความสามารถ
และวิธีการ ...
สำหรับความเร็วสูงสุดคุณสามารถใช้Nested MemberwiseClone เพื่อทำสำเนาลึกที่จะทำสำเนาลึก มันเกือบจะเร็วเหมือนการคัดลอก struct ตามตัวอักษรและเร็วกว่า (a) reflection หรือ (b) การทำให้เป็นอนุกรม (ดังที่อธิบายไว้ในคำตอบอื่น ๆ ในหน้านี้)
โปรดทราบว่าถ้าคุณใช้Nested MemberwiseClone สำหรับสำเนาลึกคุณต้องใช้ ShallowCopy สำหรับแต่ละระดับที่ซ้อนกันในคลาสและ DeepCopy ซึ่งเรียกเมธอด ShallowCopy ทั้งหมดเพื่อสร้างโคลนที่สมบูรณ์ ง่ายมากเพียงไม่กี่บรรทัดดูรหัสสาธิตด้านล่าง
นี่คือผลลัพธ์ของโค้ดที่แสดงความแตกต่างของประสิทธิภาพสัมพัทธ์สำหรับ 100,000 โคลนนิ่ง:
- 1.08 วินาทีสำหรับ Nested MemberwiseClone บนโครงสร้างที่ซ้อนกัน
- 4.77 วินาทีสำหรับ Nested MemberwiseClone ในคลาสที่ซ้อนกัน
- 39.93 วินาทีสำหรับซีเรียลไลเซชั่น / ดีซีเรียลไลเซชัน
การใช้ Nested MemberwiseClone ในคลาสเกือบจะเร็วเท่ากับการคัดลอก struct และการคัดลอก struct นั้นค่อนข้างใกล้เคียงกับความเร็วสูงสุดตามทฤษฎีที่มีความสามารถ
Demo 1 of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo 2 of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo 3 of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
เพื่อให้เข้าใจวิธีการทำสำเนาอย่างลึกโดยใช้ MemberwiseCopy ต่อไปนี้เป็นโครงการตัวอย่างที่ใช้ในการสร้างเวลาด้านบน:
// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}
จากนั้นเรียกใช้การสาธิตจาก main:
void MyMain(string[] args)
{
{
Console.Write("Demo 1 of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 2 of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n\n", sw.Elapsed, total);
}
{
Console.Write("Demo 3 of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
อีกครั้งโปรดทราบว่าถ้าคุณใช้Nested MemberwiseClone สำหรับสำเนาลึกคุณต้องใช้ ShallowCopy สำหรับแต่ละระดับที่ซ้อนกันในชั้นเรียนด้วยตนเองและ DeepCopy ซึ่งเรียกเมธอด ShallowCopy ทั้งหมดเพื่อสร้างโคลนที่สมบูรณ์ ง่ายมาก: มีเพียงไม่กี่บรรทัดเท่านั้นดูรหัสสาธิตด้านบน
ประเภทค่าเทียบกับประเภทการอ้างอิง
โปรดทราบว่าเมื่อมันมาถึงการโคลนวัตถุมีความแตกต่างใหญ่ระหว่าง " struct " และ " คลาส ":
- หากคุณมี " struct " เป็นประเภทค่าเพื่อให้คุณสามารถคัดลอกและเนื้อหาจะถูกโคลน (แต่จะทำการโคลนแบบตื้นเท่านั้นเว้นแต่คุณจะใช้เทคนิคในบทความนี้)
- หากคุณมี " คลาส " เป็นประเภทการอ้างอิงดังนั้นหากคุณคัดลอกสิ่งที่คุณทำคือการคัดลอกตัวชี้ไปยังมัน ในการสร้างโคลนที่แท้จริงคุณจะต้องมีความคิดสร้างสรรค์มากขึ้นและใช้ความแตกต่างระหว่างชนิดของค่าและประเภทการอ้างอิงที่สร้างสำเนาต้นฉบับของวัตถุในหน่วยความจำอีกชุด
ดูความแตกต่างระหว่างค่าชนิดและประเภทการอ้างอิง
ตรวจสอบผลรวมเพื่อช่วยในการดีบัก
- การโคลนวัตถุอย่างไม่ถูกต้องอาจทำให้เกิดบั๊กที่ยากต่อการตรึง ในรหัสการผลิตฉันมักจะใช้การตรวจสอบเพื่อตรวจสอบอีกครั้งว่าวัตถุได้รับการโคลนอย่างถูกต้องและไม่ได้รับความเสียหายจากการอ้างอิงอื่นไป การตรวจสอบนี้สามารถปิดได้ในโหมด Release
- ฉันพบว่าวิธีนี้มีประโยชน์มาก: บ่อยครั้งที่คุณต้องการโคลนส่วนของวัตถุเท่านั้นไม่ใช่ทุกอย่าง
มีประโยชน์จริง ๆ สำหรับ decoupling หลายเธรดจากเธรดอื่น ๆ
กรณีใช้งานที่ยอดเยี่ยมอย่างหนึ่งสำหรับรหัสนี้คือการป้อนโคลนของคลาสที่ซ้อนกันหรือโครงสร้างลงในคิวเพื่อใช้รูปแบบผู้ผลิต / ผู้บริโภค
- เราสามารถมีหนึ่ง (หรือมากกว่า)
ConcurrentQueue
หัวข้อการปรับเปลี่ยนระดับที่พวกเขาเป็นเจ้าของแล้วผลักดันสำเนาสมบูรณ์ของชั้นนี้เป็น
- จากนั้นเรามีเธรดหนึ่ง (หรือมากกว่า) ดึงสำเนาของคลาสเหล่านี้ออกมาและจัดการกับพวกเขา
วิธีนี้ใช้งานได้ดีมากในทางปฏิบัติและช่วยให้เราแยกเธรดจำนวนมาก (ผู้ผลิต) จากหนึ่งเธรดขึ้นไป (ผู้บริโภค)
และวิธีการนี้ก็รวดเร็วเช่นกัน: ถ้าเราใช้โครงสร้างซ้อนกันมันเร็วกว่าคลาสอนุกรม / deserializing 35x และทำให้เราสามารถใช้ประโยชน์จากเธรดทั้งหมดที่มีในเครื่อง
ปรับปรุง
เห็นได้ชัดว่า ExpressMapper นั้นเร็วหากไม่เร็วกว่าการเขียนด้วยมือเช่นด้านบน ฉันอาจต้องดูว่าพวกเขาเปรียบเทียบกับ profiler อย่างไร