Tous les éléments d'un ndarray Numpy doivent être du même type et tous les sous-réseaux du même niveau doivent être de la même longueur. Ces propriétés sont également celles des tableaux multidimensionnels en C. Est-il vrai que numpy ndarray possède ces propriétés uniquement parce qu'il est implémenté au-dessus d'un tableau C ? Ces propriétés sont-elles vraiment nécessaires pour créer une implémentation rapide des tableaux multidimensionnels ?
Réponse
Trop de publicités?Est-ce que numpy ndarray a ces propriétés uniquement parce qu'il est implémenté au-dessus d'un tableau C ?
Non. L'utilisation du C en interne n'est pas vraiment la raison pour laquelle Numpy a fait ce choix. En effet, les tableaux Numpy ne sont que des tableaux bruts. contiguës Le tampon de mémoire interne (alloué dynamiquement). Numpy n'utilise pas de tableau C dans sa propre implémentation. Il utilise seulement Pointeurs C . Les vues sont des objets Python qui font référence à la mémoire tampon et qui contiennent des méta-informations telles que les strides, la forme, le type, etc. Les utilisateurs de Python opèrent toujours sur des vues car les tampons bruts ne peuvent pas être lus/écrits directement.
En fait, il n'est pas très difficile de créer des jagged array en C (c'est-à-dire des tableaux contenant des tableaux de taille variable), mais il faut le faire manuellement (c'est-à-dire que ce n'est pas directement supporté par le standard). Pour un tableau 2D en dents de scie, cela se fait généralement en allouant un tableau de T* éléments, puis en effectuant N allocations pour les N sous-réseaux. Il n'est pas garanti que les sous-réseaux soient stockés de manière contiguë.
Le fait est que les tableaux en dents de scie ne sont pas efficaces en raison de la fragmentation/diffusion de la mémoire et de la non-contiguïté. En outre, de nombreuses fonctionnalités offertes par Numpy ne seraient pas possibles (efficacement) avec des tableaux irréguliers . Par exemple, il serait difficile de créer une vue secondaire pour une autre vue avec un stride. Les opérations travaillant sur plusieurs axes (ex. np.sum(2D_array, axis=0)
) devrait être redéfini pour qu'il ait un sens avec les tableaux en dents de scie. Cela rendrait également la mise en œuvre beaucoup plus complexe.
Par conséquent, ils ont choisi de ne pas mettre en œuvre le jagged array mais seulement le ND-array. Veuillez noter que Numpy a été initialement créé pour les scientifiques et en particulier les physiciens qui ont rarement besoin de jagged array mais qui se soucient de la haute performance. Les jagged arrays peuvent être implémentés de manière relativement efficace en allouant 2 tableaux Numpy : 1 tableau concaténant toutes les lignes et un tableau basé sur les tranches contenant les décalages de début et de fin.
Ces propriétés sont-elles vraiment nécessaires pour créer une implémentation rapide d'un tableau multidimensionnel ?
L'homogénéité des types est essentielle à la performance . Le typage dynamique oblige à vérifier le type de chaque élément, ce qui est coûteux. En outre, le typage dynamique nécessite souvent une indirection supplémentaire coûteuse (par exemple, le tableau ne stocke que des pointeurs/références et non l'objet lui-même) et l'accès aux objets entraîne une fragmentation/diffusion de la mémoire. Ces opérations sont très coûteuses par rapport aux opérations numériques de base telles que l'addition/soustraction/multiplication. En outre, le cycle de vie de l'objet doit certainement être soigneusement contrôlé (par exemple, le ramassage des ordures) comme le fait CPython. En fait, une liste de listes CPython se comporte comme cela et est assez inefficace. Vous pouvez créer des tableaux Numpy d'objets qui sont des tableaux Numpy, mais c'est également inefficace.
En ce qui concerne les tableaux rectangulaires, cela dépend du cas d'utilisation, mais c'est au moins critique pour la multiplication des matrices et les produits matrice-vecteur (comme le montre l'exemple suivant BLAS fonctionne sur contiguës éventuellement avec un espace entre les lignes), ainsi que des opérations ne fonctionnant pas sur la dimension la plus contiguë (les compilateurs peuvent effectuer des optimisations plus agressives avec un paramètre foulée constante ). Sans oublier les frais généraux supplémentaires spécifiés ci-dessus (par exemple, contrôles supplémentaires et fragmentation/diffusion de la mémoire).