มีผลกระทบต่อประสิทธิภาพเมื่อโทร ToList () หรือไม่?


139

เมื่อใช้ToList()จะมีผลกระทบต่อประสิทธิภาพที่ต้องพิจารณาหรือไม่

ฉันกำลังเขียนแบบสอบถามเพื่อดึงไฟล์จากไดเรกทอรีซึ่งเป็นแบบสอบถาม:

string[] imageArray = Directory.GetFiles(directory);

อย่างไรก็ตามเนื่องจากฉันชอบทำงานด้วยList<>แทนฉันจึงตัดสินใจที่จะใส่ ...

List<string> imageList = Directory.GetFiles(directory).ToList();

ดังนั้นจะมีผลกระทบต่อประสิทธิภาพการทำงานบางอย่างที่ควรพิจารณาเมื่อตัดสินใจทำการแปลงเช่นนี้หรือควรพิจารณาเมื่อจัดการกับไฟล์จำนวนมากเท่านั้น นี่เป็นการแปลงที่เล็กน้อยหรือไม่?


+1 สนใจที่จะรู้คำตอบที่นี่เช่นกัน IMHO เว้นแต่ app เป็นผลการดำเนินงานที่สำคัญผมคิดว่าผมมักจะใช้List<T>ในความโปรดปรานของT[]ถ้ามันทำให้รหัสตรรกะมากขึ้น / อ่าน / การบำรุงรักษา (เว้นแต่ของหลักสูตรการแปลงถูกก่อให้เกิดความเห็นได้ชัดเจนปัญหาประสิทธิภาพการทำงานในกรณีที่ฉันอีกครั้ง เยี่ยมชมฉันเดา)
Sepster

การสร้างรายการจากอาร์เรย์ควรจะถูกสุด ๆ
leppie

2
@Sepster ฉันระบุเฉพาะชนิดข้อมูลตามที่ฉันต้องการเพื่อทำงาน ถ้าฉันไม่ต้องโทรAddหรือRemoveฉันจะปล่อยให้มันเป็นIEnumerable<T>(หรือดีกว่าvar)
pswg

4
ฉันคิดว่าในกรณีนี้จะดีกว่าที่จะเรียกEnumerateFilesแทนGetFilesดังนั้นจะมีเพียงหนึ่งอาร์เรย์จะถูกสร้างขึ้น
tukaef

3
GetFiles(directory)ตามที่ได้มีการดำเนินการในขณะนี้ .NET new List<string>(EnumerateFiles(directory)).ToArray()สวยเท่าไหร่ ดังนั้นGetFiles(directory).ToList()สร้างรายการสร้างอาร์เรย์จากนั้นสร้างรายการอีกครั้ง เช่นเดียวกับ 2kay พูดว่าคุณควรเลือกที่จะทำEnumerateFiles(directory).ToList()ที่นี่
Joren

คำตอบ:


178

IEnumerable.ToList()

ใช่IEnumerable<T>.ToList()มีผลกระทบต่อประสิทธิภาพการทำงานเป็นการดำเนินการO (n)แม้ว่าจะต้องได้รับความสนใจในการดำเนินการที่สำคัญด้านประสิทธิภาพเท่านั้น

การToList()ดำเนินการจะใช้ตัวList(IEnumerable<T> collection)สร้าง คอนสตรัคนี้จะต้องทำสำเนาของอาเรย์ (โดยทั่วไปIEnumerable<T>) มิฉะนั้นการดัดแปลงในอนาคตของอาเรย์ดั้งเดิมจะเปลี่ยนแปลงในแหล่งที่มาT[]ซึ่งไม่เป็นที่ต้องการโดยทั่วไป

ฉันอยากจะย้ำนี้จะสร้างความแตกต่างกับรายการใหญ่คัดลอกชิ้นของหน่วยความจำค่อนข้างดำเนินการอย่างรวดเร็วเพื่อดำเนินการ

ทิปที่มีประโยชน์AsเทียบกับTo

คุณจะสังเกตเห็นใน LINQ มีหลายวิธีที่เริ่มต้นด้วยAs(เช่นAsEnumerable()) และTo(เช่นToList()) วิธีการที่เริ่มต้นด้วยToต้องมีการแปลงเช่นด้านบน (เช่น. อาจส่งผลกระทบต่อประสิทธิภาพการทำงาน) และวิธีการที่เริ่มต้นด้วยAsไม่และจะต้องดำเนินการ cast หรือง่าย

รายละเอียดเพิ่มเติมเกี่ยวกับ List<T>

นี่คือรายละเอียดเพิ่มเติมเล็กน้อยเกี่ยวกับวิธีการList<T>ทำงานในกรณีที่คุณสนใจ :)

A List<T>ยังใช้โครงสร้างที่เรียกว่าอาร์เรย์แบบไดนามิกซึ่งจำเป็นต้องปรับขนาดตามความต้องการเหตุการณ์การปรับขนาดนี้จะคัดลอกเนื้อหาของอาร์เรย์เก่าไปยังอาร์เรย์ใหม่ ดังนั้นจึงเริ่มออกขนาดเล็กและการเพิ่มขึ้นของขนาดถ้าจำเป็นต้องใช้

นี่คือความแตกต่างระหว่างCapacityและแอตทริบิวต์บนCount หมายถึงขนาดของอาร์เรย์ที่อยู่เบื้องหลังฉากที่เป็นจำนวนของรายการในที่ซึ่งอยู่เสมอ ดังนั้นเมื่อมีการเพิ่มรายการลงในรายการการเพิ่มรายการที่ผ่านมาขนาดของรายการจะเพิ่มเป็นสองเท่าและอาร์เรย์จะถูกคัดลอกList<T>CapacityCountList<T><= CapacityCapacityList<T>


2
ฉันแค่อยากจะเน้นว่าคอนList(IEnumerable<T> collection)สตรัคเตอร์ตรวจสอบว่าพารามิเตอร์การเก็บรวบรวมเป็นICollection<T>แล้วสร้างอาร์เรย์ภายในใหม่ที่มีขนาดที่ต้องการได้ทันที หากการรวบรวมพารามิเตอร์ไม่ได้ICollection<T>นวกรรมิกจะวนซ้ำมันและเรียกใช้Addสำหรับแต่ละองค์ประกอบ
Justinas Simanavicius

เป็นสิ่งสำคัญที่จะต้องทราบว่าคุณมักจะเห็น ToList () เป็นการดำเนินการที่เรียกร้องที่ทำให้เข้าใจผิด สิ่งนี้จะเกิดขึ้นเมื่อคุณสร้าง IEnumerable <> througha LINQ query แบบสอบถาม linq ถูกสร้างขึ้น แต่ไม่ได้ดำเนินการ การเรียก ToList () จะเรียกใช้การสืบค้นดังนั้นจึงดูเหมือนว่าจะต้องใช้ทรัพยากรมาก แต่เป็นแบบสอบถามที่ใช้งานได้อย่างเข้มข้นและไม่ใช่การดำเนินการ ToList () เว้นแต่จะเป็นรายการใหญ่มาก)
dancer42

36

มีผลกระทบต่อประสิทธิภาพเมื่อโทรไปที่รายการ ()?

ใช่แน่นอน ในทางทฤษฎีแม้จะi++มีผลกระทบต่อประสิทธิภาพการทำงานมันทำให้โปรแกรมช้าลงเล็กน้อย

อะไร.ToListทำอย่างไร

เมื่อคุณเรียกใช้.ToList, สายรหัสซึ่งเป็นวิธีขยายว่าEnumerable.ToList() return new List<TSource>(source)ใน Constructor ที่สอดคล้องกันภายใต้สถานการณ์ที่เลวร้ายที่สุดมันจะผ่านคอนเทนเนอร์รายการและเพิ่มทีละรายการในคอนเทนเนอร์ใหม่ ดังนั้นพฤติกรรมของมันจึงมีผลกับประสิทธิภาพเพียงเล็กน้อย เป็นไปไม่ได้ที่จะเป็นคอขวดประสิทธิภาพในใบสมัครของคุณ

มีอะไรผิดปกติกับรหัสในคำถาม

Directory.GetFilesผ่านโฟลเดอร์และส่งคืนชื่อไฟล์ทั้งหมดลงในหน่วยความจำทันทีอาจมีความเสี่ยงที่สตริง [] จะต้องใช้หน่วยความจำจำนวนมากทำให้ทุกอย่างช้าลง

สิ่งที่ควรทำในตอนนั้น

มันขึ้นอยู่กับ. หากคุณ (รวมถึงตรรกะทางธุรกิจของคุณ) รับรองว่าจำนวนไฟล์ในโฟลเดอร์นั้นเล็กเสมอรหัสนั้นเป็นที่ยอมรับ แต่ก็ยังแนะนำให้ใช้รุ่นขี้เกียจ: Directory.EnumerateFilesใน C # 4 นี่เป็นเหมือนแบบสอบถามซึ่งจะไม่ถูกดำเนินการทันทีคุณสามารถเพิ่มแบบสอบถามเพิ่มเติมได้เช่น:

Directory.EnumerateFiles(myPath).Any(s => s.Contains("myfile"))

ซึ่งจะหยุดการค้นหาพา ธ ทันทีที่พบไฟล์ที่มีชื่อ "myfile" .GetFilesนี้จะเห็นได้ชัดมีประสิทธิภาพที่ดีขึ้นแล้ว


19

มีผลกระทบต่อประสิทธิภาพเมื่อโทรไปที่รายการ ()?

ใช่แล้ว การใช้วิธีการต่อขยายEnumerable.ToList()จะสร้างList<T>วัตถุใหม่จากIEnumerable<T>แหล่งรวบรวมซึ่งแน่นอนว่ามีผลกระทบต่อประสิทธิภาพ

อย่างไรก็ตามความเข้าใจList<T>อาจช่วยให้คุณทราบว่าผลกระทบต่อประสิทธิภาพนั้นมีความสำคัญหรือไม่

List<T>ใช้อาร์เรย์ ( T[]) เพื่อจัดเก็บองค์ประกอบของรายการ ไม่สามารถขยายอาร์เรย์ได้เมื่อถูกจัดสรรดังนั้นList<T>จะใช้อาร์เรย์ที่มีขนาดใหญ่กว่าเพื่อเก็บองค์ประกอบของรายการ เมื่อการList<T>เติบโตเกินขนาดอาร์เรย์ต้นแบบจะต้องมีการจัดสรรอาร์เรย์ใหม่และเนื้อหาของอาร์เรย์เก่าจะต้องถูกคัดลอกไปยังอาร์เรย์ที่มีขนาดใหญ่กว่าใหม่ก่อนที่รายการจะโตขึ้น

เมื่อสร้างใหม่List<T>จากIEnumerable<T>มีสองกรณี:

  1. ดำเนินการเก็บรวบรวมแหล่งที่มาICollection<T>: แล้วถูกนำมาใช้เพื่อให้ได้ขนาดที่แน่นอนของการเก็บรวบรวมแหล่งที่มาและอาร์เรย์ที่ตรงกับการสนับสนุนการจัดสรรก่อนที่ทุกองค์ประกอบของคอลเลกชันของแหล่งที่มาจะถูกคัดลอกไปยังอาร์เรย์สนับสนุนโดยใช้ICollection<T>.Count ICollection<T>.CopyTo()การดำเนินการนี้ค่อนข้างมีประสิทธิภาพและอาจจะแมปกับคำสั่ง CPU บางอย่างสำหรับการคัดลอกบล็อกหน่วยความจำ อย่างไรก็ตามในแง่ของหน่วยความจำประสิทธิภาพที่จำเป็นสำหรับอาร์เรย์ใหม่และรอบ CPU ที่จำเป็นสำหรับการคัดลอกองค์ประกอบทั้งหมด

  2. มิฉะนั้นขนาดของคอลเลกชันแหล่งที่มาไม่เป็นที่รู้จักและตัวแจงนับของIEnumerable<T>จะใช้เพื่อเพิ่มองค์ประกอบของแหล่งที่มาทีละรายการในครั้งList<T>เดียว เริ่มแรกอาร์เรย์สำรองนั้นว่างเปล่าและอาร์เรย์ขนาด 4 ถูกสร้างขึ้น จากนั้นเมื่ออาเรย์นี้มีขนาดเล็กเกินไปขนาดจะเพิ่มขึ้นเป็นสองเท่าดังนั้นอาเรย์สำรองจึงเติบโตเช่นนี้ 4, 8, 16, 32 เป็นต้นทุกครั้งที่อาเรย์สำรองเพิ่มขึ้นจะต้องทำการจัดสรรใหม่และองค์ประกอบทั้งหมดที่เก็บไว้จะต้องคัดลอก การดำเนินการนี้มีค่าใช้จ่ายมากขึ้นเมื่อเทียบกับกรณีแรกที่สามารถสร้างอาร์เรย์ขนาดที่ถูกต้องได้ทันที

    นอกจากนี้หากคอลเลกชันแหล่งที่มาของคุณมีองค์ประกอบ 33 รายการรายการนั้นจะจบลงด้วยการใช้อาร์เรย์ 64 องค์ประกอบที่สิ้นเปลืองหน่วยความจำบางส่วน

ในกรณีของคุณการรวบรวมซอร์สคืออาร์เรย์ที่ใช้งานICollection<T>ดังนั้นผลกระทบต่อประสิทธิภาพไม่ใช่สิ่งที่คุณควรคำนึงถึงเว้นแต่ว่าอาเรย์ต้นทางของคุณมีขนาดใหญ่มาก การโทรToList()จะคัดลอกอาเรย์ของแหล่งที่มาและห่อไว้ในList<T>วัตถุ แม้แต่ประสิทธิภาพของเคสที่สองก็ไม่ใช่เรื่องที่น่ากังวลสำหรับคอลเล็กชั่นเล็ก ๆ


5

"มีผลกระทบด้านประสิทธิภาพที่ต้องพิจารณาหรือไม่"

ปัญหาเกี่ยวกับสถานการณ์ที่แน่นอนของคุณคือสิ่งแรกที่สำคัญที่สุดที่คุณกังวลเกี่ยวกับประสิทธิภาพมาจากความเร็วฮาร์ดไดรฟ์และประสิทธิภาพของแคชของไดรฟ์

จากมุมมองนั้นผลกระทบย่อมมีน้อยจนถึงจุดที่ไม่จำเป็นต้องพิจารณา

แต่ถ้าคุณต้องการคุณสมบัติของList<>โครงสร้างจริงๆอาจทำให้คุณมีประสิทธิภาพมากขึ้นหรืออัลกอริทึมของคุณเป็นมิตรมากขึ้นหรือได้เปรียบอื่น ๆ ไม่เช่นนั้นคุณเพียงแค่เพิ่มประสิทธิภาพการทำงานที่ไม่มีนัยสำคัญโดยไม่มีเหตุผลก็ตาม ในกรณีนี้โดยธรรมชาติคุณไม่ควรทำ! :)


4

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

โดยทั่วไปคุณไม่ควรใช้ ToList () เว้นแต่งานที่คุณทำไม่สามารถทำได้โดยไม่แปลงเป็นรายการ ตัวอย่างเช่นถ้าคุณต้องการวนซ้ำในคอลเลกชันคุณไม่จำเป็นต้องทำ ToList

หากคุณกำลังทำแบบสอบถามกับแหล่งข้อมูลตัวอย่างเช่นฐานข้อมูลที่ใช้ LINQ กับ SQL ค่าใช้จ่ายในการทำ ToList นั้นมีมากขึ้นเพราะเมื่อคุณใช้ ToList กับ LINQ ไปยัง SQL แทนที่จะทำการ Delayed Execution เช่นรายการโหลดเมื่อจำเป็น (ซึ่งอาจเป็นประโยชน์ ในหลาย ๆ สถานการณ์) มันจะโหลดไอเท็มจากฐานข้อมูลลงในหน่วยความจำทันที


Haris: สิ่งที่ฉันไม่แน่ใจเกี่ยวกับแหล่งต้นฉบับจะเกิดอะไรขึ้นกับแหล่งข้อมูลต้นฉบับหลังจากเรียกไปที่ ToList ()
TalentTuner

@Saurabh GC จะทำความสะอาด
pswg

@Saurabh ไม่มีอะไรจะเกิดขึ้นกับแหล่งต้นฉบับ องค์ประกอบของแหล่งต้นฉบับจะถูกอ้างอิงโดยรายการที่สร้างขึ้นใหม่
Haris Hasan

"ถ้าคุณต้องการทำซ้ำในคอลเลกชันคุณไม่จำเป็นต้องใช้ ToList" - แล้วคุณควรทำซ้ำอย่างไร?
SharpC

4

มันจะมีประสิทธิภาพเท่ากับ (ทำ):

var list = new List<T>(items);

หากคุณแยกรหัสแหล่งที่มาของตัวสร้างที่ใช้IEnumerable<T>คุณจะเห็นมันจะทำบางสิ่ง:

  • เรียกcollection.Countดังนั้นถ้าcollectionเป็นIEnumerable<T>มันจะบังคับให้ดำเนินการ ถ้าcollectionเป็นอาร์เรย์รายการอื่น ๆ O(1)ที่ควรจะเป็น

  • หากcollectionดำเนินการICollection<T>แล้วจะบันทึกรายการในอาร์เรย์ภายในโดยใช้ICollection<T>.CopyToวิธีการ มันควรจะO(n)เป็นnความยาวของคอลเลกชัน

  • หากcollectionไม่ได้ใช้ICollection<T>มันจะวนซ้ำรายการของคอลเลกชันและจะเพิ่มลงในรายการภายใน

ดังนั้นใช่มันจะใช้หน่วยความจำมากขึ้นเนื่องจากจะต้องสร้างรายการใหม่และในกรณีที่เลวร้ายที่สุดมันจะเป็นO(n)เพราะมันจะวนซ้ำผ่านcollectionเพื่อทำสำเนาของแต่ละองค์ประกอบ


3
ปิด0(n)ซึ่งnเป็นผลรวมของไบต์สตริงในคอลเลกชันดั้งเดิมครอบครองไม่นับองค์ประกอบ (จะต้องแน่นอนมากขึ้น n = ไบต์ / ขนาดคำที่แน่นอน)
1416420

@ user1416420 ฉันอาจจะผิด แต่ทำไมถึงเป็นอย่างนั้น? เกิดอะไรขึ้นถ้ามันเป็นชุดของประเภทอื่น ๆ (เช่น. bool, intฯลฯ )? คุณไม่จำเป็นต้องทำสำเนาของแต่ละสายในคอลเลกชัน คุณเพียงแค่เพิ่มพวกเขาในรายการใหม่
Oscar Mederos

ยังคงไม่สำคัญว่าการจัดสรรหน่วยความจำใหม่ & การคัดลอกไบต์คือสิ่งที่กำลังฆ่าวิธีนี้ บูลจะครอบครอง 4 ไบต์ใน. NET จริงๆแล้วการอ้างอิงวัตถุใน. NET แต่ละครั้งมีความยาวอย่างน้อย 8 ไบต์ดังนั้นมันจึงค่อนข้างช้า 4 ไบต์แรกชี้ไปที่ตารางประเภทและ 4 ไบต์ที่สองชี้ไปที่ค่าหรือตำแหน่งหน่วยความจำที่จะหาค่า
user1416420

3

เมื่อพิจารณาจากผลการดำเนินงานของการเรียกรายชื่อแฟ้มToList()เป็นเล็กน้อย แต่ไม่ใช่สำหรับสถานการณ์อื่น ๆ ขึ้นอยู่กับว่าคุณใช้งานที่ไหน

  • เมื่อเรียกร้องให้อาร์เรย์รายการหรือคอลเลกชันอื่น ๆ List<T>ที่คุณสร้างสำเนาของคอลเลกชันเป็นที่ ประสิทธิภาพที่นี่ขึ้นอยู่กับขนาดของรายการ คุณควรทำเมื่อจำเป็นจริงๆ

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

  • เมื่อโทรบน IEnumerable<T>คุณเป็นตัวเป็นตนIEnumerable<T> (ปกติแบบสอบถาม)


2

ToList จะสร้างรายการใหม่และคัดลอกองค์ประกอบจากแหล่งต้นฉบับไปยังรายการที่สร้างขึ้นใหม่ดังนั้นสิ่งเดียวคือการคัดลอกองค์ประกอบจากแหล่งต้นฉบับและขึ้นอยู่กับขนาดของแหล่งที่มา

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