Quand devrais-je utiliser Struct contre OpenStruct?

184

En général, quels sont les avantages et les inconvénients de l'utilisation d'un OpenStruct par rapport à un Struct? Quel type de cas d'utilisation généraux conviendrait à chacun de ces cas?

ehsanul
la source
1
J'ai quelques remarques sur Struct vs OpenStruct vs Hash dans mon récent commentaire de blog "Structs inside out" , juste au cas où quelqu'un serait intéressé.
Robert Klemme
Les informations concernant la vitesse de Hash, Struct et OpenStruct sont obsolètes. Voir stackoverflow.com/a/43987844/128421 pour une référence plus récente.
the Tin Man

Réponses:

173

Avec un OpenStruct, vous pouvez créer arbitrairement des attributs. A Struct, d'autre part, doit avoir ses attributs définis lorsque vous le créez. Le choix de l'un par rapport à l'autre doit être basé principalement sur la question de savoir si vous devez être en mesure d'ajouter des attributs ultérieurement.

La façon de penser à eux est comme le milieu du spectre entre les hachages d'un côté et les classes de l'autre. Ils impliquent une relation plus concrète entre les données que ne le fait a Hash, mais ils n'ont pas les méthodes d'instance comme le ferait une classe. Un tas d'options pour une fonction, par exemple, a du sens dans un hachage; ils ne sont que vaguement liés. Un nom, une adresse e-mail et un numéro de téléphone nécessaires à une fonction peuvent être regroupés dans un fichier Structou OpenStruct. Si ce nom, cette adresse e-mail et ce numéro de téléphone ont besoin de méthodes pour fournir le nom aux formats «First Last» et «Last, First», vous devez créer une classe pour le gérer.

Pesto
la source
49
"mais ils n'ont pas les méthodes d'instance comme le ferait une classe". eh bien, il existe un modèle assez courant pour l'utiliser comme "classe normale":class Point < Struct.new(:x, :y); methods here; end
tokland
10
@tokland comme pour aujourd'hui, l'approche "préférée" de la personnalisation de la structure avec des méthodes est de passer le bloc au constructeur Point = Struct.new(:x, :y) { methods here }. ( source ) Bien sûr, { ... }il peut être écrit sous forme de bloc multi-lignes ( do ... end) et, je pense, c'est la méthode préférée.
Ivan Kolmychek
1
@IvanKolmychek: Cool, en fait je préfère l'approche par bloc.
tokland
@tokland bien. Je voulais juste préciser que maintenant il y a une approche plus agréable, étant donné que votre commentaire est fortement voté, donc les personnes qui découvrent ruby ​​peuvent réellement penser "OK, alors c'est comme ça que cela devrait être fait, parce que tout le monde est d'accord avec ça, non ? " :)
Ivan Kolmychek
4
Une question: une fois arrivé au moment où vous souhaitez ajouter des méthodes à votre structure, pourquoi ne pas utiliser une classe?
jaydel
82

Autre repère:

require 'benchmark'
require 'ostruct'

REP = 100000

User = Struct.new(:name, :age)

USER = "User".freeze
AGE = 21
HASH = {:name => USER, :age => AGE}.freeze

Benchmark.bm 20 do |x|
  x.report 'OpenStruct slow' do
    REP.times do |index|
       OpenStruct.new(:name => "User", :age => 21)
    end
  end

  x.report 'OpenStruct fast' do
    REP.times do |index|
       OpenStruct.new(HASH)
    end
  end

  x.report 'Struct slow' do
    REP.times do |index|
       User.new("User", 21)
    end
  end

  x.report 'Struct fast' do
    REP.times do |index|
       User.new(USER, AGE)
    end
  end
end

Pour les impatients qui veulent se faire une idée des résultats du benchmark, sans les exécuter eux-mêmes, voici la sortie du code ci-dessus (sur un MB Pro 2.4GHz i7)

                          user     system      total        real
OpenStruct slow       4.430000   0.250000   4.680000 (  4.683851)
OpenStruct fast       4.380000   0.270000   4.650000 (  4.649809)
Struct slow           0.090000   0.000000   0.090000 (  0.094136)
Struct fast           0.080000   0.000000   0.080000 (  0.078940)
Robert Klemme
la source
5
avec ruby ​​2.14, la différence est plus petite de 0,94-0,97 avec OpenStruct vs 0,02-0,03 avec Ostruct (MB Pro 2.2Ghz i7)
basex
1
OpenStruct est équivalent en vitesse à l'utilisation de Struct. Voir stackoverflow.com/a/43987844/128421 .
the Tin Man
57

METTRE À JOUR:

Depuis Ruby 2.4.1, OpenStruct et Struct sont beaucoup plus rapides. Voir https://stackoverflow.com/a/43987844/128421

PRÉCÉDEMMENT:

Par souci d' exhaustivité: Struct vs Class vs Hash vs OpenStruct

Exécution d'un code similaire à celui de Burtlo, sur Ruby 1.9.2, (1 des 4 cœurs x86_64, 8 Go de RAM) [table modifiée pour aligner les colonnes]:

création de 1 Mio Structs: 1,43 sec, 219 Mo / 90 Mo (virt / res)
création d'instances de 1 Mio Class: 1,43 s, 219 Mo / 90 Mo (virt / res)
création de 1 million de hachages: 4,46 s, 493 Mo / 364 Mo (virt / res)
création de 1 Mio OpenStructs: 415,13 s, 2464 Mo / 2,3 Go (virt / res) # ~ 100x plus lent que les hachages
création d'OpenStructs 100K: 10,96 s, 369 Mo / 242 Mo (virt / res)

Les OpenStructs sont sloooooow et gourmands en mémoire , et ne s'adaptent pas bien aux grands ensembles de données

Créer 1 Mio OpenStructs est environ 100 fois plus lent que créer 1 Mio Hashes .

start = Time.now

collection = (1..10**6).collect do |i|
  {:name => "User" , :age => 21}
end; 1

stop = Time.now

puts "#{stop - start} seconds elapsed"
Tilo
la source
Informations très utiles pour les accros de la performance comme moi. Merci.
Bernardo Oliveira du
Je fais référence à la mise en œuvre de Ruby par Matz (MRI)
Tilo
1
Salut @Tilo, pourriez-vous partager votre code pour obtenir les résultats ci-dessus? Je veux l'utiliser pour comparer Struct & OStruct avec Hashie :: Mash. Merci.
Donny Kurnia
1
Hey @Donny, je viens de voir le vote positif et je me suis rendu compte que cela avait été mesuré en 2011 - je dois le relancer avec Ruby 2.1: P je ne sais pas si j'ai ce code, mais il devrait être simple à reproduire. J'essaierai de le réparer bientôt.
Tilo
2
Depuis Ruby 2.4.1, OpenStruct et Struct sont beaucoup plus rapides. Voir stackoverflow.com/a/43987844/128421
The Tin Man
34

Les cas d'utilisation des deux sont assez différents.

Vous pouvez considérer la classe Struct dans Ruby 1.9 comme un équivalent à la structdéclaration en C. Dans Ruby Struct.newprend un ensemble de noms de champs comme arguments et renvoie une nouvelle classe. De même, en C, une structdéclaration prend un ensemble de champs et permet au programmeur d'utiliser le nouveau type complexe comme il le ferait avec n'importe quel type intégré.

Rubis:

Newtype = Struct.new(:data1, :data2)
n = Newtype.new

C:

typedef struct {
  int data1;
  char data2;
} newtype;

newtype n;

La classe OpenStruct peut être comparée à une déclaration struct anonyme en C. Elle permet au programmeur de créer une instance de type complexe.

Rubis:

o = OpenStruct.new(data1: 0, data2: 0) 
o.data1 = 1
o.data2 = 2

C:

struct {
  int data1;
  char data2;
} o;

o.data1 = 1;
o.data2 = 2;

Voici quelques cas d'utilisation courants.

OpenStructs peut être utilisé pour convertir facilement les hachages en objets uniques qui répondent à toutes les clés de hachage.

h = { a: 1, b: 2 }
o = OpenStruct.new(h)
o.a = 1
o.b = 2

Les structures peuvent être utiles pour les définitions de classes abrégées.

class MyClass < Struct.new(:a,:b,:c)
end

m = MyClass.new
m.a = 1
skryl
la source
3
C'est une excellente réponse à la différence conceptuelle entre eux. Merci d'avoir souligné l'anonymat d'OpenStruct, j'ai l'impression que cela le rend beaucoup plus clair.
bryant
Excellente explication!
Yuri Ghensev
24

Les OpenStructs utilisent beaucoup plus de mémoire et sont plus lents que les Structs.

require 'ostruct' 

collection = (1..100000).collect do |index|
   OpenStruct.new(:name => "User", :age => 21)
end

Sur mon système, le code suivant s'est exécuté en 14 secondes et a consommé 1,5 Go de mémoire. Votre kilométrage peut varier:

User = Struct.new(:name, :age)

collection = (1..100000).collect do |index|
   User.new("User",21)
end

Cela s'est terminé presque instantanément et a consommé 26,6 Mo de mémoire.

Burtlo
la source
3
Mais vous savez que le test OpenStruct crée beaucoup de hachages temporaires. Je suggère un benchmark légèrement modifié - qui soutient toujours votre verdict (voir ci-dessous).
Robert Klemme
6

Struct:

>> s = Struct.new(:a, :b).new(1, 2)
=> #<struct a=1, b=2>
>> s.a
=> 1
>> s.b
=> 2
>> s.c
NoMethodError: undefined method `c` for #<struct a=1, b=2>

OpenStruct:

>> require 'ostruct'
=> true
>> os = OpenStruct.new(a: 1, b: 2)
=> #<OpenStruct a=1, b=2>
>> os.a
=> 1
>> os.b
=> 2
>> os.c
=> nil
Dorian
la source
Merci pour l'exemple. Cela aide beaucoup à comprendre dans la pratique.
Ahsan
3

En utilisant le code @Robert, j'ajoute Hashie :: Mash à l'élément de référence et j'ai obtenu ce résultat:

                           user     system      total        real
Hashie::Mash slow      3.600000   0.000000   3.600000 (  3.755142)
Hashie::Mash fast      3.000000   0.000000   3.000000 (  3.318067)
OpenStruct slow       11.200000   0.010000  11.210000 ( 12.095004)
OpenStruct fast       10.900000   0.000000  10.900000 ( 12.669553)
Struct slow            0.370000   0.000000   0.370000 (  0.470550)
Struct fast            0.140000   0.000000   0.140000 (  0.145161)
Donny Kurnia
la source
Votre référence est vraiment étrange. J'ai obtenu le résultat suivant avec ruby2.1.1 sur un mac i5: gist.github.com/nicolas-besnard/…
cappie013
Eh bien, le résultat variera entre la version ruby ​​utilisée et le matériel utilisé pour l'exécuter. Mais le schéma est toujours le même, OpenStruct est le plus lent, Struct est le plus rapide. Hashie tombe au milieu.
Donny Kurnia
0

Pas vraiment une réponse à la question, mais une considération très importante si vous vous souciez de la performance . Veuillez noter qu'à chaque fois que vous créez une OpenStructopération, l'opération efface le cache de méthode, ce qui signifie que votre application fonctionnera plus lentement. La lenteur ou non de ne OpenStructconcerne pas seulement la façon dont cela fonctionne par lui-même, mais les implications que leur utilisation apporte à l'ensemble de l'application: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Cris R
la source