C #에서 다차원 배열 double[,]
과 배열 배열의 차이점은 무엇입니까 double[][]
?
차이가있는 경우 각각에 가장 적합한 사용법은 무엇입니까?
답변
배열 배열 (들쭉날쭉 한 배열)은 다차원 배열보다 빠르며보다 효과적으로 사용할 수 있습니다. 다차원 배열의 구문이 더 좋습니다.
들쭉날쭉 한 배열과 다차원 배열을 사용하여 간단한 코드를 작성한 다음 IL 디스어셈블러를 사용하여 컴파일 된 어셈블리를 검사하면 들쭉날쭉 한 (또는 1 차원) 배열의 저장 및 검색이 단순한 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]
들쭉날쭉 한 배열에서 값 을 찾는 방법 var jagged = new int[10][5]
은 다음과 같습니다. 인덱스 3에서 요소를 찾고 (배열) 해당 배열에서 인덱스 6에서 요소를 찾습니다 (값). 이 경우 각 차원마다 추가 조회가 있습니다 (고가의 메모리 액세스 패턴).
다차원 배열은 메모리에 선형으로 배치되며 실제 값은 인덱스를 곱하여 구합니다. 그러나 array가 주어지면 다차원 배열 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
첫 번째 행은 들쭉날쭉 한 배열의 타이밍이고, 두 번째 행은 다차원 배열과 세 번째 행을 보여줍니다. 프로그램은 아래와 같습니다. 참고로 모노로 테스트했습니다. (Windows 타이밍은 주로 CLR 구현 변형으로 인해 크게 다릅니다).
윈도우에서 들쭉날쭉 한 배열의 타이밍은 다차원 배열이 어떻게 생겼는지에 대한 내 자신의 해석과 거의 동일합니다 .’Single () ‘을 참조하십시오. 안타깝게도 Windows JIT 컴파일러는 정말 어리 석고 불행히도 이러한 성능 토론을 어렵게 만들고 불일치가 너무 많습니다.
이것들은 내가 창문에 얻은 타이밍입니다. 여기서 같은 거래입니다. 첫 번째 행은 들쭉날쭉 한 배열, 두 번째 다차원 및 세 번째 다차원의 자체 구현입니다. 모노에 비해 창에서 이것이 얼마나 느린 지 주목하십시오.
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의 테이블과 유사합니다.
배열 배열 (들쭉날쭉 한 배열)을 사용하면 각 요소가 동일한 유형의 가변 길이의 다른 배열을 보유하게 할 수 있습니다.
따라서 데이터 구조가 테이블 (고정 행 / 열)처럼 보이는 경우 다차원 배열을 사용할 수 있습니다. 들쭉날쭉 한 배열은 고정 요소이며 각 요소는 가변 길이의 배열을 보유 할 수 있습니다
예 : Psuedocode :
int[,] data = new int[2,2];
data[0,0] = 1;
data[0,1] = 2;
data[1,0] = 3;
data[1,1] = 4;
위의 내용을 2×2 테이블로 생각하십시오.
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 설치에서 eax 및 edx에 저장된 인덱스를 사용하여 ecx가 가리키는 2 차원 배열에 저장된 요소 (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]
여기에서 메소드 호출로 인한 오버 헤드가 없음을 알 수 있습니다. 0이 아닌 인덱스의 가능성으로 인해 경계 검사가 매우 복잡합니다. 0이 아닌 경우 sub, cmp 및 jmps를 제거하면 코드가 거의 해결됩니다 (x*y_max+y)*sizeof(ptr)+sizeof(array_header)
. 이 계산은 요소에 대한 임의 액세스를위한 다른 것만 큼 빠릅니다 (하나의 곱셈은 시프트로 대체 될 수 있습니다. 이것이 2 비트의 거듭 제곱으로 바이트 크기를 선택하는 전체 이유이기 때문입니다).
또 다른 문제는 최신 컴파일러가 단일 차원 배열을 반복하면서 요소 액세스에 대한 중첩 경계 검사를 최적화하는 경우가 많다는 것입니다. 결과는 기본적으로 배열의 연속 메모리보다 인덱스 포인터를 앞당기는 코드입니다. 다차원 배열에 대한 순진한 반복에는 일반적으로 중첩 된 논리의 추가 계층이 포함되므로 컴파일러가 작업을 최적화 할 가능성이 적습니다. 따라서 단일 요소에 액세스하는 범위 검사 오버 헤드가 배열 차원 및 크기와 관련하여 일정한 런타임으로 바뀌더라도 차이를 측정하는 간단한 테스트 사례를 실행하는 데 여러 시간이 걸릴 수 있습니다.
답변
.NET Core의 다차원 배열이 들쭉날쭉 한 배열보다 빠르기 때문에 이것을 업데이트하고 싶습니다 . 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 개의 명령
왜 1 차원 배열이 여전히 다차원보다 더 빠른지 식별 할 수 없었지만 CPU에 대한 일부 최적화와 관련이 있다고 생각합니다.
답변
다차원 배열은 (n-1) 차원 행렬입니다.
그래서 int[,] square = new int[2,2]
정방 행렬 × 2이며, int[,,] cube = new int [3,3,3]
정방 행렬 × 3 – 큐브입니다. 비례는 필요하지 않습니다.
들쭉날쭉 한 배열은 배열의 배열입니다. 각 셀에 배열이 포함 된 배열입니다.
따라서 MDA는 비례 적이며 JD는 그렇지 않을 수 있습니다! 각 셀은 임의 길이의 배열을 포함 할 수 있습니다!
답변
이것은 위의 답변에서 언급되었지만 명시 적으로 언급되지 않았을 수 있습니다. 들쭉날쭉 한 배열을 사용 array[row]
하면 전체 데이터 행을 참조하는 데 사용할 수 있지만 다중 d 배열에는 허용되지 않습니다.