อะไรคือความแตกต่างระหว่างอาร์เรย์หลายมิติdouble[,]และอาร์เรย์ของอาร์เรย์double[][]ใน C #
หากมีความแตกต่างอะไรคือการใช้ที่ดีที่สุดสำหรับแต่ละคน?
อะไรคือความแตกต่างระหว่างอาร์เรย์หลายมิติdouble[,]และอาร์เรย์ของอาร์เรย์double[][]ใน C #
หากมีความแตกต่างอะไรคือการใช้ที่ดีที่สุดสำหรับแต่ละคน?
คำตอบ:
Array of Array (Jagged Array) นั้นเร็วกว่าอาเรย์หลายมิติและสามารถใช้งานได้อย่างมีประสิทธิภาพมากขึ้น อาร์เรย์หลายมิติมีไวยากรณ์ที่ดีกว่า
หากคุณเขียนโค้ดอย่างง่ายโดยใช้อาร์เรย์แบบ jagged และหลายมิติแล้วตรวจสอบชุดประกอบที่รวบรวมด้วย disassembler IL คุณจะเห็นว่าการจัดเก็บและเรียกข้อมูลจากอาร์เรย์แบบขรุขระ (หรือมิติเดียว) เป็นคำสั่ง IL แบบง่ายในขณะที่การดำเนินการเดียวกัน การเรียกใช้ที่ช้ากว่าเสมอ
พิจารณาวิธีการต่อไปนี้:
static void SetElementAt(int[][] array, int i, int j, int value)
{
    array[i][j] = value;
}
static void SetElementAt(int[,] array, int i, int j, int value)
{
    array[i, j] = value;
}
IL ของพวกเขาจะเป็นดังต่อไปนี้:
.method private hidebysig static void  SetElementAt(int32[][] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       7 (0x7)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldelem.ref
  IL_0003:  ldarg.2
  IL_0004:  ldarg.3
  IL_0005:  stelem.i4
  IL_0006:  ret
} // end of method Program::SetElementAt
.method private hidebysig static void  SetElementAt(int32[0...,0...] 'array',
                                                    int32 i,
                                                    int32 j,
                                                    int32 'value') cil managed
{
  // Code size       10 (0xa)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldarg.1
  IL_0002:  ldarg.2
  IL_0003:  ldarg.3
  IL_0004:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_0009:  ret
} // end of method Program::SetElementAt
เมื่อใช้อาร์เรย์แบบขรุขระคุณสามารถดำเนินการเช่นการสลับแถวและการปรับขนาดแถวได้อย่างง่ายดาย บางทีในบางกรณีการใช้อาร์เรย์หลายมิติจะปลอดภัยกว่า แต่ Microsoft FxCop บอกว่าควรใช้อาร์เรย์ที่ขรุขระแทนมิติหลายมิติเมื่อคุณใช้เพื่อวิเคราะห์โครงการของคุณ
อาเรย์หลายมิติสร้างเลย์เอาต์หน่วยความจำเชิงเส้นที่ดีในขณะที่อาเรย์แบบหยักมีความหมายหลายระดับ
การค้นหาค่าjagged[3][6]ในอาเรย์ jagged จะvar jagged = new int[10][5]ทำงานดังนี้: ค้นหาอิลิเมนต์ที่ดัชนี 3 (ซึ่งเป็นอาเรย์) และค้นหาอิลิเมนต์ที่ดัชนี 6 ในอาเรย์นั้น (ซึ่งเป็นค่า) สำหรับแต่ละมิติในกรณีนี้มีการค้นหาเพิ่มเติม (นี่เป็นรูปแบบการเข้าถึงหน่วยความจำราคาแพง)
อาเรย์หลายมิติถูกวางไว้ในหน่วยความจำเชิงเส้นซึ่งจะพบค่าจริงโดยการคูณดัชนีเข้าด้วยกัน อย่างไรก็ตามเมื่อกำหนดอาร์เรย์var mult = new int[10,30]แล้วLengthคุณสมบัติของอาร์เรย์หลายมิตินั้นจะคืนค่าจำนวนองค์ประกอบทั้งหมดเช่น 10 * 30 = 300
Rankทรัพย์สินของอาร์เรย์ที่ขรุขระเป็นเสมอ 1 แต่อาร์เรย์หลายมิติสามารถมีตำแหน่งใด ๆ GetLengthวิธีการของอาร์เรย์ใด ๆ ที่สามารถนำมาใช้เพื่อให้ได้ความยาวของแต่ละมิติ สำหรับอาร์เรย์หลายมิติในตัวอย่างนี้mult.GetLength(1)ส่งคืน 30
การจัดทำดัชนีอาร์เรย์หลายมิตินั้นเร็วกว่า เช่นกำหนดอาเรย์หลายมิติในตัวอย่างนี้mult[1,7]= 30 * 1 + 7 = 37 รับอิลิเมนต์ที่ดัชนีนั้น 37 นี่เป็นรูปแบบการเข้าถึงหน่วยความจำที่ดีกว่าเนื่องจากมีที่ตั้งหน่วยความจำเพียงแห่งเดียวซึ่งเป็นที่อยู่พื้นฐานของอาเรย์
อาร์เรย์หลายมิติจึงจัดสรรบล็อกหน่วยความจำอย่างต่อเนื่องในขณะที่อาร์เรย์แบบขรุขระไม่จำเป็นต้องเป็นรูปสี่เหลี่ยมเช่นjagged[1].Lengthไม่จำเป็นต้องเท่ากันjagged[2].Lengthซึ่งจะเป็นจริงสำหรับอาร์เรย์หลายมิติใด ๆ
ประสิทธิภาพที่ชาญฉลาดอาร์เรย์หลายมิติควรเร็วขึ้น เร็วกว่ามาก แต่เนื่องจากการใช้ CLR ที่ไม่ดีพวกเขาไม่ได้เป็นเช่นนั้น
 23.084  16.634  15.215  15.489  14.407  13.691  14.695  14.398  14.551  14.252 
 25.782  27.484  25.711  20.844  19.607  20.349  25.861  26.214  19.677  20.171 
  5.050   5.085   6.412   5.225   5.100   5.751   6.650   5.222   6.770   5.305 
แถวแรกคือการกำหนดเวลาของอาร์เรย์แบบขรุขระส่วนที่สองแสดงอาร์เรย์แบบหลายมิติและแถวที่สามนั่นคือสิ่งที่ควรจะเป็น โปรแกรมจะแสดงด้านล่าง FYI นี้ได้รับการทดสอบการทำงานโมโน (การกำหนดเวลา windows แตกต่างกันอย่างมากส่วนใหญ่เกิดจากรูปแบบการปรับใช้ CLR)
บน windows การกำหนดเวลาของอาร์เรย์แบบขรุขระนั้นเหนือกว่าอย่างมากเช่นเดียวกับการตีความของฉันเองเกี่ยวกับสิ่งที่อาร์เรย์หลายมิติควรดูให้ดู 'Single ()' น่าเศร้าที่คอมโพสิตของ JIT ของ windows นั้นโง่จริงๆและสิ่งนี้ทำให้การพูดคุยเรื่องประสิทธิภาพทำได้ยากมีความไม่สอดคล้องกันมากเกินไป
นี่คือการกำหนดเวลาที่ฉันได้รับบน windows ข้อตกลงเดียวกันที่นี่แถวแรกเป็นแถวขรุขระหลายมิติที่สองและที่สามของฉันในการนำมิติหลายมิติมาใช้
  8.438   2.004   8.439   4.362   4.936   4.533   4.751   4.776   4.635   5.864
  7.414  13.196  11.940  11.832  11.675  11.811  11.812  12.964  11.885  11.751
 11.355  10.788  10.527  10.541  10.745  10.723  10.651  10.930  10.639  10.595
รหัสแหล่งที่มา:
using System;
using System.Diagnostics;
static class ArrayPref
{
    const string Format = "{0,7:0.000} ";
    static void Main()
    {
        Jagged();
        Multi();
        Single();
    }
    static void Jagged()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var jagged = new int[dim][][];
            for(var i = 0; i < dim; i++)
            {
                jagged[i] = new int[dim][];
                for(var j = 0; j < dim; j++)
                {
                    jagged[i][j] = new int[dim];
                    for(var k = 0; k < dim; k++)
                    {
                        jagged[i][j][k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }
    static void Multi()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var multi = new int[dim,dim,dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        multi[i,j,k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }
    static void Single()
    {
        const int dim = 100;
        for(var passes = 0; passes < 10; passes++)
        {
            var timer = new Stopwatch();
            timer.Start();
            var single = new int[dim*dim*dim];
            for(var i = 0; i < dim; i++)
            {
                for(var j = 0; j < dim; j++)
                {
                    for(var k = 0; k < dim; k++)
                    {
                        single[i*dim*dim+j*dim+k] = i * j * k;
                    }
                }
            }
            timer.Stop();
            Console.Write(Format,
                (double)timer.ElapsedTicks/TimeSpan.TicksPerMillisecond);
        }
        Console.WriteLine();
    }
}
              เพียงแค่ใส่อาเรย์หลายมิติคล้ายกับตารางใน DBMS 
Array of Array (jagged array) ช่วยให้คุณแต่ละองค์ประกอบมีอาเรย์อื่นที่มีความยาวตัวแปรชนิดเดียวกัน
ดังนั้นหากคุณแน่ใจว่าโครงสร้างข้อมูลดูเหมือนตาราง (แถวคงที่ / คอลัมน์) คุณสามารถใช้อาร์เรย์หลายมิติได้ อาร์เรย์ Jagged เป็นองค์ประกอบคงที่และแต่ละองค์ประกอบสามารถเก็บอาร์เรย์ความยาวของตัวแปรได้
เช่น Psuedocode:
int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;
คิดว่าข้างต้นเป็นตาราง 2x2:
1 | 2 3 | 4
int[][] jagged = new int[3][]; 
jagged[0] = new int[4] {  1,  2,  3,  4 }; 
jagged[1] = new int[2] { 11, 12 }; 
jagged[2] = new int[3] { 21, 22, 23 }; 
คิดว่าข้างต้นเป็นแต่ละแถวที่มีจำนวนคอลัมน์แปรผัน:
1 | 2 | 3 | 4 11 | 12 21 | 22 | 23
คำนำ:ความคิดเห็นนี้มีจุดประสงค์เพื่อแก้ไขคำตอบที่ได้รับจาก okutaneแต่เนื่องจากระบบชื่อเสียงที่แย่มากของ SO ฉันจึงไม่สามารถโพสต์มันได้
การยืนยันของคุณว่าอันใดอันหนึ่งช้ากว่าอีกอันหนึ่งเนื่องจากวิธีการโทรไม่ถูกต้อง หนึ่งช้ากว่าอีกอันหนึ่งเนื่องจากอัลกอริธึมการตรวจสอบขอบเขตที่ซับซ้อนมากขึ้น คุณสามารถตรวจสอบสิ่งนี้ได้อย่างง่ายดายโดยดูที่ IL ไม่ใช่ แต่เป็นการรวบรวมที่รวบรวม ตัวอย่างเช่นในการติดตั้ง 4.5 ของฉันการเข้าถึงองค์ประกอบ (ผ่านตัวชี้ใน edx) เก็บไว้ในอาร์เรย์สองมิติที่ชี้ไปโดย ecx โดยมีดัชนีที่จัดเก็บใน eax และ edx มีลักษณะดังนี้:
sub eax,[ecx+10]
cmp eax,[ecx+08]
jae oops //jump to throw out of bounds exception
sub edx,[ecx+14]
cmp edx,[ecx+0C]
jae oops //jump to throw out of bounds exception
imul eax,[ecx+0C]
add eax,edx
lea edx,[ecx+eax*4+18]
ที่นี่คุณจะเห็นว่าไม่มีค่าใช้จ่ายจากการเรียกใช้เมธอด การตรวจสอบขอบเขตนั้นซับซ้อนมากเนื่องจากความเป็นไปได้ของดัชนีที่ไม่ใช่ศูนย์ ถ้าเราเอาย่อย cmp และ jmps (x*y_max+y)*sizeof(ptr)+sizeof(array_header)สำหรับที่ไม่ใช่ศูนย์กรณีรหัสสวยมากหายไป การคำนวณนี้เร็วมาก (หนึ่งการคูณสามารถถูกแทนที่ด้วยกะเนื่องจากนั่นเป็นเหตุผลทั้งหมดที่เราเลือกไบต์ให้มีขนาดเป็นพลังของสองบิต) เป็นอย่างอื่นสำหรับการเข้าถึงองค์ประกอบแบบสุ่ม  
ภาวะแทรกซ้อนอีกอย่างคือมีหลายกรณีที่คอมไพเลอร์สมัยใหม่จะปรับการตรวจสอบขอบเขตที่ซ้อนกันให้เหมาะสมสำหรับการเข้าถึงองค์ประกอบในขณะที่วนซ้ำแถวลำดับขนาดเดียว ผลลัพธ์คือโค้ดที่โดยทั่วไปแล้วจะเลื่อนตัวชี้ดัชนีไปยังหน่วยความจำต่อเนื่องของอาร์เรย์ การทำซ้ำแบบไร้เดียงสาในอาร์เรย์หลายมิติโดยทั่วไปเกี่ยวข้องกับเลเยอร์พิเศษของตรรกะที่ซ้อนกันดังนั้นคอมไพเลอร์จึงมีโอกาสน้อยที่จะปรับการทำงานให้เหมาะสม ดังนั้นแม้ว่าค่าใช้จ่ายในการตรวจสอบขอบเขตของการเข้าถึงองค์ประกอบเดียวจะลดค่าใช้จ่ายในการรันไทม์ที่คงที่เมื่อเทียบกับขนาดและขนาดอาเรย์กรณีทดสอบอย่างง่ายเพื่อวัดความแตกต่างอาจใช้เวลานานกว่าในการดำเนินการ
ฉันต้องการที่จะปรับปรุงเกี่ยวกับเรื่องนี้เพราะใน.NET หลักอาร์เรย์หลายมิติจะเร็วกว่าอาร์เรย์ขรุขระ ฉันรันการทดสอบจากJohn Leidegrenและนี่คือผลลัพธ์ใน. NET Core 2.0 Preview 2 ฉันเพิ่มค่ามิติเพื่อให้ได้อิทธิพลจากแอปพื้นหลังที่มองเห็นได้น้อยลง
Debug (code optimalization disabled)
Running jagged 
187.232 200.585 219.927 227.765 225.334 222.745 224.036 222.396 219.912 222.737 
Running multi-dimensional  
130.732 151.398 131.763 129.740 129.572 159.948 145.464 131.930 133.117 129.342 
Running single-dimensional  
 91.153 145.657 111.974  96.436 100.015  97.640  94.581 139.658 108.326  92.931 
Release (code optimalization enabled)
Running jagged 
108.503 95.409 128.187 121.877 119.295 118.201 102.321 116.393 125.499 116.459 
Running multi-dimensional 
 62.292  60.627  60.611  60.883  61.167  60.923  62.083  60.932  61.444  62.974 
Running single-dimensional 
 34.974  33.901  34.088  34.659  34.064  34.735  34.919  34.694  35.006  34.796 
ฉันดูเป็นชิ้นส่วนและนี่คือสิ่งที่ฉันพบ
jagged[i][j][k] = i * j * k; ต้องการ 34 คำแนะนำในการดำเนินการ
multi[i, j, k] = i * j * k; ต้องการ 11 คำแนะนำในการดำเนินการ
single[i * dim * dim + j * dim + k] = i * j * k; ต้องการ 23 คำแนะนำในการดำเนินการ
ฉันไม่สามารถระบุสาเหตุที่อาร์เรย์แบบมิติเดียวยังคงเร็วกว่าแบบหลายมิติได้ แต่ฉันเดาว่ามันต้องทำอย่างไรกับการปรับให้เหมาะสมที่สุดที่ทำกับ CPU
อาร์เรย์หลายมิติคือเมทริกซ์ขนาด (n-1)
สint[,] square = new int[2,2]แควร์เมทริกซ์ 2x2 int[,,] cube = new int [3,3,3]คือลูกบาศก์ - สแควร์เมทริกซ์ 3x3 สัดส่วนไม่จำเป็นต้องใช้
Jagged Array เป็นเพียงอาร์เรย์ของอาเรย์ - อาเรย์ที่แต่ละเซลล์มีอาเรย์
ดังนั้น MDA จึงเป็นสัดส่วน JD อาจไม่ใช่! แต่ละเซลล์สามารถมีความยาวได้ตามต้องการ!
สิ่งนี้อาจถูกกล่าวถึงในคำตอบข้างต้น แต่ไม่ชัดเจน: ด้วย jagged array คุณสามารถใช้array[row]เพื่ออ้างถึงแถวข้อมูลทั้งหมด แต่ไม่อนุญาตให้ใช้สำหรับอาร์เรย์หลาย d
นอกเหนือจากคำตอบอื่น ๆ โปรดทราบว่าอาเรย์หลายมิติถูกจัดสรรเป็นวัตถุขนาดใหญ่ก้อนเดียวบนกอง สิ่งนี้มีความหมายบางอย่าง:
<gcAllowVeryLargeObjects>หลายมิติอาร์เรย์ก่อนที่ปัญหาจะเกิดขึ้นหากคุณเคยใช้อาร์เรย์แบบขรุขระเท่านั้นฉันแยกวิเคราะห์ไฟล์. il ที่สร้างโดย ildasm เพื่อสร้างฐานข้อมูลของ assemnblies, คลาส, เมธอดและโพรซีเดอร์ที่เก็บไว้เพื่อใช้ในการแปลง ฉันเจอสิ่งต่อไปนี้ซึ่งทำให้การแยกวิเคราะห์ของฉันแตก
.method private hidebysig instance uint32[0...,0...] 
        GenerateWorkingKey(uint8[] key,
                           bool forEncryption) cil managed
หนังสือ Expert .NET 2.0 IL Assembler โดย Serge Lidin, Apress ตีพิมพ์ในปี 2006 บทที่ 8 ประเภทดั้งเดิมและลายเซ็นหน้า 149-150 อธิบาย
<type>[]จะเรียกว่าเวกเตอร์<type>,
<type>[<bounds> [<bounds>**] ] ถูกเรียกว่าอาร์เรย์ของ <type>
**หมายถึงอาจจะทำซ้ำ[ ]หมายถึงตัวเลือก
ตัวอย่าง: <type> = int32Let
1) int32[...,...]เป็นอาร์เรย์สองมิติของขอบเขตและขนาดที่ต่ำกว่าที่ไม่ได้กำหนด
2) int32[2...5]เป็นอาร์เรย์หนึ่งมิติของขอบเขตล่าง 2 และขนาด 4
3) int32[0...,0...]เป็นอาร์เรย์สองมิติของขอบเขตที่ต่ำกว่า 0 และขนาดที่ไม่ได้กำหนด
ทอม
double[,]คืออาร์เรย์สี่เหลี่ยมในขณะที่double[][]เรียกว่า "อาร์เรย์ขรุขระ" แรกจะมี "คอลัมน์" จำนวนเท่ากันสำหรับแต่ละแถวในขณะที่สองจะ (อาจ) มีจำนวน "คอลัมน์" ที่แตกต่างกันสำหรับแต่ละแถว