Pergunta

Basicamente, você tem duas maneiras para fazer isso:

for (int x = 0; x < UPPER_X; x++)
    for (int y = 0; y < UPPER_Y; y++)
    {        
        arr1[x, y] = get_value();
        arr2[y, x] = get_value();
    }

A única diferença é o que variável a mudança no loop interno: primeiro ou segundo. Ouvi dizer que os resultados diferem de língua para língua.

O que é ordem certa para .NET?

Foi útil?

Solução

A única razão é mais rápido do que o outro tem a ver com o cache do processador, e da forma como os dados são dispostos em memória.

Existem duas maneiras normais para armazenar os dados bidimensionais está no espaço de endereçamento unidimensional, ou você pode armazenar todos os dados para a primeira linha, em seguida, a segunda linha, e assim por diante (aka remar grande encomenda ), ou você pode fazê-lo por colunas (aka < a href = "http://en.wikipedia.org/wiki/Row-major_order#Column-major_order" rel = "nofollow noreferrer"> grande ordem da coluna ). Abaixo está o que as posições de memória seria para uma matriz 3x3 para ambas as opções.

Linhas:

1 2 3
4 5 6
7 8 9

Colunas:

1 4 7
2 5 8
3 6 9

Quando você acessar um local de memória, uma linha de cache inteira (que pode ser entre 8 e 512 bytes, de acordo com a Wikipedia) é carregado no cache. Então, se você acessar o próximo local de memória que já estará no cache. Assim, ele pode ser muito mais rápido para acesso sequencialmente memória do que a pular no espaço de endereço. Assim, com grandes matrizes bidimensionais, pode haver uma diferença de velocidade significativa entre a escolha de linhas ou colunas como a variável de loop interno.

Outras dicas

Você deve referência suas circunstâncias particulares para ter certeza.

Você poderia pensar que não haveria diferença para matrizes retangulares (memória ou seja, de forma contígua alocados), mas de acordo com esta artigo MSDN há uma diferença:

Você pode obter ainda melhores resultados por a conversão de uma matriz multidimensional em uma matriz de uma só dimensão. E se você não se importa a sintaxe, isto pode ser trivial; basta usar um índice como um Deslocamento. Por exemplo, a seguir declara uma matriz unidimensional para ser utilizado como uma matriz bidimensional:

double[] myArray = new double[ROW_DIM * COLUMN_DIM];

Para indexar os elementos desta matriz, use o seguinte offset:

myArray[row * COLUMN_DIM + column];

Este será sem dúvida mais rápido do que um equivalente irregulares ou rectangular array.

Então eu fiz o ponto de referência e os resultados são que o acesso à arr1 é três vezes mais rápido.

Muito interessante, eu nunca parou para pensar que pode ser uma enorme diferença tão simplesmente acessando os índices de matriz "não sequencial". Eu dei-lhe uma tentativa mim mesmo, e também encontrou o seguinte código teve o segundo ciclo, tendo entre 2-3 vezes mais:

// Hmmm, how to insert blank lines in the code formatter???
static void Main(string[] args)
{
    Stopwatch timer = new Stopwatch();
    int arraySize = 10000;

    // First array, access X by Y
    int[,] xbyy = new int[arraySize, arraySize];


    timer.Start();
    for (int x = 0; x < arraySize; x++)
        for (int y = 0; y < arraySize; y++)
        {
            xbyy[x, y] = 15;
        }
    timer.Stop();
    TimeSpan duration = timer.Elapsed;
    string realTimeFormat = string.Format("{0:00} minutes {1:00} seconds {2:000} milliseconds",
                    duration.Minutes, duration.Seconds, duration.Milliseconds);
    Console.WriteLine("X by Y took " + realTimeFormat);



    // Seecond array, access Y by X
    int[,] ybyx = new int[arraySize, arraySize];

    timer.Start();
    for (int x = 0; x < arraySize; x++)
        for (int y = 0; y < arraySize; y++)
        {
            ybyx[y, x] = 15;
        }
    timer.Stop();
    duration = timer.Elapsed;
    realTimeFormat = string.Format("{0:00} minutes {1:00} seconds {2:000} milliseconds",
                duration.Minutes, duration.Seconds, duration.Milliseconds);
    Console.WriteLine("Y by X took " + realTimeFormat);


    Console.ReadLine();
}

Para manter as coisas curto, aqui estão os trechos de IL emitidos para o X pelo circuito Y e Y por X loop.

código inicial antes de looping,

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       290 (0x122)
  .maxstack  4
  .locals init ([0] class [System]System.Diagnostics.Stopwatch timer,
           [1] int32 arraySize,
           [2] int32[0...,0...] xbyy,
           [3] int32 x,
           [4] int32 y,
           [5] valuetype [mscorlib]System.TimeSpan duration,
           [6] string realTimeFormat,
           [7] int32[0...,0...] ybyx,
           [8] int32 V_8,
           [9] int32 V_9)
  IL_0000:  newobj     instance void [System]System.Diagnostics.Stopwatch::.ctor()
  IL_0005:  stloc.0
  IL_0006:  ldc.i4     0x2710
  IL_000b:  stloc.1

looping X por Y

  IL_000c:  ldloc.1
  IL_000d:  ldloc.1
  IL_000e:  newobj     instance void int32[0...,0...]::.ctor(int32,
                                                             int32)
  IL_0013:  stloc.2
  IL_0014:  ldloc.0
  IL_0015:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_001a:  ldc.i4.0
  IL_001b:  stloc.3
  IL_001c:  br.s       IL_003d
  IL_001e:  ldc.i4.0
  IL_001f:  stloc.s    y
  IL_0021:  br.s       IL_0034
  IL_0023:  ldloc.2
  IL_0024:  ldloc.3
  IL_0025:  ldloc.s    y
  IL_0027:  ldc.i4.s   15
  IL_0029:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_002e:  ldloc.s    y
  IL_0030:  ldc.i4.1
  IL_0031:  add
  IL_0032:  stloc.s    y
  IL_0034:  ldloc.s    y
  IL_0036:  ldloc.1
  IL_0037:  blt.s      IL_0023
  IL_0039:  ldloc.3
  IL_003a:  ldc.i4.1
  IL_003b:  add
  IL_003c:  stloc.3
  IL_003d:  ldloc.3
  IL_003e:  ldloc.1
  IL_003f:  blt.s      IL_001e
  IL_0041:  ldloc.0

looping Y por X

  IL_0090:  ldloc.1
  IL_0091:  ldloc.1
  IL_0092:  newobj     instance void int32[0...,0...]::.ctor(int32,
                                                             int32)
  IL_0097:  stloc.s    ybyx
  IL_0099:  ldloc.0
  IL_009a:  callvirt   instance void [System]System.Diagnostics.Stopwatch::Start()
  IL_009f:  ldc.i4.0
  IL_00a0:  stloc.s    V_8
  IL_00a2:  br.s       IL_00c7
  IL_00a4:  ldc.i4.0
  IL_00a5:  stloc.s    V_9
  IL_00a7:  br.s       IL_00bc
  IL_00a9:  ldloc.s    ybyx
  IL_00ab:  ldloc.s    V_9
  IL_00ad:  ldloc.s    V_8
  IL_00af:  ldc.i4.s   15
  IL_00b1:  call       instance void int32[0...,0...]::Set(int32,
                                                           int32,
                                                           int32)
  IL_00b6:  ldloc.s    V_9
  IL_00b8:  ldc.i4.1
  IL_00b9:  add
  IL_00ba:  stloc.s    V_9
  IL_00bc:  ldloc.s    V_9
  IL_00be:  ldloc.1
  IL_00bf:  blt.s      IL_00a9
  IL_00c1:  ldloc.s    V_8
  IL_00c3:  ldc.i4.1
  IL_00c4:  add
  IL_00c5:  stloc.s    V_8
  IL_00c7:  ldloc.s    V_8
  IL_00c9:  ldloc.1
  IL_00ca:  blt.s      IL_00a4
  IL_00cc:  ldloc.0

O fluxo lógico IL é um pouco semelhante. A principal diferença é que pode-se observar a primeira espira consegue usar stloc e ldloc para as posições 2 e 3 para o primeiro conjunto e o índice variável X. No momento em que ele veio para o segundo ciclo, que tinha gasto o maxstack e, portanto, utilizado os stloc.s e instruções ldloc.s. Eu acredito que esta é a diferença entre variáveis ??referenciando na pilha contra na pilha, e contribuindo para o desempenho mais lento.

Agora, se você trocar a ordem em que os laços são testados, para que o Y por X laço é executado primeiro a referências pilha de acesso, você vai ver as durações de tempo se inverteu.


UPDATE: Eu estava errado sobre como fazer referência pilha ou heap endereços. Parece apenas que os primeiros quatro variáveis ??em um método são mais eficientes para referência com o stloc.0 dedicado, 1, 3, 4 e ldloc.0, 1, 3, 4 opcodes.

http://weblogs.asp.net/mnolton /archive/2004/01/09/48992.aspx

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top