Quando dovrei usare Struct vs. OpenStruct?


184

In generale, quali sono i vantaggi e gli svantaggi dell'utilizzo di OpenStruct rispetto a Struct? Che tipo di casi d'uso generali si adatterebbe a ciascuno di questi?


1
Ho alcune osservazioni su Struct vs. OpenStruct vs. Hash nel mio recente commento sul blog "Strutture al rovescio" , nel caso in cui qualcuno fosse interessato.
Robert Klemme,

Le informazioni relative alla velocità di Hash, Struct e OpenStruct non sono aggiornate. Consulta stackoverflow.com/a/43987844/128421 per un benchmark più recente.
Tin Man,

Risposte:


173

Con un OpenStruct, puoi creare arbitrariamente attributi. A Struct, invece, devono avere i suoi attributi definiti quando lo si crea. La scelta dell'una sull'altra dovrebbe basarsi principalmente sulla necessità di poter aggiungere attributi in un secondo momento.

Il modo di pensarci è come la via di mezzo tra gli hash da un lato e le classi dall'altro. Implica una relazione più concreta tra i dati rispetto a una Hash, ma non ha i metodi di istanza come farebbe una classe. Un gruppo di opzioni per una funzione, ad esempio, ha un senso in un hash; sono solo vagamente imparentati. Un nome, un indirizzo e-mail e un numero di telefono necessari a una funzione possono essere raggruppati in un Structo OpenStruct. Se quel nome, e-mail e numero di telefono necessitavano di metodi per fornire il nome in entrambi i formati "Primo ultimo" e "Ultimo, primo", allora dovresti creare una classe per gestirlo.


49
"ma non hanno i metodi di istanza come una classe". beh, c'è un modello abbastanza comune per usarlo come una "classe normale":class Point < Struct.new(:x, :y); methods here; end
tokland

10
@tokland come per oggi, l'approccio "preferito" per personalizzare la struttura con i metodi è passare il blocco al costruttore Point = Struct.new(:x, :y) { methods here }. ( fonte ) Naturalmente, { ... }ci può essere scritto come un blocco multi-linea ( do ... end) e, penso, questo è il modo preferito.
Ivan Kolmychek,

1
@IvanKolmychek: Fantastico, in realtà preferisco l'approccio a blocchi.
tokland

@tokland good. Volevo solo chiarire che ora c'è un approccio migliore, visto che il tuo commento è molto votato, quindi le persone nuove a Ruby possono effettivamente pensare "OK, quindi è così che dovrebbe essere fatto, perché tutti sono d'accordo con quello, giusto ?" :)
Ivan Kolmychek,

4
Una domanda: una volta arrivato nel momento in cui desideri aggiungere metodi alla tua struttura, perché non usare una classe?
Jaydel,

82

Altro benchmark:

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

Per l'impaziente che vuole farsi un'idea dei risultati del benchmark, senza eseguirli da soli, ecco l'output del codice sopra (su 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)

5
con ruby ​​2.14 la differenza è minore 0.94-0.97 con OpenStruct vs 0.02-0.03 con Ostruct (MB Pro 2.2Ghz i7)
basex

1
OpenStruct è equivalente in termini di velocità all'utilizzo di Struct. Vedi stackoverflow.com/a/43987844/128421 .
Tin Man,

57

AGGIORNARE:

A partire da Ruby 2.4.1 OpenStruct e Struct sono molto più vicini nella velocità. Vedi https://stackoverflow.com/a/43987844/128421

IN PRECEDENZA:

Per completezza: Struct vs. Class vs. Hash vs. OpenStruct

Esecuzione di codice simile a quello di burtlo, su Ruby 1.9.2, (1 su 4 core x86_64, 8 GB RAM) [tabella modificata per allineare le colonne]:

creazione di 1 Mio Struct: 1,43 sec, 219 MB / 90 MB (virt / res)
creazione di 1 istanze di classe Mio: 1,43 sec, 219 MB / 90 MB (virt / res)
creazione di 1 Mio Hash: 4,46 sec, 493 MB / 364 MB (virt / res)
creazione di 1 Mio OpenStruct: 415.13 sec, 2464 MB / 2.3GB (virt / res) # ~ 100 volte più lento degli hash
creazione di 100.000 OpenStruct: 10,96 sec, 369 MB / 242 MB (virt / res)

OpenStruct richiede molta memoria e molta memoria e non si adatta bene a grandi set di dati

La creazione di 1 Mio OpenStructs è circa 100 volte più lenta della creazione di 1 Mio Hash .

start = Time.now

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

stop = Time.now

puts "#{stop - start} seconds elapsed"

Informazioni molto utili per i fanatici della performance come me. Grazie.
Bernardo Oliveira,

Mi riferisco all'implementazione di Matz di Ruby (MRI)
Tilo il

1
Ciao @Tilo, potresti condividere il tuo codice per ottenere i risultati sopra? Voglio usarlo per confrontare Struct & OStruct con Hashie :: Mash. Grazie.
Donny Kurnia,

1
Ehi @Donny, ho appena visto il voto e ho capito che questo è stato misurato nel 2011 - Devo rieseguirlo con Ruby 2.1: P non sono sicuro di avere quel codice, ma dovrebbe essere semplice da riprodurre. Proverò a risolverlo presto.
Tilo,

2
A partire da Ruby 2.4.1 OpenStruct e Struct sono molto più vicini nella velocità. Vedi stackoverflow.com/a/43987844/128421
Tin Man,

34

I casi d'uso per i due sono abbastanza diversi.

Puoi pensare alla classe Struct in Ruby 1.9 come equivalente alla structdichiarazione in C. In Ruby Struct.newaccetta una serie di nomi di campo come argomenti e restituisce una nuova classe. Allo stesso modo, in C, una structdichiarazione accetta una serie di campi e consente al programmatore di utilizzare il nuovo tipo complesso proprio come farebbe con qualsiasi tipo incorporato.

Rubino:

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

C:

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

newtype n;

La classe OpenStruct può essere confrontata con una dichiarazione di struttura anonima in C. Permette al programmatore di creare un'istanza di un tipo complesso.

Rubino:

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;

Ecco alcuni casi d'uso comuni.

OpenStructs può essere utilizzato per convertire facilmente hash in oggetti unici che rispondono a tutte le chiavi hash.

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

Le strutture possono essere utili per le definizioni delle classi abbreviate.

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

m = MyClass.new
m.a = 1

3
Questa è un'ottima risposta alla differenza concettuale tra loro. Grazie per aver sottolineato l'anonimato di OpenStruct, mi sembra che lo renda molto più chiaro.
Bryant,

Grande spiegazione!
Yuri Ghensev,

24

OpenStruct utilizza molta più memoria e ha prestazioni più lente rispetto a Struct.

require 'ostruct' 

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

Sul mio sistema il seguente codice è stato eseguito in 14 secondi e ha consumato 1,5 GB di memoria. Il chilometraggio può variare:

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

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

Ciò è terminato quasi istantaneamente e ha consumato 26,6 MB di memoria.


3
Ma sei consapevole del fatto che il test OpenStruct crea molti hash temporanei. Suggerisco un benchmark leggermente modificato, che supporta ancora il tuo verdetto (vedi sotto).
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

Grazie per l'esempio Aiuta molto a capire in pratica.
Ahsan,


3

Usando il codice @Robert, aggiungo Hashie :: Mash all'elemento di riferimento e ho ottenuto questo risultato:

                           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)

Il tuo benchmark è davvero strano. Ho ottenuto il seguente risultato con ruby2.1.1 su un Mac i5: gist.github.com/nicolas-besnard/…
cappie013,

Bene, il risultato varierà tra la versione ruby ​​utilizzata e l'hardware utilizzato per eseguirla. Ma lo schema è sempre lo stesso, OpenStruct è il più lento, Struct è il più veloce. Hashie cade nel mezzo.
Donny Kurnia,

0

In realtà non è una risposta alla domanda, ma una considerazione molto importante se ti preoccupi delle prestazioni . Si noti che ogni volta che si crea OpenStructun'operazione cancella la cache del metodo, il che significa che l'applicazione funzionerà più lentamente. La lentezza o meno OpenStructnon riguarda solo il modo in cui funziona da solo, ma le implicazioni che il loro utilizzo porta all'intera applicazione: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.