Kiedy powinienem używać Struct vs. OpenStruct?

184

Jakie są zalety i wady korzystania z OpenStruct w porównaniu do Struct? Jaki rodzaj ogólnych przypadków użycia pasowałby do każdego z nich?

ehsanul
źródło
1
Mam kilka uwag na temat Struct vs. OpenStruct vs. Hash w moim niedawnym komentarzu na blogu „Struktury odwrócone” , na wypadek, gdyby ktoś był zainteresowany.
Robert Klemme,
Informacje dotyczące szybkości Hash, Struct i OpenStruct są nieaktualne. Zobacz najnowszy test porównawczy stackoverflow.com/a/43987844/128421 .
Tin Man

Odpowiedzi:

172

Za pomocą OpenStructmożesz dowolnie tworzyć atrybuty. Z Structdrugiej strony, podczas tworzenia należy zdefiniować jego atrybuty. Wybór jednego z pozostałych powinien opierać się przede wszystkim na tym, czy trzeba będzie później dodać atrybuty.

Sposób myślenia o nich jest środkiem pola między Hashes z jednej strony i klasami z drugiej. Implikują one bardziej konkretny związek między danymi niż a Hash, ale nie mają metod instancji, tak jak klasa. Na przykład wiele opcji dla funkcji ma sens w haszowaniu; są tylko luźno spokrewnione. Imię, adres e-mail i numer telefonu wymagane przez funkcję mogą być spakowane razem w Structlub OpenStruct. Jeśli ta nazwa, adres e-mail i numer telefonu wymagały metod, aby podać nazwę zarówno w formacie „First Last”, jak i „Last, First”, należy utworzyć klasę do obsługi tej nazwy.

pesto
źródło
49
„ale nie mają metod instancji, tak jak klasa”. cóż, istnieje dość powszechny wzorzec używania go jako „normalnej klasy”:class Point < Struct.new(:x, :y); methods here; end
tokland
10
@tokland na dzień dzisiejszy „preferowanym” podejściem do dostosowywania struktury metodami jest przekazanie bloku do konstruktora Point = Struct.new(:x, :y) { methods here }. ( źródło ) Oczywiście, { ... }można napisać jako blok wieloliniowy ( do ... end) i myślę, że jest to preferowany sposób.
Ivan Kolmychek
1
@IvanKolmychek: Cool, właściwie wolę podejście blokowe.
tokland
@tokland good. Chciałem tylko wyjaśnić, że teraz jest ładniejsze podejście, ponieważ twój komentarz jest wysoko oceniany, więc ludzie, którzy nie znają Rubiego, mogą naprawdę myśleć „OK, więc tak należy to zrobić, ponieważ wszyscy się z tym zgadzają, prawda ? :)
Ivan Kolmychek
4
Pytanie: kiedy już przyjedziesz, chcesz dodać metody do swojej struktury, dlaczego nie skorzystać z klasy?
jaydel
82

Inne punkty odniesienia:

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

Dla niecierpliwych, którzy chcą zorientować się w wynikach testu, bez uruchamiania ich samych, oto wynik powyższego kodu (na 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
źródło
5
z ruby ​​2.14 różnica jest mniejsza 0,94-0,97 z OpenStruct vs 0,02-0,03 z Ostruct (MB Pro 2.2Ghz i7)
basex
1
OpenStruct jest równoważny szybkością z użyciem Struct. Zobacz stackoverflow.com/a/43987844/128421 .
Tin Man
57

AKTUALIZACJA:

Począwszy od Ruby 2.4.1 OpenStruct i Struct są znacznie szybsze. Zobacz https://stackoverflow.com/a/43987844/128421

POPRZEDNIO:

Dla kompletności: Struct vs. Class vs. Hash vs. OpenStruct

Uruchamianie kodu podobnego do burtlo na Ruby 1.9.2, (1 z 4 rdzeni x86_64, 8 GB RAM) [tabela edytowana w celu wyrównania kolumn]:

tworzenie 1 Mio Struktur: 1,43 s, 219 MB / 90 MB (virt / res)
tworzenie instancji 1 Mio Class: 1,43 s, 219 MB / 90 MB (virt / res)
tworzenie 1 Mio Hashes: 4,46 s, 493 MB / 364 MB (virt / res)
tworzenie 1 Mio OpenStructs: 415,13 s, 2464 MB / 2,3 GB (virt / res) # ~ 100x wolniej niż Hashes
tworzenie 100K OpenStructs: 10,96 s, 369 MB / 242 MB (virt / res)

OpenStructs są bardzo wolne i wymagają dużej ilości pamięci i nie skalują się dobrze dla dużych zestawów danych

Utworzenie 1 Mio OpenStruct jest ~ 100x wolniejsze niż utworzenie 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
źródło
Bardzo przydatne informacje dla uzależnionych od wydajności, takich jak ja. Dzięki.
Bernardo Oliveira,
Mam na myśli implementację Rubza (MRI) przez Matza
Tilo
1
Cześć @Tilo, czy możesz udostępnić swój kod, aby uzyskać powyższe wyniki? Chcę go użyć do porównania Struct & OStruct z Hashie :: Mash. Dzięki.
Donny Kurnia
1
Hej, Donny, właśnie zobaczyłem opinię i zdałem sobie sprawę, że zmierzyłem to w 2011 roku - muszę ponownie uruchomić to w Ruby 2.1: P, nie jestem pewien, czy mam ten kod, ale powinno być łatwe do odtworzenia. Spróbuję to naprawić wkrótce.
Tilo
2
Począwszy od Ruby 2.4.1 OpenStruct i Struct są znacznie szybsze. Zobacz stackoverflow.com/a/43987844/128421
Tin Man
34

Przypadki użycia tych dwóch elementów są zupełnie inne.

Możesz myśleć o klasie Struct w Ruby 1.9 jako ekwiwalencie structdeklaracji w C. W Ruby Struct.newbierze zestaw nazw pól jako argumenty i zwraca nową klasę. Podobnie w C structdeklaracja przyjmuje zestaw pól i pozwala programiście używać nowego typu złożonego, tak jak każdego wbudowanego typu.

Rubin:

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

DO:

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

newtype n;

Klasę OpenStruct można porównać do anonimowej deklaracji struktury w C. Pozwala to programiście utworzyć instancję typu złożonego.

Rubin:

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

DO:

struct {
  int data1;
  char data2;
} o;

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

Oto kilka typowych przypadków użycia.

OpenStructs może być używany do łatwej konwersji skrótów na jednorazowe obiekty, które reagują na wszystkie klucze skrótu.

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

Struktury mogą być przydatne do definicji klas skrótowych.

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

m = MyClass.new
m.a = 1
skryl
źródło
3
To świetna odpowiedź na różnicę koncepcyjną między nimi. Dzięki za wskazanie anonimowości OpenStruct, wydaje mi się, że dzięki temu jest to o wiele bardziej jasne.
bryant
Świetne wyjaśnienie!
Jurij Ghensev,
24

OpenStruct zużywa znacznie więcej pamięci i działa wolniej w porównaniu do Struct.

require 'ostruct' 

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

W moim systemie następujący kod został wykonany w ciągu 14 sekund i zużył 1,5 GB pamięci. Twój przebieg może się różnić:

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

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

To zakończyło się niemal natychmiast i zużyło 26,6 MB pamięci.

Burtlo
źródło
3
Ale zdajesz sobie sprawę, że test OpenStruct tworzy wiele tymczasowych skrótów. Sugeruję nieco zmodyfikowany test porównawczy - który nadal wspiera twój werdykt (patrz poniżej).
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
dorycki
źródło
Dzięki za przykład. Bardzo pomaga zrozumieć w praktyce.
Ahsan,
3

Używając kodu @Robert, dodaję Hashie :: Mash do elementu testu i otrzymałem ten wynik:

                           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
źródło
Twój test porównawczy jest naprawdę dziwny. Mam następujący wynik z ruby2.1.1 na i5 mac: gist.github.com/nicolas-besnard/…
cappie013,
Wynik będzie różny w zależności od używanej wersji Ruby i sprzętu używanego do jej uruchomienia. Ale wzór jest nadal taki sam, OpenStruct jest najwolniejszy, Struct jest najszybszy. Hashie spada na środek.
Donny Kurnia
0

Nie jest to właściwie odpowiedź na pytanie, ale bardzo ważna uwaga, jeśli zależy Ci na wydajności . Zauważ, że za każdym razem, gdy tworzysz OpenStructoperację, pamięć podręczna metod jest czyszczona, co oznacza, że ​​aplikacja będzie działała wolniej. Powolność, czy nie, nie OpenStructpolega tylko na tym, jak to działa sama, ale implikacje, które przynoszą ich zastosowanie dla całej aplikacji: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Cris R.
źródło