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?

ehsanul
fonte
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.

Pesto
fonte
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)
Robert Klemme
fonte
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"
Tilo
fonte
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
skryl
fonte
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.

Burtlo
fonte
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
Dorian
fonte
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)
Donny Kurnia
fonte
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

Cris R
fonte