Forum Programmation.ruby Fonction : passage de référence

Posté par .
Tags : aucun
0
13
juil.
2009
Salut cher forum,

j'ai un petit soucis avec Ruby. Je travaille sur des jeux de données relativement volumineux. Je les stocke dans des tables de hachage pour un traitement rapide (j'ai de la RAM tant que je veux) et j'y applique quelques fonctions.

Par exemple, j'ai une fonction qui me calcule la mesure de Jaccard entre deux vecteurs. On se fout pas mal de l'implémentation (qui consiste à comparer les éléments identiques et disjoints entre les deux vecteurs: plus il y a d'élément identiques, plus la mesure est élevé, plus il y a d'élément différent, plus la mesure est faible, tout ça entre 0 et 1).

Or, quand j'appelle ma fonction, je fais

jaccard(vec_1, vec_2)


Ce qui prend pas mal de temps, parce que j'ai l'impression (et google confirme, irb aussi) qu'il recopie vec_1 et vec_2 à l'appel de la fonction, que je fais ce calcul environ 180 000 fois (j'ai beaucoup d'éléments à comparer) et que chaque vecteur est de dimension environ 3000. Du coup, la copie prend un peu de temps et plombe pas mal le calcul, déjà bien long (3000 * 180 000).

Je cherche donc une façon élégante d'appeler les /références/ à vec_1 et vec_2, puisque de toutes façons je ne modifie pas ces objets dans la fonction.

Prenons un cas plus simple. J'ai un tableau a = ["a","b","c"] et une fonction bete :

def rend_x (vecteur)
vecteur = ["x", "x", "x"]
end


Je voudrais appeler rend_x(a), et que a soit transformé en ["x", "x", "x"] (c'est-à-dire donc, que ma fonction modifie directement l'élément a -- j'insiste: c'est un exemple jouet, en pratique je m'en fout complètement, je veux juste qu'il ne recopie pas /a/ à l'appel de la fonction).

En perl c'est facile (et pour le coup, je réimplémente en ruby un script perl que je trouve bien trop gruik, mais si mon script ruby est beaucoup plus lent -- il l'est -- je vais avoir du mal à le vendre, meme s'il est beaucoup plus propre).
  • # Itération

    Posté par . Évalué à 2.

    Je me répond à moi meme, pour préciser mon propos.

    J'ai une table de hachage qui contient des tables de hachage. J'itère dessus :

    # J'ai quelque part un vecteur vec_1
    result = Hash.new

    # Je veux comparer vec_1 avec tous les autres vecteurs (vec_2) qui sont stockés dans
    # la table de hachage "hachage".
    hachage.each do |cle, valeur| # valeur est une table de hachage
    valeur.each do |osef, vec_2|
    result[cle] = jaccard(vec_1, vec_2)
    end
    end


    Dans ce cas, non seulement je voudrais appeler les références à vec_1 et vec_2, mais je voudrais également que l'itérateur ne recopie pas les objets cle, valeur, osef et vec_2.

    *cough* je suis pas vraiment un programmeur de ouf, il y a peut-etre une méthode beaucoup plus propre pour faire ça. Si j'utilise Ruby, c'est parce que c'est simple à écrire. C'est la première fois que je me retrouve dans un cas où la lenteur joue contre moi. N'hésitez pas à me conseiller d'autres manières de faire si besoin.
  • # Tout est référence

    Posté par (page perso) . Évalué à 5.

    En ruby, tout est référence.

    Pour faire une copie, il faut utiliser la méthode clone.

    Illustration:


    >> a = 1
    >> b = a
    >> c = 3

    >> a.object_id
    => 3
    >> b.object_id
    => 3
    >> c.object_id
    => 7

    >> def salut(d)
    >> d.object_id
    >> end

    >> salut a
    => 3

    Envoyé depuis mon lapin.

    • [^] # Re: Tout est référence

      Posté par . Évalué à 2.

      Cela signifie donc que mon problème ne vient pas de là. Pour confirmer ce qui se dit ici, si j'ai :
      h1 = Hash.new
      # population de h1 avec plein de trucs
      
      h2 = Hash.new
      # population de h2 avec plein de trucs
      
      # Calcul du cosinus
      STDOUT.puts cosinus(h1,h2)
      
      Avec une fonction cosinus :
      # Ce n'est pas vraiment une fonction de calcul du cosinus
      def cosinus(v1, v2)
         resultat = 0
         intersection = v1.keys & v2.keys # éléments commun à v1 et v2
         intersection.each do |cle|
            resultat = v1[cle] * v2[cle]
         end
         return resultat
      end
      
      Seule la référence à mes hash sera envoyé à /cosinus/ (pas de recopie dans v1 et v2, donc), donc les appels v1[cle] et v2[cle] se font sur h1 et h2. J'ai bon ? Si oui, je n'explique pas vraiment comment, à algorithme équivalent, mon programme en Perl est deux fois plus rapide que mon programme en ruby. Des pistes ?
      • [^] # Re: Tout est référence

        Posté par (page perso) . Évalué à 2.

        sans vouloir entrer dans le troll, un facteur 2 n'est pas si etonnant que ça.
        Ruby est en general plus lent que Perl, ne serait-ce que parce que l'interpreteur Perl est plus optimisé.
        Ce probleme est connu de la communauté Ruby, et plusieurs projets tentent d'y remedier (cf. YARV, Jruby, ...).
  • # Héritage de C

    Posté par . Évalué à 1.

    "
    Prenons un cas plus simple. J'ai un tableau a = ["a","b","c"] et une fonction bete :

    def rend_x (vecteur)
    vecteur = ["x", "x", "x"]
    end

    Je voudrais appeler rend_x(a), et que a soit transformé en ["x", "x", "x"] (c'est-à-dire donc, que ma fonction modifie directement l'élément a -- j'insiste: c'est un exemple jouet, en pratique je m'en fout complètement, je veux juste qu'il ne recopie pas /a/ à l'appel de la fonction).
    "

    C'est dans ces cas-là que je pense qu'il est *nécessaire* d'avoir fait du C pour comprendre Ruby/Python/Java/autre-langage-impératif-commun.

    Quand tu appelles "rend_x(foo)", dans ta fonction, "vecteur" et "foo" sont 2 variables distinctes, elles peuvent pointer au même endroit, et c'est le cas au début de ta fonction, mais elles peuvent aussi pointer des endroits différents, avec une simple affectation.
    En l'occurence, quand tu fais "vecteur = ...", tu fais pointer "vecteur" vers d'autres données, sans modifier "foo" du coup. Tu alloues un nouveau tableau, totalement distinct de ton précédent ["a","b","c"], et les 2 ne sont pas à la même adresse.
    (Note: à moins d'avoir des pointeurs sur pointeurs comme en C, tu ne peux pas modifier la variable "foo", mais tu peux modifier le contenu de là où elle pointe)
    Si tu veux modifier le tableau, tu peux faire

    vecteur[0] = "x"
    vecteur[1] = "x"
    vecteur[2] = "x"

    En Python, il est possible de faire
    vecteur[:] = ["x", "x", "x"]
    qui va modifier en place le contenu de "vecteur" (il va le déréférencer), et qui ne va pas faire pointer "vecteur" vers un nouveau tableau. Un équivalent existe peut-être en Ruby, je ne connais pas assez.

    Ou sinon un équivalent à :

    vecteur.clear()
    vecteur.extend(["x", "x", "x"])

    Ou encore (méthode à éviter)

    vecteur.clear()
    vecteur += ["x", "x", "x"]

    (qui n'est pas *du tout* équivalent à vecteur = vecteur + ["x", "x", "x"], car ce dernier ferait pointer vecteur autre part, alors que chez le premier "+=" est une opération en place pour les tableaux uniquement)
  • # Mauvais langage

    Posté par (page perso) . Évalué à 2.

    Du traitement de données volumineuses avec des opérations trigo ?
    Je crois vraiment que si tu te plains de la rapidité mais que tu insistes pour utiliser un langage de script, on ne peut plus rien pour toi.

    À ta place, je piperais tout vers un filtre écrit en C (Ada, fortran, etc) (un filtre, c'est à dire un "while stdin != EOF").
    Genre en entrée:
    taille_paire_vecteurs
    v1_1 v1_2 v1_3 … v1_taille_paire_vecteurs
    v2_1 v2_2 v2_3 … v2_taille_paire_vecteurs
    taille_paire_vecteurs
    v1_1 v1_2 v1_3 … v1_taille_paire_vecteurs
    v2_1 v2_2 v2_3 … v2_taille_paire_vecteurs


    Et en sortie, ta mesure.

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.