Quando devo usar o Struct vs. OpenStruct?


184

Em geral, quais são as vantagens e desvantagens de usar um OpenStruct em comparação com um Struct? Que tipo de casos de uso geral se encaixaria em cada um deles?


1
Tenho algumas observações sobre Struct vs. OpenStruct vs. Hash no meu recente comentário no blog "Structs inside out" , apenas no caso de alguém estar interessado.
11119 Robert Klemme

As informações sobre velocidade do Hash, Struct e OpenStruct estão desatualizadas. Consulte stackoverflow.com/a/43987844/128421 para obter uma referência mais recente.
the Tin Man

Respostas:


173

Com um OpenStruct, você pode criar arbitrariamente atributos. UMAStruct , por outro lado, deve ter seus atributos definidos quando você o cria. A escolha de um sobre o outro deve basear-se principalmente em se você precisa adicionar atributos posteriormente.

A maneira de pensar sobre eles é como o meio termo do espectro entre os Hashes de um lado e as classes do outro. Elas implicam uma relação mais concreta entre os dados do que a Hash, mas não possuem os métodos de instância como uma classe. Um monte de opções para uma função, por exemplo, faz sentido em um hash; eles são apenas vagamente relacionados. Um nome, email e número de telefone necessários para uma função podem ser empacotados juntos em um Structou OpenStruct. Se esse nome, email e número de telefone precisassem de métodos para fornecer o nome nos formatos "Primeiro Último" e "Último, Primeiro", você deverá criar uma classe para lidar com isso.


49
"mas eles não têm os métodos de instância como teriam uma classe". bem, existe um padrão bastante comum para usá-lo como uma "classe normal":class Point < Struct.new(:x, :y); methods here; end
tokland

10
@tokland hoje, a abordagem "preferida" de personalizar a estrutura com métodos é passar o bloco para o construtor Point = Struct.new(:x, :y) { methods here }. ( fonte ) Obviamente, { ... }pode ser escrito como um bloco de várias linhas ( do ... end) e, eu acho, essa é a maneira preferida.
precisa saber é o seguinte

1
@IvanKolmychek: Legal, na verdade eu prefiro a abordagem de blocos.
tokland

@tokland good. Eu só queria esclarecer que agora existe uma abordagem melhor, visto que seu comentário é altamente votado, então, as pessoas novas no ruby ​​podem realmente pensar "OK, então é assim que deve ser feito, porque todos concordam com isso, certo ? " :)
Ivan Kolmychek

4
Uma pergunta: quando você chega no momento em que deseja adicionar métodos à sua estrutura, por que não usar uma classe?
jaydel

82

Outra referência:

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

Para o impaciente que deseja ter uma idéia dos resultados do benchmark, sem executá-los, eis a saída do código acima (em um 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
com rubi 2,14 a diferença é menor 0,94-0,97 com OpenStruct vs 0,02-0,03 com Ostruct (MB Pro 2.2GHz i7)
Basex

1
OpenStruct é equivalente em velocidade ao uso de Struct. Consulte stackoverflow.com/a/43987844/128421 .
the Tin Man

57

ATUALIZAR:

A partir do Ruby 2.4.1, OpenStruct e Struct estão muito mais próximos em velocidade. Consulte https://stackoverflow.com/a/43987844/128421

ANTERIORMENTE:

Para completar: Struct vs. Class vs. Hash vs. OpenStruct

Executando código semelhante ao do burtlo, no Ruby 1.9.2, (1 de 4 núcleos x86_64, 8 GB de RAM) [tabela editada para alinhar colunas]:

criando 1 Mio Structs: 1,43 s, 219 MB / 90 MB (virt / res)
criando 1 instância da classe Mio: 1,43 s, 219 MB / 90 MB (virt / res)
criando 1 Mio Hashes: 4,46 s, 493 MB / 364 MB (virt / res)
criando 1 Mio OpenStructs: 415,13 s, 2464 MB / 2,3 GB (virt / res) # ~ 100x mais lento que o Hashes
criando 100K OpenStructs: 10,96 s, 369 MB / 242 MB (virt / res)

Os OpenStructs consomem muita memória e muita memória e não se adaptam bem a grandes conjuntos de dados

Criar 1 Mio OpenStructs é ~ 100x mais lento que criar 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"

Informações muito úteis para viciados em desempenho como eu. Obrigado.
Bernardo Oliveira

Estou me referindo a implementação do Matz de Ruby (MRI)
Tilo

1
Olá @Tilo, você poderia compartilhar seu código para obter os resultados acima? Eu quero usá-lo para comparar Struct & OStruct com Hashie :: Mash. Obrigado.
Donny Kurnia

1
Hey @Donny, acabei de ver o voto positivo e percebi que isso foi medido em 2011 - preciso repetir isso com o Ruby 2.1: P não tenho certeza se tenho esse código, mas deve ser simples de reproduzir. Vou tentar consertar isso em breve.
Tilo 15/02

2
A partir do Ruby 2.4.1, OpenStruct e Struct estão muito mais próximos em velocidade. Veja stackoverflow.com/a/43987844/128421
the Tin Man

34

Os casos de uso para os dois são bem diferentes.

Você pode pensar na classe Struct no Ruby 1.9 como equivalente à structdeclaração em C. No Ruby, Struct.newum conjunto de nomes de campos é argumentos e retorna uma nova classe. Da mesma forma, em C, umstruct declaração utiliza um conjunto de campos e permite que o programador use o novo tipo complexo, como faria com qualquer tipo interno.

Rubi:

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

C:

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

newtype n;

A classe OpenStruct pode ser comparada a uma declaração de estrutura anônima em C. Ele permite que o programador crie uma instância de um tipo complexo.

Rubi:

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;

Aqui estão alguns casos de uso comuns.

O OpenStructs pode ser usado para converter facilmente hashes em objetos únicos que respondem a todas as chaves de hash.

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

Estruturas podem ser úteis para definições de classes abreviadas.

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

m = MyClass.new
m.a = 1

3
Esta é uma ótima resposta da diferença conceitual entre eles. Obrigado por apontar o anonimato do OpenStruct, sinto que isso fica muito mais claro.
Bryant

Ótima explicação!
Yuri Ghensev 7/02

24

O OpenStructs usa significativamente mais memória e apresenta desempenho mais lento do que o Structs.

require 'ostruct' 

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

No meu sistema, o código a seguir foi executado em 14 segundos e consumiu 1,5 GB de memória. Sua milhagem pode variar:

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

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

Isso terminou quase instantaneamente e consumiu 26,6 MB de memória.


3
Mas você está ciente de que o teste OpenStruct cria muitos hashes temporários. Sugiro uma referência ligeiramente modificada - que ainda suporta seu veredicto (veja abaixo).
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

Obrigado pelo exemplo. Ajuda muito a entender na prática.
Ahsan


3

Usando o código @Robert, adiciono Hashie :: Mash ao item de referência e obtive este resultado:

                           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)

Sua referência é realmente estranha. Eu obtive o seguinte resultado com o ruby2.1.1 em um mac i5: gist.github.com/nicolas-besnard/…
cappie013

Bem, o resultado varia entre a versão ruby ​​usada e o hardware usado para executá-la. Mas o padrão ainda é o mesmo, o OpenStruct é o mais lento, o Struct é o mais rápido. Hashie cai no meio.
Donny Kurnia

0

Na verdade, não é uma resposta para a pergunta, mas uma consideração muito importante se você se importa com o desempenho . Observe que toda vez que você cria uma OpenStructoperação, o cache do método é limpo, o que significa que seu aplicativo terá um desempenho mais lento. A lentidão ou não não OpenStructé apenas sobre como ela funciona sozinha, mas as implicações que as utilizam trazem para todo o aplicativo: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.