63 votes

Un moyen plus rapide d’initialiser les tableaux via la multiplication de matrice vide? (Matlab)

J'ai trébuché sur la façon bizarre (à mon avis) que Matlab est de traiter avec vide matrices. Par exemple, si deux vide matrices sont multiplié le résultat est:

zeros(3,0)*zeros(0,3)
ans =

 0     0     0
 0     0     0
 0     0     0

Maintenant, déjà, m'a pris par surprise, cependant, une recherche rapide m'a fait le lien ci-dessus, et j'ai eu une explication quelque peu logique tordue de pourquoi cela se passe.

Cependant, rien ne m'a préparé pour la suite de l'observation. Je me suis demandé, quelle est l'efficacité de ce type de multiplication vs juste à l'aide de zeros(n) fonction, disons pour la fin de l'initialisation? J'ai utilisé timeit de répondre à ceci:

f=@() zeros(1000)
timeit(f)
ans =
    0.0033

vs:

g=@() zeros(1000,0)*zeros(0,1000)
timeit(g)
ans =
    9.2048e-06

Les deux ont le même résultat de 1000x1000 matrice de zéros de la classe double, mais le vide de la multiplication de matrice est ~350 fois plus vite! (un résultat similaire se produit à l'aide de tic et toc et une boucle)

Comment cela peut-il être? sont - timeit ou tic,toc bluff ou j'ai trouvé un moyen plus rapide pour initialiser les matrices? (cela a été fait avec matlab, 2012a, sur un win7-64-linge, intel i5 650 3.2 Ghz...)

EDIT:

Après avoir lu vos commentaires, j'ai regardé plus attentivement dans cette particularité, et testé sur 2 ordinateurs différents (même matlab ver si 2012a) un code qui examinent le temps d'exécution par rapport à la taille de la matrice n. C'est ce que j'obtiens:

enter image description here

Le code utilisé pour générer ce timeit comme avant, mais une boucle avec tic et toc aura la même apparence. Donc, pour les petites tailles, zeros(n) est comparable. Cependant, autour de n=400 il y a un saut de performance pour le vide de la multiplication de matrice. Le code que j'ai utilisé pour générer cette parcelle a été:

n=unique(round(logspace(0,4,200)));
for k=1:length(n)
    f=@() zeros(n(k));
    t1(k)=timeit(f);

    g=@() zeros(n(k),0)*zeros(0,n(k));
    t2(k)=timeit(g);
end

loglog(n,t1,'b',n,t2,'r');
legend('zeros(n)','zeros(n,0)*zeros(0,n)',2);
xlabel('matrix size (n)'); ylabel('time [sec]');

Est-ce que vous rencontrez ce trop?

EDIT #2:

D'ailleurs, vides de la matrice de la multiplication n'est pas nécessaire pour obtenir cet effet. On peut tout simplement faire:

z(n,n)=0;

où n> seuil de la taille de la matrice observée dans le graphique précédent, et obtenir le exacte du profil d'efficience comme vides de la matrice de multiplication (de nouveau à l'aide de timeit).

enter image description here

Voici un exemple où il améliore l'efficacité d'un code:

n = 1e4;
clear z1
tic
z1 = zeros( n ); 
for cc = 1 : n
    z1(:,cc)=cc;
end
toc % Elapsed time is 0.445780 seconds.

%%
clear z0
tic
z0 = zeros(n,0)*zeros(0,n);
for cc = 1 : n
    z0(:,cc)=cc;
end
toc % Elapsed time is 0.297953 seconds.

Cependant, l'utilisation d' z(n,n)=0; au lieu de cela génère des résultats similaires à l' zeros(n) des cas.

33voto

Pavan Yalamanchili Points 6637

C'est étrange, je vois f étant plus rapide, tandis que g étant plus lent que ce que vous voyez. Mais deux d'entre eux sont identiques pour moi. Peut-être une autre version de MATLAB ?

>> g = @() zeros(1000, 0) * zeros(0, 1000);
>> f = @() zeros(1000)
f =     
    @()zeros(1000)
>> timeit(f)  
ans =    
   8.5019e-04
>> timeit(f)  
ans =    
   8.4627e-04
>> timeit(g)  
ans =    
   8.4627e-04

MODIFIER pouvez-vous ajouter + 1 pour la fin de f et de g, et voir ce que le temps que vous obtenez.

EDIT Jan 6, 2013 7:42 HNE

Je suis l'aide d'une machine à distance, donc désolé pour la faible qualité des graphiques (dû générer des aveugles).

Machine config:

i7 920. 2.653 GHz. Linux. 12 GO DE RAM. 8 mo de cache.

Graph generated on i7 920

Il semble que même la machine, j'ai accès à l'affiche ce comportement, sauf à une taille de plus (quelque part entre 1979 et 2073). Il n'y a pas de raison que je pense dès maintenant pour le vide de la multiplication de matrice pour être plus rapide dans les grandes tailles.

Je vais étudier un peu plus avant de revenir.

EDIT Jan 11, 2013

Après @EitanT post, je voulais faire un peu plus de creuser. J'ai écrit du code C pour voir comment matlab peut être la création d'une zéros de la matrice. Voici le code c++ que j'ai utilisé.

int main(int argc, char **argv)
{
    for (int i = 1975; i <= 2100; i+=25) {
    timer::start();
    double *foo = (double *)malloc(i * i * sizeof(double));
    for (int k = 0; k < i * i; k++) foo[k]  = 0;
    double mftime = timer::stop();
    free(foo);

    timer::start();
    double *bar = (double *)malloc(i * i * sizeof(double));
    memset(bar, 0, i * i * sizeof(double));
    double mmtime = timer::stop();
    free(bar);

    timer::start();
    double *baz = (double *)calloc(i * i, sizeof(double));
    double catime = timer::stop();
    free(baz);

    printf("%d, %lf, %lf, %lf\n", i, mftime, mmtime, catime);
    }
}

Voici les résultats.

$ ./test
1975, 0.013812, 0.013578, 0.003321
2000, 0.014144, 0.013879, 0.003408
2025, 0.014396, 0.014219, 0.003490
2050, 0.014732, 0.013784, 0.000043
2075, 0.015022, 0.014122, 0.000045
2100, 0.014606, 0.014480, 0.000045

Comme vous pouvez le voir calloc (4e colonne) semble être la méthode la plus rapide. Il est également beaucoup plus rapide entre 2025 et 2050 (je suppose que ce serait autour de 2048 ?).

Maintenant, je suis retourné à matlab de vérifier pour la même chose. Voici les résultats.

>> test
1975, 0.003296, 0.003297
2000, 0.003377, 0.003385
2025, 0.003465, 0.003464
2050, 0.015987, 0.000019
2075, 0.016373, 0.000019
2100, 0.016762, 0.000020

Il ressemble à la fois à f() et g() sont à l'aide de calloc à de plus petites tailles (<2048 ?). Mais dans les grandes tailles f() (zeros(m, n)) commence à utiliser malloc + memset, tandis que g() (zeros(m, 0) * des zéros(0, n)) continue d'utiliser calloc.

Alors la divergence s'explique par les éléments suivants

  • les zéros(..) commence à utiliser une autre (plus lent ?) régime dans les grandes tailles.
  • calloc également se comporte de façon un peu inattendue, conduisant à une amélioration de la performance.

C'est le comportement sur Linux. Quelqu'un peut-il faire la même expérience sur une autre machine (et peut-être un autre OS) et de voir si l'expérience est titulaire ?

29voto

Amro Points 72743

Les résultats pourraient être un peu trompeur. Quand vous multipliez deux vide matrices, la matrice obtenue est pas immédiatement "alloué" et "initialisé", plutôt c'est reportée jusqu'à la première utilisation (un peu comme une évaluation différée).

La même chose s'applique lorsque l'indexation en dehors des limites pour grandir une variable, qui, dans le cas de tableaux numériques remplit toutes les entrées manquantes avec des zéros (j'en discute ensuite de la non-numérique cas). Bien sûr, la culture de la matrice de cette façon, ne pas remplacer les éléments existants.

Ainsi, alors qu'il peut sembler plus rapide, vous êtes juste de retarder le moment de l'allocation jusqu'à ce que vous en fait de la première utilisation de la matrice. En fin de compte, vous aurez les mêmes périodes que si vous ne l'allocation dès le début.

Exemple pour montrer ce comportement, par rapport à quelques autres alternatives:

N = 1000;

clear z
tic, z = zeros(N,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
tic, z = zeros(N,0)*zeros(0,N); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
tic, z(N,N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
tic, z = full(spalloc(N,N,0)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
tic, z(1:N,1:N) = 0; toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
val = 0;
tic, z = val(ones(N)); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

clear z
tic, z = repmat(0, [N N]); toc
tic, z = z + 1; toc
assert(isequal(z,ones(N)))

Le résultat montre que si l'on somme le temps écoulé pour les deux instructions dans chaque cas, vous vous retrouvez avec total semblable horaires:

// zeros(N,N)
Elapsed time is 0.004525 seconds.
Elapsed time is 0.000792 seconds.

// zeros(N,0)*zeros(0,N)
Elapsed time is 0.000052 seconds.
Elapsed time is 0.004365 seconds.

// z(N,N) = 0
Elapsed time is 0.000053 seconds.
Elapsed time is 0.004119 seconds.

Les autres timings étaient:

// full(spalloc(N,N,0))
Elapsed time is 0.001463 seconds.
Elapsed time is 0.003751 seconds.

// z(1:N,1:N) = 0
Elapsed time is 0.006820 seconds.
Elapsed time is 0.000647 seconds.

// val(ones(N))
Elapsed time is 0.034880 seconds.
Elapsed time is 0.000911 seconds.

// repmat(0, [N N])
Elapsed time is 0.001320 seconds.
Elapsed time is 0.003749 seconds.

Ces mesures sont trop faibles dans les millisecondes et pourraient ne pas être très précis, de sorte que vous pourriez voulez exécuter ces commandes dans une boucle de quelques milliers de fois et en faire la moyenne. Aussi parfois en cours d'exécution enregistrés M-fonctions est plus rapide que l'exécution de scripts ou sur l'invite de commande, comme certaines optimisations seulement se produire de cette façon...

De toute façon l'allocation se fait généralement une fois, alors, qui se soucie si il faut un supplément de 30ms :)


Un comportement similaire peut être observée avec les cellules des tableaux ou des tableaux de structures. Considérons l'exemple suivant:

N = 1000;

tic, a = cell(N,N); toc
tic, b = repmat({[]}, [N,N]); toc
tic, c{N,N} = []; toc

ce qui donne:

Elapsed time is 0.001245 seconds.
Elapsed time is 0.040698 seconds.
Elapsed time is 0.004846 seconds.

Notez que même si ils sont tous égaux, ils occupent des différents quantité de mémoire:

>> assert(isequal(a,b,c))
>> whos a b c
  Name         Size                  Bytes  Class    Attributes

  a         1000x1000              8000000  cell               
  b         1000x1000            112000000  cell               
  c         1000x1000              8000104  cell               

En fait, la situation est un peu plus compliqué ici, depuis MATLAB est probablement le partage de la même matrice vide pour toutes les cellules, plutôt que de créer plusieurs copies.

La matrice de cellules de a est en fait un tableau de cellules non initialisée (un tableau de pointeurs NULL), tandis que l' b est une cellule de tableau, où chaque cellule est un tableau vide, [] (en interne et en raison du partage des données, seule la première cellule b{1} points de [] alors que tous les autres ont une référence à la première cellule). Le tableau final c est similaire à l' a (non initialisée cellules), mais avec le dernier contenant un vide numérique de la matrice [].


J'ai regardé autour de la liste des fonctions C exportées à partir de l' libmx.dll (à l'aide de Dependency Walker outil), et j'ai trouvé quelques petites choses intéressantes.

  • il y a des sans-papiers, des fonctions pour la création non initialisée tableaux: mxCreateUninitDoubleMatrix, mxCreateUninitNumericArray, et mxCreateUninitNumericMatrix. Il y a en fait une présentation sur le Fichier d'Échange permet l'utilisation de ces fonctions afin de fournir une alternative plus rapide à l' zeros fonction.

  • il existe un sans-papiers fonction appelée mxFastZeros. Googler en ligne, je peux vous voir de la croix-posté cette question sur MATLAB Réponses ainsi, avec d'excellentes réponses là-bas. James Tursa (même auteur de UNINIT d'avant) a donné un exemple sur la façon d'utiliser ce sans-papiers de fonction.

  • libmx.dll est liée à l'encontre tbbmalloc.dll bibliothèque partagée. C'est Intel TBB évolutive allocateur de mémoire. Cette bibliothèque fournit l'équivalent fonctions d'allocation de mémoire (malloc, calloc, free) optimisé pour les applications parallèles. Rappelez-vous que de nombreuses fonctions MATLAB sont automatiquement multithread, donc je ne serais pas surpris si zeros(..) est multithread et est à l'aide d'Intel allocateur de mémoire une fois la matrice de taille est assez grande (ici est récent commentaire par Loren Shure qui confirme cet état de fait).

Concernant le dernier point sur l'allocateur de mémoire, vous pouvez écrire une semblable référence en C/C++ similaire à ce que @PavanYalamanchili n', et de comparer les diverses allocateurs disponibles. Quelque chose comme cela. Rappelez-vous que le MEX-files ont un peu plus de la mémoire la gestion des frais généraux, depuis MATLAB libère automatiquement la mémoire qui a été allouée à MEX-files à l'aide de l' mxCalloc, mxMallocou mxRealloc fonctions. Pour ce que ça vaut, il était possible de changer de l'intérieur gestionnaire de mémoire dans les anciennes versions.


EDIT:

Ici est un plus approfondie de référence pour comparer l'examen des solutions de rechange. Il montre en particulier que, une fois que vous insistez sur l'utilisation de l'ensemble de l'alloué de la matrice, les trois méthodes sont sur un pied d'égalité, et la différence est négligeable.

function compare_zeros_init()
    iter = 100;
    for N = 512.*(1:8)
        % ZEROS(N,N)
        t = zeros(iter,3);
        for i=1:iter
            clear z
            tic, z = zeros(N,N); t(i,1) = toc;
            tic, z(:) = 9; t(i,2) = toc;
            tic, z = z + 1; t(i,3) = toc;
        end
        fprintf('N = %4d, ZEROS = %.9f\n', N, mean(sum(t,2)))

        % z(N,N)=0
        t = zeros(iter,3);
        for i=1:iter
            clear z
            tic, z(N,N) = 0; t(i,1) = toc;
            tic, z(:) = 9; t(i,2) = toc;
            tic, z = z + 1; t(i,3) = toc;
        end
        fprintf('N = %4d, GROW  = %.9f\n', N, mean(sum(t,2)))

        % ZEROS(N,0)*ZEROS(0,N)
        t = zeros(iter,3);
        for i=1:iter
            clear z
            tic, z = zeros(N,0)*zeros(0,N); t(i,1) = toc;
            tic, z(:) = 9; t(i,2) = toc;
            tic, z = z + 1; t(i,3) = toc;
        end
        fprintf('N = %4d, MULT  = %.9f\n\n', N, mean(sum(t,2)))
    end
end

Ci-dessous sont les horaires en moyenne plus de 100 itérations en termes d'augmentation de la taille de la matrice. J'ai effectué les tests en R2013a.

>> compare_zeros_init
N =  512, ZEROS = 0.001560168
N =  512, GROW  = 0.001479991
N =  512, MULT  = 0.001457031

N = 1024, ZEROS = 0.005744873
N = 1024, GROW  = 0.005352638
N = 1024, MULT  = 0.005359236

N = 1536, ZEROS = 0.011950846
N = 1536, GROW  = 0.009051589
N = 1536, MULT  = 0.008418878

N = 2048, ZEROS = 0.012154002
N = 2048, GROW  = 0.010996315
N = 2048, MULT  = 0.011002169

N = 2560, ZEROS = 0.017940950
N = 2560, GROW  = 0.017641046
N = 2560, MULT  = 0.017640323

N = 3072, ZEROS = 0.025657999
N = 3072, GROW  = 0.025836506
N = 3072, MULT  = 0.051533432

N = 3584, ZEROS = 0.074739924
N = 3584, GROW  = 0.070486857
N = 3584, MULT  = 0.072822335

N = 4096, ZEROS = 0.098791732
N = 4096, GROW  = 0.095849788
N = 4096, MULT  = 0.102148452

27voto

Eitan T Points 24450

Après avoir fait quelques recherches, j'ai trouvé cet article dans "sans-papiers Matlab", dans lequel M. Yair Altman était déjà venu à la conclusion que MathWork façon de preallocating matrices à l'aide de zeros(M, N) est en effet pas la manière la plus efficace.

Il chronométrés x = zeros(M,N) vs clear x, x(M,N) = 0 et a constaté que ce dernier est environ 500 fois plus rapide. Selon ses explications, la deuxième méthode crée simplement un M-par-N de la matrice, dont les éléments sont automatiquement initialisées à 0. La première méthode, cependant, crée x (avec x avoir zéro automatique des éléments) et attribue alors un zéro à chaque élément en x encore, et c'est redondant opération qui prend plus de temps.

Dans le cas de vide de multiplication de matrice, comme ce que vous avez indiqué dans votre question, MATLAB s'attend à ce que le produit soit un M×N matrice, et par conséquent, il alloue un M×N matrice. Par conséquent, la matrice de sortie est automatiquement initialisée à zéros. Depuis les matrices originales sont vides, pas d'autres calculs sont effectués, et donc les éléments de la matrice de sortie reste inchangé et égal à zéro.

2voto

Dennis Jaheruddin Points 10154

Question intéressante, apparemment, il ya plusieurs façons de le 'beat' le haut- zeros fonction. Mon seul deviner pourquoi ce qui se passe serait qu'il pourrait être plus efficace en terme de mémoire (après tout, zeros(LargeNumer) seront tôt provoquer Matlab pour frapper la limite de la mémoire de forme d'une dévastateur goulot d'étranglement de vitesse dans la plupart des code), ou plus robuste en quelque sorte.

Voici un autre rapide de la méthode de répartition à l'aide d'une matrice creuse, j'ai ajouté de la régulièrement des zéros de la fonction en tant que point de référence:

tic; x=zeros(1000,1000); toc
Elapsed time is 0.002863 seconds.

tic; clear x; x(1000,1000)=0; toc
Elapsed time is 0.000282 seconds.

tic; x=full(spalloc(1000,1000,0)); toc
Elapsed time is 0.000273 seconds.

tic; x=spalloc(1000,1000,1000000); toc %Is this the same for practical purposes?
Elapsed time is 0.000281 seconds.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X