MATLAB: génération de nombres aléatoires – pour boucle vs matrice 3D

Bonjour, j’ai une question concernant les performances de MATLAB lors de la génération de nombres aléatoires. C’est une situation assez simple, j’ai deux versions du même code, l’une génère des vecteurs de nombres aléatoires dans une boucle for et les enregistre dans une matrice 3D, et l’autre version génère les nombres aléatoires à la fois dans une matrice 3D en utilisant simplement fonction rand (). Il se trouve que la version « for loop » est plus rapide que la version « no for loop » après une certaine taille de la matrice 3D aléatoire. J’en ai discuté avec mes pairs et personne ne semble comprendre pourquoi. Est-ce que quelqu’un sait pourquoi cela se produit? Merci!
PS: Ce code a été essayé sur différentes machines exécutant Windows et macOS, suffisamment de RAM était disponible et des processeurs capables ont été utilisés.
Code:
 
clear all
m = 10^4;
N = 10^3;
tic
r = zeros(N,3,m);
for n = 1:m
r(:,1:2,n) = rand([N,2]) - 1/2;
r(:,3,n) = rand([N,1]) - 1;
end
toc
clear r
tic
r = rand(N,3,m) - 1;
r(:,1:2,:) = r(:,1:2,:) + 1/2;
toc
 

Meilleure réponse

  • Il est vraiment impossible de savoir quelque chose comme ça, à moins d’avoir le code assis devant vous. Et c’est quelque chose que nous n’avons pas. De plus, le tic et le toc sont généralement une mauvaise façon de chronométrer les choses. Ils ont tendance à ne pas utiliser JIT également, car ils sont utilisés dans un script. Et ils ne se réchauffent pas correctement. Et puisque vous devez quand même apprendre à utiliser les fonctions, utilisez timeit. Quoi qu’il en soit, laissez-moi voir ce que je peux apprendre ici. Tout d’abord, reconstituez les choses en fonctions. L’utilisation de fonctions peut améliorer le fonctionnement de JIT, du moins c’était le cas par le passé. Pas si vrai aujourd’hui, mais cela nous aide également à utiliser le profileur.
     
    function r = test1(m,N)
    r = zeros(N,3,m);
    for n = 1:m
    r(:,1:2,n) = rand([N,2]) - 1/2;
    r(:,3,n) = rand([N,1]) - 1;
    end
    function r = test2(m,N)
    r = rand(N,3,m) - 1;
    r(:,1:2,:) = r(:,1:2,:) + 1/2;
     
    Après avoir inséré le code dans une fonction, appelez maintenant timeit.
     
    timeit(@() test1(1e4,1e3))
    ans =
    0.2452168234265
    timeit(@() test2(1e4,1e3))
    ans =
    0.3289476464265
     
    Il montre également le même comportement. (Notez que j’ai utilisé la notation scientifique pour définir m et N. Plus facile à écrire, et à mesure que vous écrivez plus de MATLAB, c’est plus facile à faire.)
    Alors, ensuite, exécutez l’outil de profil. UTILISEZ LES OUTILS DE MATLAB. C’est la seule façon d’apprendre ce qui se passe. Quand je fais cela, je peux regarder le code de test1 et de test2, pour savoir où MATLAB semble prendre plus de temps. Pour plusieurs appels à test1 (rappelez-vous que le temps est en boucle sur les appels à la fonction pour obtenir un temps moyen utilisé), test1 avait un temps total de 3,532 secondes.
     
    2.297 seconds: r(:,1:2,n) = rand([N,2]) - 1/2;
    1.226 seconds: r(:,3,n) = rand([N,1]) - 1;
     
    Tout le reste du test 1 était négligeable. Il y avait 150000 occurrences réservées pour chacune de ces lignes. Mais ils étaient tous de petits lots de nombres aléatoires dans chaque cas.
    Ensuite, nous regardons test2. Il a été appelé seulement 15 fois.
     
    3.234 seconds: r = rand(N,3,m) - 1;
    1.515 seconds: r(:,1:2,:) = r(:,1:2,:) + 1/2;
     
    Cependant, chaque appel était plus important. Comme vous pouvez le constater, les gros appels semblent en effet moins efficaces. Mais en réalité, les appels dans test2 SONT inefficaces. Pourquoi? Ils sont inefficaces car vous avez calculé un ensemble de nombres aléatoires, un grand tableau, puis soustrait 1 de TOUS. Mais pour CERTAINS d’entre eux, vous avez ajouté 1/2 de retour.
    En effet, vous avez fait un ensemble supplémentaire d’ajouts sur certains numéros, alors qu’il n’était pas nécessaire de faire DEUX ajouts sur les mêmes numéros.
    Pouvons-nous être plus efficaces nous-mêmes? Par exemple, considérez test3.
     
    function r = test3(m,N)
    r = rand(N,3,m) - [1/2 1/2 1]; % Fixed per Ameer's comment
     
    En testant ce code, nous voyons:
     
    timeit(@() test3(1e4,1e3))
    ans =
    0.2259027794265
     
    Dans test3, j’ai utilisé une expansion implicite du vecteur [1 1 1/2] pour accomplir l’opération souhaitée en un appel plus simple. Désormais, un seul ajout par élément est effectué. Encore une fois, il n’y a eu que 15 appels à rand, mais ici, nous n’avions besoin que d’une seule opération arithmétique par élément, donc cela a pris moins de temps. Et cet appel à rand avec UN ajout par élément est plus rapide que même la boucle accélérée JIT. Voici le profileur me dit, pour encore 15 appels à test3:
     

    3.345 seconds: r = rand(N,3,m) - [1 1 1/2];