ลำดับของฟังก์ชัน LINQ มีความสำคัญหรือไม่?


114

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

ตัวอย่าง:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

ทั้งสองให้ผลลัพธ์เดียวกันกับฉัน แต่อยู่ในลำดับ LINQ ที่แตกต่างกัน ฉันตระหนักดีว่าการจัดเรียงรายการบางรายการใหม่จะทำให้ได้ผลลัพธ์ที่แตกต่างกันและฉันไม่ได้กังวลเกี่ยวกับสิ่งเหล่านั้น สิ่งที่ฉันกังวลหลักคือการรู้ว่าในการได้ผลลัพธ์เดียวกันการสั่งซื้ออาจส่งผลต่อประสิทธิภาพหรือไม่ และไม่ใช่แค่ในการโทร 2 LINQ ที่ฉันโทร (OrderBy, Where) แต่ในการโทร LINQ ใด ๆ


9
คำถามน่ากลัว
Robert S.

var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);มันก็ยิ่งชัดเจนมากขึ้นในเรื่องการเพิ่มประสิทธิภาพของผู้ให้บริการกับกรณีที่อวดความรู้มากขึ้นเช่น
Mark Hurd

1
คุณได้รับการโหวตขึ้น :) คำถามที่น่าสนใจ ฉันจะพิจารณาเมื่อเขียน Linq ถึง Entities ใน EF
GibboK

1
@GibboK: โปรดใช้ความระมัดระวังเมื่อพยายาม "เพิ่มประสิทธิภาพ" คำค้นหา LINQ ของคุณ (ดูคำตอบด้านล่าง) บางครั้งคุณไม่ได้ลงเอยด้วยการเพิ่มประสิทธิภาพอะไรเลย ควรใช้เครื่องมือสร้างโปรไฟล์เมื่อพยายามเพิ่มประสิทธิภาพ
myermian

คำตอบ:


147

จะขึ้นอยู่กับผู้ให้บริการ LINQ ที่ใช้งาน สำหรับ LINQ to Objects นั้นสามารถสร้างความแตกต่างอย่างมาก สมมติว่าเรามีจริง:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

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

เปรียบเทียบกับการดำเนินการย้อนกลับโดยกรองก่อน:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

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

นอกจากนี้ยังสามารถสร้างความแตกต่างว่าแบบสอบถามดำเนินการอย่างถูกต้องหรือไม่ พิจารณา:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

ไม่เป็นไร - เรารู้ว่าเราจะไม่หารด้วย 0 แต่ถ้าเราทำการสั่งซื้อก่อนการกรองแบบสอบถามจะทำให้เกิดข้อยกเว้น


2
@ Jon Skeet มีเอกสารเกี่ยวกับ Big-O สำหรับผู้ให้บริการและฟังก์ชัน LINQ แต่ละรายการหรือไม่? หรือนี่เป็นเพียงกรณีของ "แต่ละนิพจน์ไม่ซ้ำกันสำหรับสถานการณ์"
michael

1
@ ไมเคิล: มันไม่ได้มีการบันทึกไว้อย่างชัดเจน แต่ถ้าคุณอ่านบล็อกซีรีส์ "Edulinq" ของฉันฉันคิดว่าฉันพูดถึงเรื่องนี้ในรายละเอียดที่สมเหตุสมผล
Jon Skeet

3
@michael: คุณสามารถค้นหาได้ที่นี่msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
VoodooChild

3
@gdoron: ยังไม่ชัดเจนว่าคุณหมายถึงอะไรพูดตามตรง ดูเหมือนว่าคุณอาจต้องการเขียนคำถามใหม่ โปรดจำไว้ว่า Queryable ไม่ได้พยายามตีความคำค้นหาของคุณเลยงานนี้มีไว้เพื่อรักษาคำค้นหาของคุณไว้เพื่อให้สิ่งอื่นสามารถตีความได้ โปรดทราบว่า LINQ to Objects ไม่ได้ใช้ต้นไม้นิพจน์
Jon Skeet

1
@gdoron: ประเด็นคืองานของผู้ให้บริการไม่ใช่งานของ Queryable และไม่ควรสำคัญเมื่อใช้ Entity Framework ด้วยเช่นกัน มันไม่เรื่อง LINQ กับวัตถุแม้ว่า แต่ใช่แล้วถามคำถามอื่น
Jon Skeet

17

ใช่.

แต่ว่าสิ่งที่แตกต่างประสิทธิภาพขึ้นอยู่กับวิธีการที่ต้นไม้แสดงออกพื้นฐานได้รับการประเมินโดยผู้ให้บริการ LINQ

ตัวอย่างเช่นการสืบค้นของคุณอาจดำเนินการเร็วขึ้นในครั้งที่สอง (โดยใช้ WHERE clause first) สำหรับ LINQ-to-XML แต่เร็วกว่าในครั้งแรกสำหรับ LINQ-to-SQL

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


5

ในตัวอย่างเฉพาะของคุณสามารถสร้างความแตกต่างให้กับประสิทธิภาพได้

ข้อความค้นหาแรก: การOrderByโทรของคุณต้องวนซ้ำตามลำดับแหล่งที่มาทั้งหมดรวมถึงรายการเหล่านั้นที่Code3 หรือน้อยกว่า Whereข้อแล้วยังต้องย้ำทั้งสั่งซื้อลำดับ

แบบสอบถามที่สอง: การWhereโทรจะ จำกัด ลำดับให้เฉพาะรายการที่Codeมากกว่า 3 OrderByจากนั้นการเรียกจะต้องสำรวจลำดับที่ลดลงซึ่งส่งคืนโดยการWhereเรียกเท่านั้น


3

ใน Linq-To-Objects:

การเรียงลำดับค่อนข้างช้าและใช้O(n)หน่วยความจำ Whereในทางกลับกันค่อนข้างเร็วและใช้หน่วยความจำคงที่ ดังนั้นการทำWhereก่อนจะเร็วกว่าและสำหรับคอลเลกชันขนาดใหญ่เร็วกว่า

ความดันหน่วยความจำที่ลดลงอาจมีนัยสำคัญเช่นกันเนื่องจากการจัดสรรบนฮีปวัตถุขนาดใหญ่ (ร่วมกับคอลเล็กชันของพวกเขา) มีราคาค่อนข้างแพงในประสบการณ์ของฉัน


1

เห็นได้ชัดว่าผลลัพธ์จะต้องเหมือนเดิม ...

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

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
ไม่สิ่งที่ฉันหมายถึงคือผลลัพธ์ควรเหมือนกันเพื่อพิจารณาการเพิ่มประสิทธิภาพ ไม่มีประเด็นในการ "เพิ่มประสิทธิภาพ" บางสิ่งและได้ผลลัพธ์ที่แตกต่าง
michael

1

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

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

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

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

ฉันรู้ว่ามีคนไม่มากนักที่ใช้ LINQ แบบเปิดเผยสำหรับวัตถุ แต่มันก็เป็นอาหารที่ดีสำหรับความคิด


0

ขึ้นอยู่กับความเกี่ยวข้อง สมมติว่าคุณมีสินค้าน้อยมากที่มี Code = 3 คำสั่งซื้อถัดไปจะทำงานกับคอลเล็กชันชุดเล็ก ๆ เพื่อรับคำสั่งซื้อตามวันที่

ในขณะที่คุณมีหลายรายการที่มี CreatedDate เดียวกันคำสั่งซื้อถัดไปจะทำงานกับคอลเล็กชันชุดใหญ่เพื่อรับคำสั่งซื้อตามวันที่

ดังนั้นในทั้งสองกรณีประสิทธิภาพจะแตกต่างกัน

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