web-dev-qa-db-fra.com

Ligne-majeur vs confusion colonne-majeur

J'ai beaucoup lu à ce sujet, plus je lisais, plus je devenais confus.

Ma compréhension: Dans row-major, les lignes sont stockées de manière contiguë en mémoire, dans column-major, les colonnes sont stockées de manière contiguë en mémoire Donc, si nous avons une séquence de nombres [1, ..., 9] et que nous voulons les stocker dans une matrice de lignes majeures, nous obtenons:

|1, 2, 3|
|4, 5, 6|
|7, 8, 9|

tandis que la colonne majeure (corrigez-moi si je me trompe) est:

|1, 4, 7|
|2, 5, 8|
|3, 6, 9|

qui est effectivement la transposée de la matrice précédente.

Ma confusion: je ne vois aucune différence. Si nous itérons à la fois sur les matrices (par des lignes dans la première et par des colonnes dans la seconde), nous couvrirons les mêmes valeurs dans le même ordre: 1, 2, 3, ..., 9

Même si la multiplication de matrice est la même, nous prenons les premiers éléments contigus et nous les multiplions avec les colonnes de la seconde matrice. Alors disons que nous avons la matrice M:

|1, 0, 4| 
|5, 2, 7| 
|6, 0, 0|

Si nous multiplions la matrice de rangée majeure précédente R par M, c'est R x M nous obtiendrons:

|1*1 + 2*0 + 3*4, 1*5 + 2*2 + 3*7, etc|
|etc.. |
|etc.. |

Si nous multiplions la matrice-colonne C par M, c'est-à-dire C x M en prenant les colonnes de C au lieu de ses lignes, nous obtenons exactement le même résultat à partir de R x M

Je suis vraiment confus, si tout est pareil, pourquoi ces deux termes existent-ils? Je veux dire, même dans la première matrice R, je pourrais regarder les lignes et les considérer comme des colonnes ...

Est-ce que je manque quelque chose? Qu'est-ce que rang-major vs col-majeur implique réellement dans mes mathématiques matricielles? J'ai toujours appris dans mes classes d'algèbre linéaire que nous multiplions les lignes de la première matrice par des colonnes de la seconde. Est-ce que cela change si la première matrice était en colonne majeure? Devons-nous maintenant multiplier ses colonnes avec des colonnes de la deuxième matrice, comme je l’ai fait dans mon exemple, ou est-ce que c’était tout simplement faux?

Toutes les clarifications sont vraiment appréciées!

EDIT: L’une des autres sources de confusion que je rencontre est GLM ... Je passe donc la souris sur son type de matrice et appuie sur F12 pour voir comment elle est mise en œuvre. Là, je vois un tableau de vecteurs. une matrice 3x3 nous avons un tableau de 3 vecteurs. En regardant le type de ces vecteurs que j'ai vu 'col_type', j'ai donc supposé que chacun de ces vecteurs représentait une colonne, et nous avons donc un système colonne-majeur, n'est-ce pas?

Eh bien, je ne sais pas pour être honnête. J'ai écrit cette fonction d'impression pour comparer ma matrice de traduction à celle de glm, je vois le vecteur de traduction dans glm à la dernière rangée et le mien à la dernière colonne ... 

 enter image description here

Cela n’ajoute que de la confusion. Vous pouvez clairement voir que chaque vecteur de la matrice glmTranslate représente une ligne dans la matrice. Donc ... ça veut dire que la matrice est en rangée majeure, n'est-ce pas? Qu'en est-il de ma matrice? (J'utilise un tableau float [16]). Les valeurs de traduction sont dans la dernière colonne. Cela signifie-t-il que ma matrice est une colonne majeure et que je ne l'ai pas maintenant? _ {essaie d'empêcher la tête de tourner

11
vexe

Je pense que vous mélangez un détail d'implémentation avec l'utilisation, si vous voulez. 

Commençons par un tableau ou une matrice à deux dimensions:

    | 1  2  3 |
    | 4  5  6 |
    | 7  8  9 |

Le problème est que la mémoire de l'ordinateur est un tableau d'octets unidimensionnel. Pour rendre notre discussion plus facile, groupons les octets simples en groupes de quatre, ainsi Nous avons quelque chose qui ressemble à ceci (chaque simple, + - + représente un octet, quatre systèmes d'exploitation à bits):

   -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
    |       |       |       |       |       |       |       |       |  
   -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
       \/                   \       /
      one byte               one integer

    low memory    ------>                          high memory

Une autre façon de représenter 

La question est donc de savoir comment mapper une structure à deux dimensions (notre matrice) sur cette structure à une dimension (c'est-à-dire la mémoire). Il y a deux façons de le faire.

  1. Ordre des lignes majeures: dans cet ordre, nous mettons la première ligne en mémoire en premier, puis la seconde, et ainsi de suite. En faisant cela, nous aurions en mémoire ce qui suit:

    -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |   1   |   2   |   3   |   4   |   5   |   6   |   7   |   8   |   9   |
    -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

Avec cette méthode, nous pouvons trouver un élément donné de notre tableau en effectuant l’arithmétique suivante. Supposons que nous voulions accéder à l'élément $ M_ {ij} $ du tableau. Si nous supposons que nous avons un pointeur sur le premier élément du tableau, disons ptr et connaissons le nombre de colonnes dites nCol, nous pouvons trouver n'importe quel élément en:

     $M_{ij} = i*nCol + j$ 

Pour voir comment cela fonctionne, considérons M_ {02} (c'est-à-dire la première ligne, la troisième colonne - rappelez-vous que C est basé sur zéro. 

      $M_{02} = 0*3 + 2 = 2

Nous avons donc accès au troisième élément du tableau.

  1. Ordre des principales colonnes: Dans cet ordre, nous mettons la première colonne en mémoire en premier, puis la seconde, et ainsi de suite. En faisant cela, nous aurions en mémoire ce qui suit:

    -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |   1   |   4   |   7   |   2   |   5   |   8   |   3   |   6   |   9   |
    -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    

SO, la réponse courte - format ligne-majeur et format-colonne-majeur décrit comment les tableaux à deux dimensions (ou plus) sont mappés dans un tableau de mémoire à une dimension.

J'espère que cela vous aidera . T.

9
thurizas

Tu as raison. Peu importe si un système stocke les données dans une structure à lignes principales ou à colonnes. C'est comme un protocole. Ordinateur: "Hé, humain. Je vais stocker votre tableau de cette façon. Pas de problème. Hein?" considérez les trois choses suivantes. 

1. la plupart des tableaux sont accessibles en ordre de rangée.

2. Lorsque vous accédez à la mémoire, elle n'est pas lue directement à partir de la mémoire. Vous stockez d’abord des blocs de données de la mémoire au cache, puis vous lisez les données du cache à votre processeur.

3. Si les données souhaitées n'existent pas dans le cache, celui-ci doit être extrait de nouveau de la mémoire

Lorsqu'un cache extrait des données de la mémoire, la localité est importante. En d’autres termes, si vous stockez des données de manière insuffisante dans la mémoire, votre cache doit extraire les données de la mémoire plus souvent. Cette action corrompt les performances de vos programmes car l'accès à la mémoire est beaucoup plus lent (plus de 100 fois!) Puis l'accès au cache. Moins vous accédez à la mémoire, plus votre programme est rapide. Ainsi, ce tableau de lignes majeures est plus efficace car l'accès à ses données est plus susceptible d'être local.

3
Y.C.Jung

Peu importe ce que vous utilisez: soyez juste cohérent!

Rangée majeure ou colonne majeure est juste une convention. Peu importe C utilise une ligne majeure, Fortran utilise une colonne. Les deux fonctionnent. Utilisez ce qui est standard dans votre langage/environnement de programmation.

Le désaccord entre les deux sera! @ # $

Si vous utilisez un adressage majeur de ligne sur une matrice stockée dans colum major, vous pouvez obtenir le mauvais élément, lire la fin du tableau, etc.

Row major: A(i,j) element is at A[j + i * n_columns];  <---- mixing these up will
Col major: A(i,j) element is at A[i + j * n_rows];     <---- make your code fubar

Il est incorrect de dire que le code pour faire la multiplication de matrice est le même pour la ligne principale et la colonne principale

(Bien sûr, le calcul de la multiplication de matrice est le même.) Imaginez que vous ayez deux tableaux en mémoire:

X = [x1, x2, x3, x4]    Y = [y1, y2, y3, y4]

Si les matrices sont stockées dans la colonne majeure, alors X, Y et X * Y sont:

IF COL MAJOR: [x1, x3  *  [y1, y3    =   [x1y1+x3y2, x1y3+x3y4
               x2, x4]     y2, y4]        x2y1+x4y2, x2y3+x4y4]

Si les matrices sont stockées dans la rangée principale, alors X, Y et X * Y sont:

IF ROW MAJOR:  [x1, x2    [y1, y2     = [x1y1+x2y3, x1y2+x2y4;
                x3, x4]    y3, y4]       x3y1+x4y3, x3y2+x4y4];

X*Y in memory if COL major   [x1y1+x3y2, x2y1+x4y2, x1y3+x3y4, x2y3+x4y4]
              if ROW major   [x1y1+x2y3, x1y2+x2y4, x3y1+x4y3, x3y2+x4y4]

Il n'y a rien de profond ici. Ce ne sont que deux conventions différentes. C'est comme mesurer en milles ou en kilomètres. Dans les deux cas, vous ne pouvez pas basculer entre les deux sans convertir!

2
Matthew Gunn

Ok, alors, étant donné que le mot "confusion" est littéralement dans le titre, je peux comprendre le niveau de ... confusion.

Tout d’abord, ceci est absolument un réel problème

Jamais, jamais succomber à l'idée que "il est utilisé soit, mais ... PC est de nos jours ..."

Les principaux problèmes sont les suivants: -Cache eviction strategy (LRU, FIFO, etc.) as @Y.C.Jung was beginning to touch on -Branch prediction -Pipelining (it's depth, etc) -Actual physical memory layout -Size of memory -Architecture of machine, (ARM, MIPS, Intel, AMD, Motorola, etc.)

Cette réponse portera sur l’architecture de Harvard, la machine Von Neumann telle qu’elle s’applique le mieux au PC actuel. 

La hiérarchie de la mémoire:

https://en.wikipedia.org/wiki/File:ComputerMemoryHierarchy.svgis

Est-ce une juxtaposition de coût contre vitesse.

Pour le système PC standard actuel, cela ressemblerait à quelque chose comme: SIZE: 500GB HDD > 8GB RAM > L2 Cache > L1 Cache > Registers. SPEED: 500GB HDD < 8GB RAM < L2 Cache < L1 Cache < Registers.

Cela conduit à l'idée de localité temporelle et spatiale. L'un signifie comment vos données sont organisées (code, ensemble de travail, etc.), l'autre signifie physiquement vos données sont organisées en "mémoire".

Etant donné que "la plupart" des PC actuels sont des machines {little-endian} (Intel), ils conservent les données en mémoire dans un ordre little-endian spécifique. Il diffère fondamentalement du big-endian.

https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Data/endian.html (le couvre plutôt ... swiftly;))

(Pour la simplicité de cet exemple, je vais «dire» que les choses se passent dans des entrées uniques, cela est incorrect, des blocs de cache entiers sont généralement consultés et varient considérablement de mon fabricant, beaucoup moins de modèle).

Donc, maintenant que nous en avons un exemple, si, hypothétiquement votre programme a demandé 1GB of data from your 500GB HDD, chargé dans votre 8GB of RAM,, puis dans la hiérarchie cache, puis finalement registers, où votre programme est allé et a lu la première entrée de votre ligne de cache la plus récente juste pour que votre deuxième entrée (dans VOTRE code) souhaitée se trouve dans le next cache line, (c.-à-d. la prochaine RANG&EACUTE;E au lieu de colonne, vous auriez un cache MANQUER.

En supposant que le cache est plein, car il est (petit} _, en cas d'échec, selon la procédure d'expulsion, une ligne serait expulsée pour laisser la place à la ligne qui a le prochaines données dont vous avez besoin. Si ce modèle se répète, vous obtiendrez un MISS sur TOUS LES tentatives de récupération de données! 

Pire encore, vous évinceriez des lignes contenant des données valides dont vous allez avoir besoin. Vous devrez donc les récupérer AGAIN et AGAIN. 

Le terme correspondant s'appelle: thrashing 

https://en.wikipedia.org/wiki/Thrashing_(computer_science) et peut en effet bloquer un système mal écrit/sujet aux erreurs. (Pensez à Windows BSOD) ....

Par contre, si vous aviez disposé les données correctement (majeur), vous auriez encore des ratés!

Mais ces manquements se produiraient seulement à la fin de chaque récupération, et non pas à TOUTES les tentatives de récupération. Cela se traduirait par des ordres de grandeur des différences de performances du système et du programme.

Extrait très très simple:

#include<stdio.h>

#define NUM_ROWS 1024
#define NUM_COLS 1024

int COL_MAJOR [NUM_ROWS][NUM_COLS];

int main (void){
        int i=0, j=0;
        for(i; i<NUM_ROWS; i++){
                for(j; j<NUM_COLS; j++){
                        COL_MAJOR[j][i]=(i+j);//NOTE i,j order here!
                }//end inner for
        }//end outer for
return 0;
}//end main

Maintenant, compilez avec:gcc -g col_maj.c -o col.o

Maintenant, lancez avec:time ./col.oreal 0m0.009suser 0m0.003ssys 0m0.004s

_ {Répétez maintenant pour ROW majeur:} _

#include<stdio.h>

#define NUM_ROWS 1024
#define NUM_COLS 1024

int ROW_MAJOR [NUM_ROWS][NUM_COLS];

int main (void){
        int i=0, j=0;
        for(i; i<NUM_ROWS; i++){
                for(j; j<NUM_COLS; j++){
                        ROW_MAJOR[i][j]=(i+j);//NOTE i,j order here!
                }//end inner for
        }//end outer for
return 0;
}//end main

Compile: terminal4$ gcc -g row_maj.c -o row.o Exécuter: _____time ./row.oreal 0m0.005suser 0m0.001ssys 0m0.003s

Comme vous pouvez le constater, le Row Major était nettement plus rapide.

Pas convaincu? Si vous souhaitez voir un exemple plus radical: Créez la matrice 1000000 x 1000000, initialisez-la, transposez-la et imprimez-la sur la sortie standard .

(Remarque: sur un système * NIX, vous devrez définir ulimit unlimited).

_ {Questions avec ma réponse:} _ -Optimizing compilers, they change a LOT of things! -Type of system -Please point any others out -This system has an Intel i5 processor

1
eulerworks

Compte tenu des explications ci-dessus, voici un extrait de code code illustrant le concept.

//----------------------------------------------------------------------------------------
// A generalized example of row-major, index/coordinate conversion for
// one-/two-dimensional arrays.
// ex: data[i] <-> data[r][c]
//
// Sandboxed at: http://Swift.sandbox.bluemix.net/#/repl/5a077c462e4189674bea0810
//
// -eholley
//----------------------------------------------------------------------------------------

// Algorithm

let numberOfRows    = 3
let numberOfColumns = 5
let numberOfIndexes = numberOfRows * numberOfColumns

func index(row: Int, column: Int) -> Int {
    return (row * numberOfColumns) + column
}

func rowColumn(index: Int) -> (row: Int, column: Int) {
    return (index / numberOfColumns, index % numberOfColumns)
}

//----------------------------------------------------------------------------------------

// Testing

let oneDim = [
       0,    1,    2,    3,    4,
       5,    6,    7,    8,    9,
      10,   11,   12,   13,   14,
]

let twoDim = [
    [  0,    1,    2,    3,    4 ],
    [  5,    6,    7,    8,    9 ],
    [ 10,   11,   12,   13,   14 ],
]

for i1 in 0..<numberOfIndexes {
    let v1 = oneDim[i1]
    let rc = rowColumn(index: i1)
    let i2 = index(row: rc.row, column: rc.column)
    let v2 = oneDim[i2]
    let v3 = twoDim[rc.row][rc.column]
    print(i1, v1, i2, v2, v3, rc)
    assert(i1 == i2)
    assert(v1 == v2)
    assert(v2 == v3)
}

/* Output:
0 0 0 0 0 (row: 0, column: 0)
1 1 1 1 1 (row: 0, column: 1)
2 2 2 2 2 (row: 0, column: 2)
3 3 3 3 3 (row: 0, column: 3)
4 4 4 4 4 (row: 0, column: 4)
5 5 5 5 5 (row: 1, column: 0)
6 6 6 6 6 (row: 1, column: 1)
7 7 7 7 7 (row: 1, column: 2)
8 8 8 8 8 (row: 1, column: 3)
9 9 9 9 9 (row: 1, column: 4)
10 10 10 10 10 (row: 2, column: 0)
11 11 11 11 11 (row: 2, column: 1)
12 12 12 12 12 (row: 2, column: 2)
13 13 13 13 13 (row: 2, column: 3)
14 14 14 14 14 (row: 2, column: 4)
*/
0
eholley

Un bref addenda aux réponses ci-dessus . En termes de C, où l’accès à la mémoire est presque direct, l’ordre en rangées majeures ou en colonnes affecte votre programme de deux manières: 1. Cela affecte la disposition de votre matrice en mémoire 2. L'ordre d'accès aux éléments qui doit être conservé - sous la forme de boucles de commande.

  1. est expliqué assez en détail dans les réponses précédentes, je vais donc ajouter 2.

eulerworks répond que, dans son exemple, l’utilisation de la matrice principale des lignes a entraîné un ralentissement important du calcul. Eh bien, il a raison, mais le résultat peut être inversé.

L'ordre des boucles était pour (sur les lignes) {pour (sur les colonnes) {faire quelque chose sur une matrice}}. Ce qui signifie que la double boucle accédera aux éléments d’une rangée, puis passera à la rangée suivante. Par exemple, A (0,1) -> A (0,2) -> A (0,3) -> ... -> A (0, N_ROWS) -> A (1,0) -> .. .

Dans ce cas, si A était stocké au format principal de ligne, le cache manquait très peu, car les éléments s'alignaient probablement de manière linéaire en mémoire. Sinon, au format colonne-majeur, l’accès à la mémoire sautera en utilisant N_ROWS comme foulée. Donc, la ligne principale est plus rapide dans le cas.

Maintenant, nous pouvons réellement changer la boucle, de manière à ce que cela fonctionne pour (sur les colonnes) {pour (sur les lignes) {faire quelque chose sur une matrice}}. Dans ce cas, le résultat sera exactement le contraire. Le calcul de la colonne principale sera plus rapide car la boucle lira les éléments dans les colonnes de manière linéaire.

Par conséquent, vous pourriez aussi bien vous souvenir de ceci: 1. Le choix du format de stockage majeur de lignes ou de colonnes est à votre goût, même si la communauté de programmation C traditionnelle semble préférer le format de lignes majeures . 2. Bien que vous soyez assez libre de choisir ce que vous voulez, vous devez être cohérent avec la notion d'indexation . 3. En outre, ceci est très important, gardez à l’esprit que lorsque vous écrivez vos propres algorithmes, essayez de commander les boucles afin qu’elles respectent le format de stockage de votre choix . 4. Être cohérent.

0
Juwhan Kim

Aujourd'hui, il n'y a aucune raison d'utiliser un ordre autre que colonne-majeur, plusieurs bibliothèques le supportent dans c/c ++ (eigen, armadillo, ...). De plus, l'ordre des colonnes est plus naturel, par exemple. les images avec [x, y, z] sont stockées tranche par tranche dans le fichier, il s'agit de l'ordre de la colonne principale. Alors que dans les deux dimensions, il peut être déroutant de choisir un meilleur ordre, dans les dimensions plus élevées, il est évident que l’ordre des colonnes est la seule solution dans de nombreuses situations.

Les auteurs de C ont créé le concept de tableaux, mais ils ne s'attendaient peut-être pas à ce que quelqu'un l'ait utilisé comme matrices. Je serais moi-même choqué si je voyais comment les tableaux sont utilisés à la place, où tout était déjà constitué dans l'ordre fortran et column-major. Je pense que cet ordre de rang majeur est simplement une alternative à un ordre de colonne majeur mais uniquement dans les cas où il est vraiment nécessaire (pour l'instant, je n'en connais aucun).

Il est étrange que quelqu'un crée encore une bibliothèque avec un ordre de lignes majeur. C'est un gaspillage inutile d'énergie et de temps. J'espère qu'un jour tout sera en ordre de colonne et que toute confusion disparaîtra tout simplement. 

0
Paul