linq มีประสิทธิภาพมากกว่าที่ปรากฏบนพื้นผิวหรือไม่?


13

ถ้าฉันเขียนอะไรแบบนี้:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)

นี่เป็นเช่นเดียวกับ:

var results1 = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue)
        results1.Add(t);

var results2 = new List<Thing>();
foreach(var t in results1)
    if(t.IsSomeOtherValue)
        results2.Add(t);

หรือมีเวทมนตร์ใต้ฝาครอบที่ใช้งานได้มากกว่านี้:

var results = new List<Thing>();
foreach(var t in mythings)
    if(t.IsSomeValue && t.IsSomeOtherValue)
        results.Add(t);

หรือเป็นสิ่งที่แตกต่างอย่างสิ้นเชิงโดยสิ้นเชิง?


4
คุณสามารถดูได้ใน ILSpy
ChaosPandion

1
มันเป็นเหมือนตัวอย่างที่สองมากกว่าคำตอบแรกของ ChaosPandion ที่สอง แต่ ILSpy เป็นเพื่อนของคุณ
Michael

คำตอบ:


27

คำสั่ง LINQ มีความขี้เกียจ นั่นหมายถึงรหัส:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

ทำน้อยมาก นับเดิม ( mythings) มีระบุเฉพาะเมื่อมีการนับที่เกิด ( things) มีการบริโภคเช่นโดยforeachห่วงหรือ.ToList().ToArray()

ถ้าคุณโทรthings.ToList()มันจะเทียบเท่ากับรหัสหลังของคุณซึ่งอาจมีบางส่วน (โดยทั่วไปไม่มีนัยสำคัญ) จากผู้แจกแจง

ในทำนองเดียวกันถ้าคุณใช้วงหน้า foreach:

foreach (var t in things)
    DoSomething(t);

มันคล้ายกับประสิทธิภาพในการ:

foreach (var t in mythings)
    if (t.IsSomeValue && t.IsSomeOtherValue)
        DoSomething(t);

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

หากนับได้เพียงบางส่วนเท่านั้นสิ่งนี้มีความสำคัญเป็นพิเศษ พิจารณารหัสนี้:

things.First();

วิธีการนำ LINQ ไปใช้mythingsจะถูกระบุจนถึงองค์ประกอบแรกที่ตรงกับเงื่อนไขของคุณ หากองค์ประกอบนั้นอยู่ในช่วงต้นรายการอาจเป็นการเพิ่มประสิทธิภาพอย่างมาก (เช่น O (1) แทน O (n))


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

2
นั่นคือสิ่งที่ฉันหมายถึงโดยค่าใช้จ่ายแจกแจง อาจเป็นปัญหาในบางกรณี (หายาก) แต่จากประสบการณ์ของฉันที่ไม่บ่อยนัก - โดยปกติแล้วเวลาที่ใช้ในการเริ่มต้นจะเล็กมากหรือมีค่าเกินกว่าการปฏิบัติการอื่น ๆ
Cyanfish

ข้อ จำกัด ของการประเมินผลที่น่ารังเกียจขี้เกียจ Linq คือว่ามีวิธีที่จะใช้ "ภาพรวม" ของการแจงนับยกเว้นผ่านทางวิธีการที่ไม่เหมือนใครหรือToList ToArrayหากสิ่งนั้นถูกสร้างขึ้นอย่างถูกต้องIEnumerableมันจะเป็นไปได้ที่จะขอรายการ "สแน็ปช็อต" ด้านใด ๆ ที่อาจเปลี่ยนแปลงในอนาคตโดยไม่ต้องสร้างทุกสิ่ง
supercat

7

รหัสต่อไปนี้:

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue);

เทียบเท่ากับอะไรเลยเพราะการประเมินที่ขี้เกียจไม่มีอะไรจะเกิดขึ้น

var things = mythings
    .Where(x => x.IsSomeValue)
    .Where(y => y.IsSomeOtherValue)
    .ToList();

แตกต่างกันเนื่องจากการประเมินผลจะเปิดตัว

รายการของแต่ละคนจะได้รับไปก่อนmythings ถ้ามันผ่านไปมันจะได้รับที่สองWhere Whereถ้ามันผ่านมันจะเป็นส่วนหนึ่งของการส่งออก

ดังนั้นนี่จะเป็นดังนี้:

var results = new List<Thing>();
foreach(var t in mythings)
{
    if(t.IsSomeValue)
    {
        if(t.IsSomeOtherValue)
        {
            results.Add(t);
        }
    }
}

7

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

ขอเพียงแค่จินตนาการที่คุณเรียกบนToListthings

การดำเนินการของผลตอบแทนEnumerable.Where Enumerable.WhereListIteratorเมื่อคุณโทรWhereไปที่WhereListIterator(สายผูกมัดWhere-calls) คุณจะไม่โทรอีกต่อไปEnumerable.Whereแต่Enumerable.WhereListIterator.Whereจะรวมภาคแสดง (ใช้Enumerable.CombinePredicates)

ดังนั้นจึงเป็นเช่นif(t.IsSomeValue && t.IsSomeOtherValue)นั้นมากขึ้น


"คืน Enumerable.WhereListIterator" ทำให้คลิกเพื่อฉัน อาจเป็นแนวคิดที่ง่ายมาก แต่นั่นคือสิ่งที่ฉันเห็นด้วย ILSpy ขอบคุณ
ConditionRacer

ดูการนำการเพิ่มประสิทธิภาพนี้ไปใช้อีกครั้งของ Jon Skeetหากคุณสนใจการวิเคราะห์เชิงลึกเพิ่มเติม
Servy

1

ไม่มันไม่เหมือนกัน ในตัวอย่างของคุณthingsคือIEnumerableซึ่ง ณ จุดนี้ยังคงเป็นเพียงตัววนซ้ำไม่ใช่อาร์เรย์หรือรายการจริง ยิ่งกว่านั้นเนื่องจากthingsไม่ได้ใช้ลูปจะไม่ได้รับการประเมินแม้แต่ ประเภทนี้IEnumerableอนุญาตให้วนซ้ำผ่านองค์ประกอบต่างๆyieldโดยคำสั่ง Linq และประมวลผลเพิ่มเติมด้วยคำแนะนำเพิ่มเติมซึ่งหมายความว่าในท้ายที่สุดคุณจะมีลูปเดียวเท่านั้น

แต่ทันทีที่คุณเพิ่มคำแนะนำเช่น.ToArray()หรือ.ToList()คุณกำลังสั่งการสร้างโครงสร้างข้อมูลจริงดังนั้นจึงกำหนดขอบเขตให้กับห่วงโซ่ของคุณ

ดูคำถาม SO ที่เกี่ยวข้องนี้: /programming/2789389/how-do-i-implement-ienumerable

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