ประสิทธิภาพของอาร์เรย์กับรายการ


194

สมมติว่าคุณต้องมีรายการ / อาร์เรย์ของจำนวนเต็มที่คุณต้องวนซ้ำบ่อยๆและฉันหมายถึงบ่อยมาก เหตุผลอาจแตกต่างกัน แต่บอกว่ามันอยู่ในหัวใจของวงส่วนใหญ่ภายในของการประมวลผลปริมาณสูง

โดยทั่วไปจะเลือกใช้รายการ (รายการ) เนื่องจากความยืดหยุ่นในขนาด ยิ่งกว่านั้นเอกสาร msdn ที่อ้างสิทธิ์ว่ารายการใช้อาร์เรย์ภายในและควรดำเนินการอย่างรวดเร็ว (ดูอย่างรวดเร็วด้วยตัวสะท้อนที่ยืนยันสิ่งนี้) ไม่ว่าจะมีค่าใช้จ่ายที่เกี่ยวข้อง

ใครบ้างที่วัดสิ่งนี้จริงหรือ การวนซ้ำ 6M ครั้งในรายการใช้เวลาเดียวกับอาร์เรย์หรือไม่


3
ปัญหาด้านประสิทธิภาพการทำงานฉันต้องการใช้ Array มากกว่ารายการสำหรับขนาดคงที่ (ในกรณีที่ไม่จำเป็นต้องเปลี่ยนจำนวนรายการ) เมื่ออ่านรหัสที่มีอยู่ฉันพบว่ามีประโยชน์ที่จะรู้ได้อย่างรวดเร็วว่ารายการนั้นถูกบังคับให้มีขนาดคงที่แทนที่จะต้องตรวจสอบรหัสเพิ่มเติมในฟังก์ชัน
Warren Stevens

2
T[]เทียบกับList<T>สามารถสร้างความแตกต่างประสิทธิภาพใหญ่ ฉันเพิ่งปรับแอพพลิเคชั่นแบบวนมากที่ซ้อนกันเพื่อย้ายจากรายการไปยังอาร์เรย์ใน. NET 4.0 ฉันคาดหวังว่าอาจจะได้รับการปรับปรุง 5% ถึง 10% แต่เพิ่มความเร็วได้มากกว่า 40%! ไม่มีการเปลี่ยนแปลงอื่นนอกจากย้ายโดยตรงจากรายการไปยังอาร์เรย์ การแจกแจงทั้งหมดกระทำด้วยforeachข้อความสั่ง ขึ้นอยู่กับคำตอบ Marc Gravell ของมันดูเหมือนforeachกับList<T>ไม่ดีโดยเฉพาะอย่างยิ่ง
ซอสพิเศษ

คำตอบ:


221

วัดง่ายมาก ...

ในรหัสการประมวลผลแบบลูปจำนวนน้อยที่ฉันรู้ว่าความยาวได้รับการแก้ไขฉันใช้อาร์เรย์สำหรับการเพิ่มประสิทธิภาพขนาดเล็กพิเศษเล็กน้อย อาร์เรย์อาจเร็วกว่าเล็กน้อยหากคุณใช้ตัวทำดัชนี / สำหรับฟอร์ม - แต่ IIRC เชื่อว่ามันขึ้นอยู่กับชนิดของข้อมูลในอาร์เรย์ แต่ถ้าคุณต้องการที่จะไมโครเพิ่มประสิทธิภาพให้มันง่ายและใช้List<T>ฯลฯ

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

นี่คือผลลัพธ์ของฉันโดยใช้ "int" (หมายเลขที่สองคือการตรวจสอบเพื่อยืนยันว่าพวกเขาทั้งหมดทำงานเหมือนกัน):

(แก้ไขเพื่อแก้ไขข้อบกพร่อง)

List/for: 1971ms (589725196)
Array/for: 1864ms (589725196)
List/foreach: 3054ms (589725196)
Array/foreach: 1860ms (589725196)

ขึ้นอยู่กับอุปกรณ์ทดสอบ:

using System;
using System.Collections.Generic;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(12345);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next(5000));
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

8
รายละเอียดที่น่าสนใจ: นี่คือเวลาปล่อย / แก้ปัญหาบนแท่นขุดเจาะของฉัน (.net 3.5 sp1): 0.92, 0.80, 0.96, 0.93; ซึ่งบอกฉันว่ามีความฉลาดใน VM เพื่อเพิ่มประสิทธิภาพ Array / สำหรับวนรอบประมาณ 10% ดีกว่ากรณีอื่น ๆ
David Schmitt

2
ใช่มีการเพิ่มประสิทธิภาพ JIT สำหรับอาร์เรย์ / สำหรับ ที่จริงแล้วฉันอยู่ภายใต้ความประทับใจที่รวมกรณีความยาว (เนื่องจากทราบว่าได้รับการแก้ไขแล้ว) ดังนั้นทำไมฉันจึงไม่ดึงมันออกมาก่อน (ต่างจากรายการที่ฉันทำ) ขอบคุณสำหรับการอัพเดท.
Marc Gravell

2
แปลก. การทดสอบที่คล้ายกันมากของฉันไม่แสดงความแตกต่างอย่างมีนัยสำคัญระหว่าง foreach และ foreach เมื่อใช้อาร์เรย์ จะตรวจสอบอย่างละเอียดในโพสต์บล็อกที่มีมาตรฐานที่คนอื่น ๆ สามารถทำงานและส่งผลสำหรับ ...
จอน Skeet

1
ฉันได้รับผลลัพธ์ที่แตกต่างกันอย่างมากถ้าใช้ตัวแปรที่แตกต่างกันสำหรับ chk สำหรับการทดสอบแต่ละครั้ง (chk1, chk2, chk3, chk4) รายการ / สำหรับ = 1303ms, อาร์เรย์ / สำหรับ = 433ms ความคิดใด ๆ
Jon

4
ลิงก์ที่กล่าวถึงในความคิดเห็นด้านบนโดย Jon ไปยังบล็อกของ Jon Skeet นั้นใช้งานไม่ได้ ด้านล่างคือลิงค์ที่อัพเดท codeblog.jonskeet.uk/2009/01/29/…
Josh DeLong

88

สรุป:

  • อาร์เรย์ต้องใช้:

    • บ่อยครั้งที่สุด มันเร็วและใช้ช่วง RAM ที่เล็กที่สุดสำหรับข้อมูลจำนวนเดียวกัน
    • หากคุณรู้จำนวนเซลล์ที่แน่นอน
    • ถ้าข้อมูลถูกบันทึกในอาร์เรย์ <85000 b (85000/32 = 2656 องค์ประกอบสำหรับข้อมูลจำนวนเต็ม)
    • หากต้องการความเร็วในการเข้าถึงสูงแบบสุ่ม
  • รายการต้องใช้:

    • หากจำเป็นต้องเพิ่มเซลล์ในตอนท้ายของรายการ (บ่อยครั้ง)
    • หากจำเป็นต้องเพิ่มเซลล์ในจุดเริ่มต้น / กลางของรายการ (ไม่ได้ออฟ)
    • ถ้าข้อมูลถูกบันทึกในอาร์เรย์ <85000 b (85000/32 = 2656 องค์ประกอบสำหรับข้อมูลจำนวนเต็ม)
    • หากต้องการความเร็วในการเข้าถึงสูงแบบสุ่ม
  • LinkedList ต้องใช้:

    • หากจำเป็นต้องเพิ่มเซลล์ในต้น / กลาง / ปลายของรายการ (มัก)

    • หากจำเป็นต้องเข้าถึงแบบต่อเนื่องเท่านั้น (ไปข้างหน้า / ข้างหลัง)

    • หากคุณต้องการบันทึกรายการขนาดใหญ่ แต่จำนวนรายการต่ำ

    • อย่าใช้ดีกว่าสำหรับรายการจำนวนมากเพราะใช้หน่วยความจำเพิ่มเติมสำหรับลิงค์

      หากคุณไม่แน่ใจว่าคุณต้องการลิงค์ลิสต์ - คุณไม่จำเป็นต้องใช้มัน


รายละเอียดเพิ่มเติม:

ความหมายของสี

Array vs List เทียบกับรายชื่อที่เชื่อมโยง

รายละเอียดเพิ่มเติม:

https://stackoverflow.com/a/29263914/4423545


ฉันสับสนเล็กน้อยจากการที่คุณยืนยันว่ารายการที่เติมไว้นั้นค่อนข้างเร็ว แต่การแทรกช้า การแทรกยังเป็นเวลาเชิงเส้นและเร็วขึ้น 50% โดยเฉลี่ยกว่าแบบเติม
Mike Marynowski

1
@MikeMarynowski ใน c # list คือ wrapper รอบ ๆ Array ดังนั้นในกรณีของการแทรกลงในรายการคุณจะมีเวลาเชิงเส้นไปยังบางจุดเท่านั้น หลังจากระบบนี้จะสร้างอาร์เรย์ใหม่ที่ใหญ่กว่าและต้องใช้เวลาในการคัดลอกรายการจากเก่า
แอนดรู

สิ่งเดียวกันกับ prepends
Mike Marynowski

การดำเนินการล่วงหน้าเป็นเพียงการแทรกที่ 0 มันเป็นตัวแทรกกรณีที่แย่ที่สุดในแง่ของประสิทธิภาพดังนั้นหากการแทรกช้าการเติมล่วงหน้าจะยิ่งช้าลง
Mike Marynowski

ทั้งส่วนแทรกและส่วนเสริมคือ O (n) (ตัดจำหน่าย) ส่วนเสริมเป็นส่วนเสริม แต่เป็นส่วนแทรกที่ช้าที่สุดเนื่องจากต้องย้ายรายการทั้งหมดในรายการหนึ่งจุด การแทรกในตำแหน่งสุ่มจะต้องเลื่อนรายการที่ดัชนีสูงกว่าจุดแทรกดังนั้นโดยเฉลี่ย 50% ของรายการ
Mike Marynowski

26

ฉันคิดว่าการแสดงจะคล้ายกันมาก โอเวอร์เฮดที่เกี่ยวข้องเมื่อใช้รายการเทียบกับอาร์เรย์คือ IMHO เมื่อคุณเพิ่มรายการลงในรายการและเมื่อรายการต้องเพิ่มขนาดของอาร์เรย์ที่ใช้ภายในเมื่อถึงความจุของอาร์เรย์

สมมติว่าคุณมีรายการที่มีความจุ 10 รายการจะเพิ่มความจุเมื่อคุณต้องการเพิ่มองค์ประกอบที่ 11 คุณสามารถลดผลกระทบด้านประสิทธิภาพโดยการกำหนดค่าเริ่มต้นความจุของรายการเป็นจำนวนรายการที่จะเก็บไว้

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

int numberOfElements = 6000000;

List<int> theList = new List<int> (numberOfElements);
int[] theArray = new int[numberOfElements];

for( int i = 0; i < numberOfElements; i++ )
{
    theList.Add (i);
    theArray[i] = i;
}

Stopwatch chrono = new Stopwatch ();

chrono.Start ();

int j;

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theList[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the List took {0} msec", chrono.ElapsedMilliseconds));

 chrono.Reset();

 chrono.Start();

 for( int i = 0; i < numberOfElements; i++ )
 {
     j = theArray[i];
 }

 chrono.Stop ();
 Console.WriteLine (String.Format("iterating the array took {0} msec", chrono.ElapsedMilliseconds));

 Console.ReadLine();

ในระบบของฉัน; วนซ้ำในอาร์เรย์ใช้เวลา 33 มิลลิวินาที วนซ้ำในรายการใช้เวลา 66 มิลลิวินาที

บอกตามตรงฉันไม่ได้คาดหวังว่าความผันแปรจะเป็นเท่าไหร่ ดังนั้นฉันจึงวนซ้ำของฉัน: ตอนนี้ฉันดำเนินการซ้ำทั้ง 1,000 ครั้ง ผลลัพธ์ที่ได้คือ:

การวนซ้ำรายการใช้เวลา 67146 msec วนซ้ำการเรียงลำดับใช้ 40821 msec

ทีนี้รูปแบบก็ไม่ใหญ่อีกต่อไป แต่ก็ยัง ...

ดังนั้นฉันได้เริ่มต้น. NET Reflector และผู้ทะเยอทะยานของตัวสร้างดัชนีของคลาส List ดูเหมือนว่า:

public T get_Item(int index)
{
    if (index >= this._size)
    {
        ThrowHelper.ThrowArgumentOutOfRangeException();
    }
    return this._items[index];
}

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


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

1
จะไม่ส่งคืนสิ่งนี้ _items [ดัชนี]; ส่งข้อยกเว้นหากดัชนีอยู่นอกช่วงหรือไม่ เหตุใด. NET จึงมีการตรวจสอบพิเศษนี้เมื่อผลลัพธ์สุดท้ายเหมือนกันทั้งที่มีหรือไม่มี
John Mercier

@John Mercier การตรวจสอบนั้นขัดกับขนาดของรายการ (จำนวนรายการที่มีอยู่ในปัจจุบัน) ซึ่งแตกต่างกันและอาจน้อยกว่าความจุของอาร์เรย์ _items อาร์เรย์ได้รับการจัดสรรโดยมีความจุเกินกว่าที่จะทำให้การเพิ่มรายการในอนาคตเร็วขึ้นโดยไม่ต้องมีการจัดสรรใหม่ทุกครั้ง
Trasvi

21

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

หากคุณใช้ของคุณเองสำหรับ (int int i = 0; i <x. [ความยาว / จำนวน]; i ++) ดังนั้นความแตกต่างที่สำคัญมีดังนี้:

  • อาร์เรย์:
    • การตรวจสอบขอบเขตจะถูกลบออก
  • รายการ
    • ทำการตรวจสอบขอบเขต

หากคุณใช้ foreach ความแตกต่างที่สำคัญมีดังนี้:

  • อาร์เรย์:
    • ไม่มีการจัดสรรวัตถุเพื่อจัดการการทำซ้ำ
    • การตรวจสอบขอบเขตจะถูกลบออก
  • รายการผ่านตัวแปรที่รู้จักกันว่าเป็นรายการ
    • ตัวแปรการจัดการการทำซ้ำคือการจัดสรรสแต็ก
    • ทำการตรวจสอบขอบเขต
  • รายการผ่านตัวแปรที่รู้จักกันว่าเป็น IList
    • ตัวแปรการจัดการการทำซ้ำคือการจัดสรรฮีป
    • การตรวจสอบขอบเขตจะดำเนินการพร้อมกับค่ารายการอาจไม่สามารถเปลี่ยนแปลงได้ในระหว่างการ foreach ในขณะที่อาร์เรย์สามารถ

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


11

[ ดูคำถามนี้ด้วย ]

ฉันได้แก้ไขคำตอบของ Marc เพื่อใช้ตัวเลขสุ่มจริงและใช้งานจริงในทุกกรณี

ผล:

         foreach
อาเรย์: 1575ms 1575ms (+ 0%)
รายการ: 1630ms 2627ms (+ 61%)
         (+ 3%) (+ 67%)

(Checksum: -1000038876)

คอมไพล์เป็น Release ภายใต้ VS 2008 SP1 ทำงานโดยไม่มีการดีบักใน Q6600@2.40GHz, .NET 3.5 SP1

รหัส:

class Program
{
    static void Main(string[] args)
    {
        List<int> list = new List<int>(6000000);
        Random rand = new Random(1);
        for (int i = 0; i < 6000000; i++)
        {
            list.Add(rand.Next());
        }
        int[] arr = list.ToArray();

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = arr.Length;
            for (int i = 0; i < len; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);
        Console.WriteLine();

        Console.ReadLine();
    }
}

มันแปลกมาก - ฉันเพิ่งเรียกใช้รหัสที่แน่นอนของคุณซึ่งสร้างจากบรรทัดคำสั่ง (3.5SP1) ด้วย / o + / debug- และผลลัพธ์ของฉันคือ: list / for: 1524; อาร์เรย์ / สำหรับ: 1472; รายการ / foreach: 4128; อาร์เรย์ / foreach: 1484
Jon Skeet

คุณบอกว่านี่เป็นคอมไพล์ในรีลีส - ฉันสามารถตรวจสอบได้ว่าคุณรันมันมากกว่าการดีบั๊กหรือไม่? คำถามโง่ฉันรู้ แต่ฉันไม่สามารถอธิบายได้ผลเป็นอย่างอื่น ...
จอน Skeet

2

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


2

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


2

นี่คือสิ่งที่ใช้พจนานุกรม, IEnumerable:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

static class Program
{
    static void Main()
    {
        List<int> list = new List<int>(6000000);

        for (int i = 0; i < 6000000; i++)
        {
                list.Add(i);
        }
        Console.WriteLine("Count: {0}", list.Count);

        int[] arr = list.ToArray();
        IEnumerable<int> Ienumerable = list.ToArray();
        Dictionary<int, bool> dict = list.ToDictionary(x => x, y => true);

        int chk = 0;
        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            int len = list.Count;
            for (int i = 0; i < len; i++)
            {
                chk += list[i];
            }
        }
        watch.Stop();
        Console.WriteLine("List/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            for (int i = 0; i < arr.Length; i++)
            {
                chk += arr[i];
            }
        }
        watch.Stop();
        Console.WriteLine("Array/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }

        Console.WriteLine("Ienumerable/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }

        Console.WriteLine("Dict/for: {0}ms ({1})", watch.ElapsedMilliseconds, chk);


        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in list)
            {
                chk += i;
            }
        }

        watch.Stop();
        Console.WriteLine("List/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in arr)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Array/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);



        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in Ienumerable)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Ienumerable/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        chk = 0;
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 100; rpt++)
        {
            foreach (int i in dict.Keys)
            {
                chk += i;
            }
        }
        watch.Stop();
        Console.WriteLine("Dict/foreach: {0}ms ({1})", watch.ElapsedMilliseconds, chk);

        Console.ReadLine();
    }
}

2

อย่าพยายามเพิ่มความจุโดยการเพิ่มจำนวนองค์ประกอบ

ประสิทธิภาพ

List For Add: 1ms
Array For Add: 2397ms

    Stopwatch watch;
        #region --> List For Add <--

        List<int> intList = new List<int>();
        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            intList.Add(rand.Next());
        }
        watch.Stop();
        Console.WriteLine("List For Add: {0}ms", watch.ElapsedMilliseconds);
        #endregion

        #region --> Array For Add <--

        int[] intArray = new int[0];
        watch = Stopwatch.StartNew();
        int sira = 0;
        for (int rpt = 0; rpt < 60000; rpt++)
        {
            sira += 1;
            Array.Resize(ref intArray, intArray.Length + 1);
            intArray[rpt] = rand.Next();

        }
        watch.Stop();
        Console.WriteLine("Array For Add: {0}ms", watch.ElapsedMilliseconds);

        #endregion

ฉันได้ปรับขนาดอาเรย์ 60k เป็นไปได้ช้า ... แน่นอนในการใช้งานในโลกแห่งความเป็นจริง แต่วิธีการนั้นก็เพื่อตรวจสอบว่าคุณต้องการช่องพิเศษจำนวนเท่าไรปรับขนาดให้ยาว + 60k
tobriand

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

2

ฉันเป็นห่วงว่า Benchmarks ที่โพสต์ในคำตอบอื่น ๆ จะยังคงมีที่ว่างสำหรับคอมไพเลอร์ในการเพิ่มประสิทธิภาพกำจัดหรือรวมลูปดังนั้นฉันจึงเขียนหนึ่งที่:

  • อินพุตที่ไม่แน่นอนที่ใช้ (สุ่ม)
  • รันการคำนวณด้วยผลลัพธ์ที่พิมพ์ไปยังคอนโซล
  • แก้ไขข้อมูลอินพุตแต่ละการทำซ้ำ

ผลที่ตามมาคืออาร์เรย์โดยตรงมีประสิทธิภาพที่ดีกว่าประมาณ 250% กว่าการเข้าถึงอาร์เรย์ที่ห่อหุ้มด้วย IList:

  • เข้าถึงอาร์เรย์ 1 พันล้านครั้ง: 4000 ms
  • เข้าถึงได้ 1,000,000 รายการ: 10,000 ms
  • เข้าถึงอาร์เรย์ได้ 100 ล้านรายการ: 350 มิลลิวินาที
  • เข้าถึงรายการได้ 100 ล้านรายการ: 1,000 มิลลิวินาที

นี่คือรหัส:

static void Main(string[] args) {
  const int TestPointCount = 1000000;
  const int RepetitionCount = 1000;

  Stopwatch arrayTimer = new Stopwatch();
  Stopwatch listTimer = new Stopwatch();

  Point2[] points = new Point2[TestPointCount];
  var random = new Random();
  for (int index = 0; index < TestPointCount; ++index) {
    points[index].X = random.NextDouble();
    points[index].Y = random.NextDouble();
  }

  for (int repetition = 0; repetition <= RepetitionCount; ++repetition) {
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Start();
    }
    doWorkOnArray(points);
    if (repetition > 0) { // first repetition is for cache warmup
      arrayTimer.Stop();
    }

    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Start();
    }
    doWorkOnList(points);
    if (repetition > 0) { // first repetition is for cache warmup
      listTimer.Stop();
    }
  }

  Console.WriteLine("Ignore this: " + points[0].X + points[0].Y);
  Console.WriteLine(
    string.Format(
      "{0} accesses on array took {1} ms",
      RepetitionCount * TestPointCount, arrayTimer.ElapsedMilliseconds
    )
  );
  Console.WriteLine(
    string.Format(
      "{0} accesses on list took {1} ms",
      RepetitionCount * TestPointCount, listTimer.ElapsedMilliseconds
    )
  );

}

private static void doWorkOnArray(Point2[] points) {
  var random = new Random();

  int pointCount = points.Length;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

private static void doWorkOnList(IList<Point2> points) {
  var random = new Random();

  int pointCount = points.Count;

  Point2 accumulated = Point2.Zero;
  for (int index = 0; index < pointCount; ++index) {
    accumulated.X += points[index].X;
    accumulated.Y += points[index].Y;
  }

  accumulated /= pointCount;

  // make use of the result somewhere so the optimizer can't eliminate the loop
  // also modify the input collection so the optimizer can merge the repetition loop
  points[random.Next(0, pointCount)] = accumulated;
}

0

เนื่องจาก List <> ใช้อาร์เรย์ภายในประสิทธิภาพพื้นฐานจึงควรเหมือนกัน สองเหตุผลทำไมรายการอาจช้าลงเล็กน้อย:

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

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


0

เนื่องจากฉันมีคำถามคล้ายกันทำให้ฉันเริ่มต้นได้อย่างรวดเร็ว

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

การทดสอบที่ทำโดย Marc Gravell แสดงให้เห็นเป็นอย่างมาก แต่ไม่ใช่เวลาเข้าถึง เวลาของเขารวมถึงการวนลูปมากกว่าอาร์เรย์และรายการเช่นกัน ตั้งแต่ฉันขึ้นมาด้วยวิธีที่สามที่ฉันต้องการทดสอบ 'พจนานุกรม' เพื่อเปรียบเทียบฉันขยายรหัสทดสอบ hist

Firts ฉันทำการทดสอบโดยใช้ค่าคงที่ซึ่งทำให้ฉันมีเวลารวมถึงลูป นี่เป็นเวลา 'เปล่า' ไม่รวมการเข้าถึงจริง จากนั้นฉันทำการทดสอบด้วยการเข้าถึงโครงสร้างเรื่องสิ่งนี้ทำให้ฉันและ 'รวมค่าใช้จ่าย' เวลาลูปและการเข้าถึงจริง

ความแตกต่างระหว่างการกำหนดเวลาแบบ 'เปล่า' และการกำหนดเวลาแบบ 'เหนือศีรษะ' ทำให้ฉันบ่งบอกถึงการกำหนดเวลาแบบ 'เข้าถึงโครงสร้าง'

แต่เวลานี้แม่นยำแค่ไหน? ในระหว่างการทดสอบหน้าต่างจะทำการแบ่งเวลาเพื่อ shure ฉันไม่มีข้อมูลเกี่ยวกับการแบ่งเวลา แต่ฉันเชื่อว่ามีการกระจายอย่างเท่าเทียมกันในระหว่างการทดสอบและตามลำดับเป็นสิบมิลลิวินาทีซึ่งหมายความว่าความแม่นยำสำหรับการกำหนดเวลาควรอยู่ในลำดับ +/- 100 msec หรือมากกว่านั้น ประมาณการคร่าวๆ? อย่างไรก็ตามแหล่งที่มาของข้อผิดพลาดที่เป็นระบบ

นอกจากนี้ยังทำการทดสอบในโหมด 'Debug' โดยไม่มีการปรับให้เหมาะสม มิฉะนั้นคอมไพเลอร์อาจเปลี่ยนรหัสทดสอบจริง

ดังนั้นฉันได้รับผลลัพธ์สองรายการหนึ่งรายการสำหรับค่าคงที่ทำเครื่องหมาย '(c)' และอีกรายการหนึ่งสำหรับการเข้าถึงที่มีเครื่องหมาย '(n)' และความแตกต่าง 'dt' จะบอกฉันว่าใช้เวลาเข้าถึงจริงเท่าใด

และนี่คือผลลัพธ์ที่ได้:

          Dictionary(c)/for: 1205ms (600000000)
          Dictionary(n)/for: 8046ms (589725196)
 dt = 6841

                List(c)/for: 1186ms (1189725196)
                List(n)/for: 2475ms (1779450392)
 dt = 1289

               Array(c)/for: 1019ms (600000000)
               Array(n)/for: 1266ms (589725196)
 dt = 247

 Dictionary[key](c)/foreach: 2738ms (600000000)
 Dictionary[key](n)/foreach: 10017ms (589725196)
 dt = 7279

            List(c)/foreach: 2480ms (600000000)
            List(n)/foreach: 2658ms (589725196)
 dt = 178

           Array(c)/foreach: 1300ms (600000000)
           Array(n)/foreach: 1592ms (589725196)
 dt = 292


 dt +/-.1 sec   for    foreach
 Dictionary     6.8       7.3
 List           1.3       0.2
 Array          0.2       0.3

 Same test, different system:
 dt +/- .1 sec  for    foreach
 Dictionary     14.4   12.0
       List      1.7    0.1
      Array      0.5    0.7

ด้วยการประมาณการที่ดีขึ้นเกี่ยวกับข้อผิดพลาดเกี่ยวกับเวลา (จะลบข้อผิดพลาดการวัดอย่างเป็นระบบเนื่องจากการแบ่งเวลาได้อย่างไร) อาจกล่าวเพิ่มเติมเกี่ยวกับผลลัพธ์ได้

ดูเหมือนว่ารายการ / foreach มีการเข้าถึงที่เร็วที่สุด แต่ค่าใช้จ่ายถูกฆ่า

ความแตกต่างระหว่าง List / for และ List / foreach คือ stange อาจจะมีบาง cashing เกี่ยวข้อง?

นอกจากนี้สำหรับการเข้าถึงอาร์เรย์มันไม่สำคัญว่าคุณจะใช้การforวนซ้ำหรือการforeachวนซ้ำ ผลลัพธ์เวลาและความแม่นยำทำให้ผลลัพธ์ 'เปรียบเทียบได้'

การใช้พจนานุกรมนั้นช้าที่สุดฉันคิดเพียงเพราะทางด้านซ้าย (ตัวทำดัชนี) ฉันมีรายการจำนวนเต็มเล็กน้อยและไม่ใช่ช่วงที่ใช้ในการทดสอบนี้

นี่คือรหัสทดสอบที่แก้ไข

Dictionary<int, int> dict = new Dictionary<int, int>(6000000);
List<int> list = new List<int>(6000000);
Random rand = new Random(12345);
for (int i = 0; i < 6000000; i++)
{
    int n = rand.Next(5000);
    dict.Add(i, n);
    list.Add(n);
}
int[] arr = list.ToArray();

int chk = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // dict[i];
    }
}
watch.Stop();
long c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = dict.Count;
    for (int i = 0; i < len; i++)
    {
        chk += dict[i];
    }
}
watch.Stop();
long n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("         Dictionary(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += 1; // list[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(c)/for: {0}ms ({1})", c_dt, chk);

watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    int len = list.Count;
    for (int i = 0; i < len; i++)
    {
        chk += list[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("               List(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += 1; // arr[i];
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("              Array(c)/for: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    for (int i = 0; i < arr.Length; i++)
    {
        chk += arr[i];
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/for: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += 1; // dict[i]; ;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in dict.Keys)
    {
        chk += dict[i]; ;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Dictionary[key](n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in list)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("           List(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += 1; // i;
    }
}
watch.Stop();
c_dt = watch.ElapsedMilliseconds;
Console.WriteLine("          Array(c)/foreach: {0}ms ({1})", c_dt, chk);

chk = 0;
watch = Stopwatch.StartNew();
for (int rpt = 0; rpt < 100; rpt++)
{
    foreach (int i in arr)
    {
        chk += i;
    }
}
watch.Stop();
n_dt = watch.ElapsedMilliseconds;
Console.WriteLine("Array(n)/foreach: {0}ms ({1})", n_dt, chk);
Console.WriteLine("dt = {0}", n_dt - c_dt);

0

ในการทดสอบสั้น ๆ บางอย่างฉันพบว่าการรวมกันของทั้งสองจะดีกว่าในสิ่งที่ฉันจะเรียกว่าคณิตศาสตร์เข้มข้นมากพอสมควร:

ประเภท: List<double[]>

เวลา: 00: 00: 05.1861300

ประเภท: List<List<double>>

เวลา: 00: 00: 05.7941351

ประเภท: double[rows * columns]

เวลา: 00: 00: 06.0547118

ใช้รหัส:

int rows = 10000;
int columns = 10000;

IMatrix Matrix = new IMatrix(rows, columns);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();


for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] = Math.E;

for (int r = 0; r < Matrix.Rows; r++)
    for (int c = 0; c < Matrix.Columns; c++)
        Matrix[r, c] *= -Math.Log(Math.E);


stopwatch.Stop();
TimeSpan ts = stopwatch.Elapsed;

Console.WriteLine(ts.ToString());

ฉันหวังว่าเราจะมีเมทริกซ์คลาสเร่งฮาร์ดแวร์ชั้นยอดอย่างทีม. NET ทำกับSystem.Numerics.Vectorsคลาส!

C # อาจเป็นภาษา ML ที่ดีที่สุดโดยมีงานเพิ่มขึ้นอีกเล็กน้อย!

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