อะไรคือความแตกต่างระหว่างอาร์เรย์หลายมิติ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> = int32
Let
1) int32[...,...]
เป็นอาร์เรย์สองมิติของขอบเขตและขนาดที่ต่ำกว่าที่ไม่ได้กำหนด
2) int32[2...5]
เป็นอาร์เรย์หนึ่งมิติของขอบเขตล่าง 2 และขนาด 4
3) int32[0...,0...]
เป็นอาร์เรย์สองมิติของขอบเขตที่ต่ำกว่า 0 และขนาดที่ไม่ได้กำหนด
ทอม
double[,]
คืออาร์เรย์สี่เหลี่ยมในขณะที่double[][]
เรียกว่า "อาร์เรย์ขรุขระ" แรกจะมี "คอลัมน์" จำนวนเท่ากันสำหรับแต่ละแถวในขณะที่สองจะ (อาจ) มีจำนวน "คอลัมน์" ที่แตกต่างกันสำหรับแต่ละแถว