Wann sollte ich Struct vs. OpenStruct verwenden?

184

Welche Vor- und Nachteile hat die Verwendung eines OpenStruct im Vergleich zu einer Struktur im Allgemeinen? Welche Art von allgemeinen Anwendungsfällen würde zu jedem dieser Fälle passen?

ehsanul
quelle
1
Ich habe ein paar Anmerkungen zu Struct vs. OpenStruct vs. Hash in meinem letzten Blog-Kommentar "Structs inside out" , nur für den Fall, dass jemand interessiert ist.
Robert Klemme
Die Informationen zur Geschwindigkeit von Hash, Struct und OpenStruct sind veraltet. Eine aktuellere Benchmark finden Sie unter stackoverflow.com/a/43987844/128421 .
Der Blechmann

Antworten:

172

Mit einem OpenStructkönnen Sie beliebig Attribute erstellen. Bei A Structhingegen müssen die Attribute beim Erstellen definiert werden. Die Auswahl eines über das andere sollte in erster Linie davon abhängen, ob Sie später Attribute hinzufügen müssen.

Die Art, über sie nachzudenken, ist der Mittelweg des Spektrums zwischen Hashes auf der einen Seite und Klassen auf der anderen Seite. Sie implizieren eine konkretere Beziehung zwischen den Daten als a Hash, haben jedoch nicht die Instanzmethoden wie eine Klasse. Eine Reihe von Optionen für eine Funktion sind beispielsweise in einem Hash sinnvoll. Sie sind nur lose verwandt. Ein Name, eine E-Mail-Adresse und eine Telefonnummer, die von einer Funktion benötigt werden, können in einem Structoder zusammengefasst werden OpenStruct. Wenn für diesen Namen, diese E-Mail-Adresse und diese Telefonnummer Methoden erforderlich sind, um den Namen in den Formaten "First Last" und "Last, First" bereitzustellen, sollten Sie eine Klasse erstellen, um damit umzugehen.

Pesto
quelle
49
"Aber sie haben nicht die Instanzmethoden wie eine Klasse". class Point < Struct.new(:x, :y); methods here; end
Nun
10
@tokland wie heute besteht der "bevorzugte" Ansatz zum Anpassen der Struktur mit Methoden darin, den Block an den Konstruktor zu übergeben Point = Struct.new(:x, :y) { methods here }. ( Quelle ) Natürlich { ... }kann es als mehrzeiliger Block ( do ... end) geschrieben werden, und ich denke, das ist der bevorzugte Weg.
Ivan Kolmychek
1
@IvanKolmychek: Cool, eigentlich bevorzuge ich den Blockansatz.
tokland
@tokland gut. Ich wollte nur klarstellen, dass es jetzt einen schöneren Ansatz gibt, da Ihr Kommentar hoch bewertet ist, sodass Leute, die neu bei Ruby sind, tatsächlich denken können: "OK, so sollte es gemacht werden, denn alle sind damit einverstanden, richtig ? " :)
Ivan Kolmychek
4
Eine Frage: Wenn Sie zu dem Zeitpunkt angekommen sind, an dem Sie Ihrer Struktur Methoden hinzufügen möchten, können Sie eine Klasse verwenden.
Jaydel
82

Andere 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

Für Ungeduldige, die sich ein Bild von den Benchmark-Ergebnissen machen möchten, ohne sie selbst auszuführen, ist hier die Ausgabe des obigen Codes (auf einem 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
quelle
5
mit Ruby 2.14 ist der Unterschied kleiner 0.94-0.97 mit OpenStruct vs 0.02-0.03 mit Ostruct (MB Pro 2.2Ghz i7)
basex
1
OpenStruct entspricht in seiner Geschwindigkeit der Verwendung von Struct. Siehe stackoverflow.com/a/43987844/128421 .
Der Blechmann
57

AKTUALISIEREN:

Ab Ruby 2.4.1 sind OpenStruct und Struct viel schneller. Siehe https://stackoverflow.com/a/43987844/128421

VORHER:

Der Vollständigkeit halber : Struct vs. Class vs. Hash vs. OpenStruct

Ausführen von ähnlichem Code wie bei burtlo unter Ruby 1.9.2 (1 von 4 Kernen x86_64, 8 GB RAM) [Tabelle bearbeitet, um Spalten auszurichten]:

Erstellen von 1 Mio. Strukturen: 1,43 Sek., 219 MB / 90 MB (virt / res)
Erstellen von Instanzen der 1-Mio-Klasse: 1,43 Sek., 219 MB / 90 MB (virt / res)
Erstellen von 1 Mio Hashes: 4,46 Sek., 493 MB / 364 MB (virt / res)
Erstellen von 1 Mio OpenStructs: 415,13 Sek., 2464 MB / 2,3 GB (virt / res) # ~ 100x langsamer als Hashes
Erstellen von 100K OpenStructs: 10,96 Sek., 369 MB / 242 MB (virt / res)

OpenStructs sind langsam und speicherintensiv und lassen sich für große Datenmengen nicht gut skalieren

Das Erstellen von 1 Mio OpenStructs ist ~ 100x langsamer als das Erstellen von 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
quelle
Sehr nützliche Informationen für Leistungssüchtige wie mich. Vielen Dank.
Bernardo Oliveira
Ich beziehe mich auf Matz 'Implementierung von Ruby (MRI)
Tilo
1
Hallo @Tilo, könnten Sie Ihren Code teilen, um die obigen Ergebnisse zu erhalten? Ich möchte damit Struct & OStruct mit Hashie :: Mash vergleichen. Vielen Dank.
Donny Kurnia
1
Hey @Donny, ich habe gerade die positive Bewertung gesehen und festgestellt, dass dies im Jahr 2011 gemessen wurde. Ich muss dies mit Ruby 2.1: P erneut ausführen. Ich bin mir nicht sicher, ob ich diesen Code habe, aber er sollte einfach zu reproduzieren sein. Ich werde versuchen, es bald zu beheben.
Tilo
2
Ab Ruby 2.4.1 sind OpenStruct und Struct viel schneller. Siehe stackoverflow.com/a/43987844/128421
der Blechmann
34

Die Anwendungsfälle für die beiden sind sehr unterschiedlich.

Sie können sich die Struct-Klasse in Ruby 1.9 als Äquivalent zur structDeklaration in C vorstellen . In Ruby werden Struct.neweine Reihe von Feldnamen als Argumente verwendet und eine neue Klasse zurückgegeben. In ähnlicher Weise structnimmt eine Deklaration in C eine Reihe von Feldern auf und ermöglicht es dem Programmierer, den neuen komplexen Typ wie jeden integrierten Typ zu verwenden.

Rubin:

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

C:

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

newtype n;

Die OpenStruct-Klasse kann mit einer anonymen Strukturdeklaration in C verglichen werden. Dadurch kann der Programmierer eine Instanz eines komplexen Typs erstellen .

Rubin:

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;

Hier sind einige häufige Anwendungsfälle.

OpenStructs können verwendet werden, um Hashes einfach in einmalige Objekte zu konvertieren, die auf alle Hash-Schlüssel reagieren.

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

Strukturen können für Kurzklassendefinitionen nützlich sein.

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

m = MyClass.new
m.a = 1
skryl
quelle
3
Dies ist eine großartige Antwort auf den konzeptuellen Unterschied zwischen ihnen. Vielen Dank, dass Sie auf die Anonymität des OpenStruct hingewiesen haben. Ich denke, das macht es viel klarer.
Bryant
Tolle Erklärung!
Yuri Ghensev
24

OpenStructs verbrauchen deutlich mehr Speicher und sind langsamer als Structs.

require 'ostruct' 

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

Auf meinem System wurde der folgende Code in 14 Sekunden ausgeführt und verbrauchte 1,5 GB Speicher. Ihr Kilometerstand kann variieren:

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

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

Das endete fast augenblicklich und verbrauchte 26,6 MB Speicher.

burtlo
quelle
3
Sie wissen jedoch, dass der OpenStruct-Test viele temporäre Hashes erstellt. Ich schlage einen leicht modifizierten Benchmark vor, der Ihr Urteil noch stützt (siehe unten).
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
quelle
Danke für das Beispiel. Es hilft sehr, in der Praxis zu verstehen.
Ahsan
3

Mit @ Robert-Code füge ich Hashie :: Mash zum Benchmark-Element hinzu und erhalte das folgende Ergebnis:

                           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
quelle
Ihr Benchmark ist wirklich seltsam. Ich habe das folgende Ergebnis mit ruby2.1.1 auf einem i5-Mac erhalten: gist.github.com/nicolas-besnard/…
cappie013
Nun, das Ergebnis variiert zwischen der verwendeten Ruby-Version und der zum Ausführen verwendeten Hardware. Das Muster ist jedoch immer noch dasselbe, OpenStruct ist das langsamste, Struct das schnellste. Hashie fällt in die Mitte.
Donny Kurnia
0

Eigentlich keine Antwort auf die Frage, aber eine sehr wichtige Überlegung, wenn Sie Wert auf Leistung legen . Bitte beachten Sie, dass jedes Mal, wenn Sie einen OpenStructVorgang erstellen, der Methodencache geleert wird, was bedeutet, dass Ihre Anwendung langsamer arbeitet. Bei der Langsamkeit oder Nicht-Langsamkeit OpenStructgeht es nicht nur darum, wie es von selbst funktioniert, sondern auch um die Auswirkungen , die die Verwendung auf die gesamte Anwendung hat: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Cris R.
quelle