LINQ. VS ใด ๆ อยู่ - ความแตกต่างคืออะไร


413

การใช้ LINQ กับคอลเล็กชั่นอะไรคือความแตกต่างระหว่างบรรทัดโค้ดต่อไปนี้?

if(!coll.Any(i => i.Value))

และ

if(!coll.Exists(i => i.Value))

อัปเดต 1

เมื่อฉันถอดแยกชิ้นส่วน.Existsดูเหมือนว่าไม่มีรหัส

อัปเดต 2

ใครรู้ว่าทำไมไม่มีรหัสสำหรับหนึ่งนี้


9
โค้ดที่คุณคอมไพล์มีลักษณะอย่างไร คุณแยกชิ้นส่วนได้อย่างไร ildasm? คุณคาดหวังสิ่งใด แต่ไม่พบ
Meinersbur

คำตอบ:


423

ดูเอกสารประกอบ

List.Exists (วิธีวัตถุ - MSDN)

พิจารณาว่า List (T) มีองค์ประกอบที่ตรงกับเงื่อนไขที่กำหนดโดยเพรดิเคตที่ระบุหรือไม่

สิ่งนี้มีอยู่ตั้งแต่. NET 2.0 ดังนั้นก่อน LINQ หมายถึงที่จะใช้กับผู้แทน Predicate แต่การแสดงออกแลมบ์ดานั้นเข้ากันได้แบบย้อนหลัง นอกจากนี้เพียงแค่รายการมีสิ่งนี้ (ไม่แม้แต่ IList)

IEnumerable.Any (วิธีการขยาย - MSDN)

กำหนดว่าองค์ประกอบใด ๆ ของลำดับเป็นไปตามเงื่อนไขหรือไม่

นี่เป็นสิ่งใหม่ใน. NET 3.5 และใช้ Func (TSource, bool) เป็นอาร์กิวเมนต์ดังนั้นนี่จึงมีวัตถุประสงค์เพื่อใช้กับแลมบ์ดานิพจน์และ LINQ

ในพฤติกรรมเหล่านี้เหมือนกัน


4
ผมมารู้ทีหลังทำโพสต์ในหัวข้ออื่นที่ฉันแสดงทั้งหมด Linq "เทียบเท่า" ของ .NET 2 List<>วิธีเช่น
Jeppe Stig Nielsen

201

ความแตกต่างคือว่าใด ๆ เป็นวิธีการขยายสำหรับใด ๆ ที่IEnumerable<T>กำหนดไว้ใน System.Linq.Enumerable มันสามารถใช้กับIEnumerable<T>อินสแตนซ์ ใด ๆ

มีอยู่ไม่ปรากฏว่าเป็นวิธีการขยาย ฉันเดาว่า Coll List<T>เป็นประเภท ถ้ามีอยู่เป็นวิธีการอินสแตนซ์ซึ่งทำหน้าที่คล้ายกับใด ๆ

ในระยะสั้น , วิธีการที่เป็นหลักเดียวกัน หนึ่งทั่วไปมากกว่าอื่น ๆ

  • ผู้ใดก็ตามที่มีการโอเวอร์โหลดซึ่งไม่มีพารามิเตอร์และเพียงแค่มองหารายการใด ๆ
  • มีอยู่ไม่มีเกินพิกัดดังกล่าว

13
เป็นอย่างดี (+1) รายการ <T> มีอยู่ตั้งแต่. Net 2 แต่ใช้ได้กับรายการทั่วไปเท่านั้น IEnumerable <T> .Any ถูกเพิ่มใน. Net 3 เป็นส่วนขยายที่ทำงานกับคอลเลกชันใด ๆ ที่นับได้ นอกจากนี้ยังมีสมาชิกที่คล้ายกันเช่น List <T> .Count ซึ่งเป็นคุณสมบัติและ IEnumerable <T> .Count () - เมธอด
Keith

51

TLDR; ประสิทธิภาพที่ชาญฉลาดAnyดูเหมือนจะช้าลง (ถ้าฉันตั้งค่านี้อย่างถูกต้องเพื่อประเมินค่าทั้งสองในเวลาเดียวกัน)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

เครื่องกำเนิดรายการทดสอบ:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

ด้วยบันทึก 10M

"ใด ๆ : 00: 00: 00.3770377 มีอยู่: 00: 00: 00.2490249"

ด้วยบันทึก 5M

"ใด ๆ : 00: 00: 00.0940094 มีอยู่: 00: 00: 00.1420142"

ด้วย 1M บันทึก

"ใด ๆ : 00: 00: 00.0180018 มีอยู่: 00: 00: 00.0090009"

ด้วย 500k (ฉันพลิกไปตามลำดับที่พวกเขาได้รับการประเมินเพื่อดูว่าไม่มีการดำเนินการเพิ่มเติมใด ๆ ที่เกี่ยวข้องกับสิ่งที่ทำงานก่อนหรือไม่)

"มีอยู่: 00: 00: 00.0050005 อะไรก็ได้: 00: 00: 00.0100010"

ด้วยการบันทึก 100k

"มีอยู่: 00: 00: 00.0010001 อะไรก็ได้: 00: 00: 00.0020002"

ดูเหมือนว่าAnyจะช้าลงด้วยขนาด 2

แก้ไข:สำหรับ 5 และ 10M บันทึกฉันเปลี่ยนวิธีที่มันสร้างรายการและExistsก็ช้ากว่าAnyที่แสดงถึงมีบางอย่างผิดปกติในวิธีที่ฉันกำลังทดสอบ

กลไกการทดสอบใหม่:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

แก้ไข 2:ตกลงเพื่อกำจัดอิทธิพลใด ๆ จากการสร้างข้อมูลทดสอบฉันเขียนมันทั้งหมดไปยังไฟล์และตอนนี้อ่านจากที่นั่น

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10M

"ใด ๆ : 00: 00: 00.1640164 มีอยู่: 00: 00: 00.0750075"

5M

"ใด ๆ : 00: 00: 00.0810081 มีอยู่: 00: 00: 00.0360036"

1M

"ใด ๆ : 00: 00: 00.0190019 มีอยู่: 00: 00: 00.0070007"

500k

"Any: 00: 00: 00.0120012 มีอยู่: 00: 00: 00.0040004"

ป้อนคำอธิบายรูปภาพที่นี่


3
ไม่มีความน่าเชื่อถือสำหรับคุณ แต่ฉันรู้สึกสงสัยมากกว่ามาตรฐานเหล่านี้ ดูที่ตัวเลข: ผลลัพธ์ทุกรายการมีการเรียกซ้ำ (3770377: 2490249) อย่างน้อยสำหรับฉันนั่นเป็นสัญญาณว่าบางอย่างไม่ถูกต้อง ฉันไม่แน่ใจร้อยเปอร์เซ็นต์เกี่ยวกับคณิตศาสตร์ที่นี่ แต่ฉันคิดว่าอัตราต่อรองของรูปแบบที่เกิดซ้ำนั้นคือ 1 ใน 999 ^ 999 (หรือ 999! ดังนั้นโอกาสที่จะเกิดขึ้น8 ครั้งติดต่อกันจึงน้อยมาก ฉันคิดว่ามันเป็นเพราะคุณใช้DateTime สำหรับการเปรียบเทียบ
Jerri Kangasniemi

@JerriKangasniemi ทำซ้ำการดำเนินการเดียวกันในการแยกควรใช้เวลาเท่ากันเสมอกันไปสำหรับการทำซ้ำหลายครั้ง อะไรทำให้คุณพูดว่าเป็น DateTime
Matas Vaitkevicius

แน่นอนมันไม่ ปัญหายังคงเป็นไปไม่ได้อย่างมหาศาลที่จะใช้เวลาเช่น 0120012 วินาทีสำหรับการโทร 500k และถ้ามันเป็นเส้นตรงอย่างสมบูรณ์ดังนั้นการอธิบายตัวเลขอย่างนั้นการโทร 1M จะใช้เวลา 0240024 วินาที (นานเป็นสองเท่า) อย่างไรก็ตามนั่นไม่ใช่กรณี 1M การโทรใช้เวลา 58, (3)% นานกว่า 500k และ 10M ใช้เวลานานกว่า 102,5% นานกว่า 5M ดังนั้นมันจึงไม่ใช่ฟังก์ชันเชิงเส้นดังนั้นจึงไม่สมเหตุสมผลสำหรับจำนวนที่จะเรียกคืนทั้งหมด ฉันพูดถึง DateTime เพราะฉันเคยมีปัญหากับตัวเองในอดีตเนื่องจาก DateTime ไม่ได้ใช้ตัวจับเวลาที่มีความแม่นยำสูง
Jerri Kangasniemi

2
@JerriKangasniemi ฉันขอแนะนำให้คุณแก้ไขและโพสต์คำตอบ
Matas Vaitkevicius

1
ถ้าฉันอ่านผลลัพธ์ของคุณถูกต้องคุณรายงานว่ามีค่าประมาณ 2 ถึง 3 เท่าของความเร็วที่มีอยู่ ฉันไม่เห็นว่าข้อมูลสนับสนุนการยืนยันของคุณอย่างอ่อนโยนเพียงใดว่า "ดูเหมือนว่า Any จะช้าลงด้วยขนาด 2" มันช้ากว่าเล็กน้อยแน่นอนไม่ใช่คำสั่งของขนาด
Suncat2000

16

เป็นความต่อเนื่องในคำตอบของ Matasเกี่ยวกับการเปรียบเทียบ

TL / DR : มีอยู่ () และใด ๆ () มีความรวดเร็วเท่ากัน

ก่อนปิด: การเปรียบเทียบโดยใช้นาฬิกาจับเวลาไม่แม่นยำ ( ดูคำตอบของ series0ne ในหัวข้อที่แตกต่างกัน แต่คล้ายกัน ) แต่มันแม่นยำกว่า DateTime มาก

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

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

หลังจากรันโค้ดด้านบน 4 ครั้ง (ซึ่งจะทำ 1,000 Exists()และAny()ในรายการที่มีองค์ประกอบ 1,000,000) ก็ไม่ยากที่จะเห็นว่าวิธีการนั้นค่อนข้างเร็วเท่ากัน

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

มีคือความแตกต่างเล็กน้อย แต่มันคือความแตกต่างที่มีขนาดเล็กเกินไปที่จะไม่สามารถอธิบายได้ด้วยเสียงพื้นหลัง ฉันเดาจะเป็นไปได้ว่าถ้าใครจะทำ 10 000 หรือ 100 000 Exists()และAny()แทนที่แตกต่างกันเล็กน้อยจะหายไปมากหรือน้อย


ฉันขอแนะนำให้คุณทำ 10,000 ถึง 100,000 และ 1000000 เพื่อให้มีระเบียบเกี่ยวกับเรื่องนี้ทำไมค่า min และไม่ใช่ค่าเฉลี่ย?
Matas Vaitkevicius

2
ค่าต่ำสุดเป็นเพราะฉันต้องการเปรียบเทียบการประมวลผลที่เร็วที่สุด (= อาจมีจำนวนเสียงรบกวนพื้นหลังน้อยที่สุด) ของแต่ละวิธี ฉันอาจทำซ้ำได้มากขึ้นถึงแม้ว่ามันจะเกิดขึ้นในภายหลัง (ฉันสงสัยว่าเจ้านายของฉันต้องการจ่ายเงินให้ฉันเพื่อทำสิ่งนี้แทนที่จะทำงานผ่าน Backlog ของเรา)
Jerri Kangasniemi

ฉันได้ถามพอล Lindberg และเขาบอกว่ามันไม่เป็นไร;) ในการไปถึงขั้นต่ำที่ฉันสามารถดูวิธีการเหตุผลของคุณ แต่ดั้งเดิมมากขึ้นคือการใช้เฉลี่ยen.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
หากรหัสที่คุณโพสต์นั้นเป็นรหัสที่คุณเรียกใช้จริง ๆ แล้วก็ไม่น่าแปลกใจเลยที่คุณจะได้รับผลลัพธ์ที่คล้ายกันในขณะที่คุณเรียกมีอยู่ในการวัดทั้งสอง ;)
Simon Touchtech

เฮ้ใช่ฉันเห็นแล้วเหมือนกันที่คุณพูด ไม่ได้อยู่ในการดำเนินการของฉัน นี่เป็นเพียงแนวคิดที่แยกออกจากสิ่งที่ฉันเปรียบเทียบ : P
Jerri Kangasniemi

4

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


3

เมื่อคุณแก้ไขการวัด - ตามที่กล่าวไว้ข้างต้น: สิ่งที่มีอยู่และการเพิ่มค่าเฉลี่ย - เราจะได้ผลลัพธ์ต่อไปนี้:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

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