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érale conviendrait à chacun d'entre eux?
Avec un OpenStruct
, vous pouvez créer des attributs de manière arbitraire. Un Struct
, en revanche, doit avoir ses attributs définis lors de sa création. Le choix de l’un par rapport à l’autre doit reposer principalement sur la nécessité d’ajouter des attributs ultérieurement.
La façon de les considérer est le juste 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 qu'un Hash
, mais ils ne disposent pas des méthodes d'instance comme le ferait une classe. Un ensemble d'options pour une fonction, par exemple, a un sens dans un hachage; ils ne sont que vaguement liés. Un nom, une adresse électronique et un numéro de téléphone requis par une fonction peuvent être regroupés dans un fichier Struct
ou OpenStruct
. Si ce nom, cet email et ce numéro de téléphone nécessitaient des méthodes pour fournir le nom dans les formats "Premier Dernier" et "Dernier, Premier", vous devez créer une classe pour le gérer.
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 avoir 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)
UPDATE:
A partir de Ruby 2.4.1 OpenStruct et Struct sont beaucoup plus rapides en vitesse. Voir https://stackoverflow.com/a/43987844/128421
PRECEDENT:
Pour être complet: Struct vs. Classe vs. Hachage vs OpenStruct
Exécution d'un code similaire à celui de burtlo, on Ruby 1.9.2, (un des 4 cœurs x86_64, 8 Go de RAM)) [tableau modifié pour aligner les colonnes]:
création de 1 Mio Structs: 1,43 s, 219 Mo/90MB (virt/res) création de 1 Mio instances de classe: 1,43 s, 219 Mo/90 Mo (virt/res) création de 1 Mio Hashes: 4,46 secondes, 493 Mo/virtuels (virt/res) création de 1 Mio OpenStructs: 415,13 s, 2464 Mo/2,3 Go (virtuels/res) # ~ 100x plus lent que Hash création de 100K OpenStructs: 10,96 s, 369 Mo/242 Mo (virt/res)
OpenStructs utilise sloooooow et une mémoire intensive , et ne s'adapte pas correctement aux grands ensembles de données
La création de 1 Mio OpenStructs est ~ 100x plus lente que la création de 1 Mio hachages .
start = Time.now
collection = (1..10**6).collect do |i|
{:name => "User" , :age => 21}
end; 1
stop = Time.now
puts "#{stop - start} seconds elapsed"
Les cas d'utilisation pour les deux sont assez différents.
Vous pouvez considérer la classe Struct dans Ruby 1.9 comme un équivalent de la déclaration struct
en C. Dans Ruby Struct.new
prend un ensemble de noms de champs comme arguments et retourne une nouvelle classe. De même, en C, une déclaration struct
prend un ensemble de champs et permet au programmeur d'utiliser le nouveau type complexe comme il le ferait pour tout 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 de structure anonyme en C. Elle permet au programmeur de créer une instance d'un 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 des hachages en objets uniques répondant à 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
OpenStructs utilise beaucoup plus de mémoire et est plus lent que Struct.
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.
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
Regardez l'API en ce qui concerne la nouvelle méthode. On y trouve beaucoup de différences.
Personnellement, j'aime bien OpenStruct, car je n'ai pas à définir la structure de l'objet à l'avance, mais à ajouter des éléments à ma guise. Je suppose que ce serait son principal (dis) avantage?
À l'aide du code @Robert, j'ai ajouté Hashie :: Mash à l'élément de référence et obtenu le résultat suivant:
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)