¿Cuándo debo usar Struct vs. OpenStruct?

184

En general, ¿cuáles son las ventajas y desventajas de usar un OpenStruct en comparación con un Struct? ¿Qué tipo de casos de uso generales encajarían en cada uno de estos?

ehsanul
fuente
1
Tengo algunas observaciones sobre Struct vs. OpenStruct vs. Hash en mi reciente comentario en el blog "Structs inside out" , en caso de que alguien esté interesado.
Robert Klemme
La información sobre la velocidad de Hash, Struct y OpenStruct no está actualizada. Consulte stackoverflow.com/a/43987844/128421 para obtener un punto de referencia más reciente.
The Tin Man

Respuestas:

173

Con un OpenStruct, puede crear atributos arbitrariamente. A Struct, por otro lado, debe tener sus atributos definidos cuando lo crea. La elección de uno sobre el otro debería basarse principalmente en si necesita poder agregar atributos más adelante.

La forma de pensar en ellos es como el término medio del espectro entre Hashes por un lado y las clases por el otro. Implican una relación más concreta entre los datos que la de a Hash, pero no tienen los métodos de instancia como lo haría una clase. Un montón de opciones para una función, por ejemplo, tiene sentido en un hash; solo están vagamente relacionados. El nombre, el correo electrónico y el número de teléfono que necesita una función se pueden agrupar en un Structo OpenStruct. Si ese nombre, correo electrónico y número de teléfono necesitaban métodos para proporcionar el nombre en los formatos "Primero, Último" y "Último, Primero", entonces debe crear una clase para manejarlo.

pesto
fuente
49
"pero no tienen los métodos de instancia como lo haría una clase". bueno, hay un patrón bastante común para usarlo como una "clase normal":class Point < Struct.new(:x, :y); methods here; end
2011
10
@tokland en cuanto a hoy, el enfoque "preferido" de personalizar la estructura con métodos es pasar el bloque al constructor Point = Struct.new(:x, :y) { methods here }. ( fuente ) Por supuesto, { ... }puede escribirse como un bloque de varias líneas ( do ... end) y, creo, esa es la forma preferida.
Ivan Kolmychek
1
@IvanKolmychek: Genial, en realidad prefiero el enfoque de bloque.
tokland
@tokland bien. Solo quería aclarar que ahora hay un enfoque más agradable, ya que su comentario es altamente votado, por lo que las personas nuevas en Ruby pueden pensar "OK, así es como debe hacerse, porque todos están de acuerdo con eso, correcto ? " :)
Ivan Kolmychek
44
Una pregunta: una vez que llega en el momento en que desea agregar métodos a su estructura, ¿por qué no usar una clase?
Jaydel
82

Otro punto de referencia:

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 los impacientes que quieren tener una idea de los resultados de referencia, sin ejecutarlos ellos mismos, aquí está la salida del código anterior (en 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)
Robert Klemme
fuente
55
con ruby ​​2.14 la diferencia es menor 0.94-0.97 con OpenStruct vs 0.02-0.03 con Ostruct (MB Pro 2.2Ghz i7)
basex
1
OpenStruct es equivalente en velocidad al uso de Struct. Ver stackoverflow.com/a/43987844/128421 .
The Tin Man
57

ACTUALIZAR:

A partir de Ruby 2.4.1 OpenStruct y Struct están mucho más cerca en velocidad. Ver https://stackoverflow.com/a/43987844/128421

PREVIAMENTE:

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

Ejecutando un código similar al de Burtlo, en Ruby 1.9.2, (1 de 4 núcleos x86_64, 8GB de RAM) [tabla editada para alinear columnas]:

creando 1 Mio Structs: 1.43 sec, 219 MB / 90MB (virt / res)
creando 1 instancia de Mio Class: 1.43 segundos, 219 MB / 90MB (virt / res)
creando 1 Mio Hashes: 4.46 seg, 493 MB / 364MB (virt / res)
creando 1 Mio OpenStructs: 415.13 segundos, 2464 MB / 2.3GB (virt / res) # ~ 100 veces más lento que Hashes
creando 100K OpenStructs: 10.96 segundos, 369 MB / 242MB (virt / res)

OpenStructs son sloooooow y mucha memoria , y no se adaptan bien para grandes conjuntos de datos

Crear 1 Mio OpenStructs es ~ 100 veces más lento que crear 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
fuente
Información muy útil para adictos al rendimiento como yo. Gracias.
Bernardo Oliveira
Me refiero a la implementación de Matz de Ruby (MRI)
Tilo
1
Hola @Tilo, ¿podrías compartir tu código para obtener los resultados anteriores? Quiero usarlo para comparar Struct & OStruct con Hashie :: Mash. Gracias.
Donny Kurnia
1
Hola @Donny, acabo de ver el voto a favor y me di cuenta de que esto se midió en 2011: necesito volver a ejecutar esto con Ruby 2.1: P no estoy seguro si tengo ese código, pero debería ser fácil de reproducir. Intentaré arreglarlo pronto.
Tilo
2
A partir de Ruby 2.4.1 OpenStruct y Struct están mucho más cerca en velocidad. Ver stackoverflow.com/a/43987844/128421
The Tin Man
34

Los casos de uso para los dos son bastante diferentes.

Puede pensar en la clase Struct en Ruby 1.9 como un equivalente a la structdeclaración en C. En Ruby Struct.newtoma un conjunto de nombres de campo como argumentos y devuelve una nueva Clase. De manera similar, en C, una structdeclaración toma un conjunto de campos y le permite al programador usar el nuevo tipo complejo como lo haría con cualquier tipo incorporado.

Rubí:

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

C:

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

newtype n;

La clase OpenStruct se puede comparar con una declaración de estructura anónima en C. Permite al programador crear una instancia de un tipo complejo.

Rubí:

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;

Aquí hay algunos casos de uso comunes.

OpenStructs se puede utilizar para convertir fácilmente hashes en objetos únicos que responden a todas las claves hash.

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

Las estructuras pueden ser útiles para las definiciones de clase abreviadas.

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

m = MyClass.new
m.a = 1
skryl
fuente
3
Esta es una gran respuesta de la diferencia conceptual entre ellos. Gracias por señalar el anonimato de OpenStruct, creo que eso lo deja mucho más claro.
Bryant
¡Gran explicación!
Yuri Ghensev
24

OpenStructs usa significativamente más memoria y tienen un rendimiento más lento en comparación con Structs.

require 'ostruct' 

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

En mi sistema, el siguiente código se ejecutó en 14 segundos y consumió 1,5 GB de memoria. Su kilometraje puede variar:

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

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

Eso terminó casi instantáneamente y consumió 26.6 MB de memoria.

burtlo
fuente
3
Pero sabe que la prueba OpenStruct crea muchos hashes temporales. Sugiero un punto de referencia ligeramente modificado, que aún respalda su veredicto (ver más abajo).
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
dorio
fuente
Gracias por el ejemplo Ayuda mucho entender en la práctica.
Ahsan
3

Usando el código @Robert, agregué Hashie :: Mash al elemento de referencia y obtuve 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
fuente
Su punto de referencia es realmente extraño. Obtuve el siguiente resultado con ruby2.1.1 en un i5 mac: gist.github.com/nicolas-besnard/…
cappie013
Bueno, el resultado variará entre la versión de ruby ​​utilizada y el hardware utilizado para ejecutarlo. Pero el patrón sigue siendo el mismo, OpenStruct es el más lento, Struct es el más rápido. Hashie cae en el medio.
Donny Kurnia
0

En realidad, no es una respuesta a la pregunta, pero es una consideración muy importante si te importa el rendimiento . Tenga en cuenta que cada vez que crea una OpenStructoperación, se borra la caché del método, lo que significa que su aplicación funcionará más lentamente. La lentitud o no de no OpenStructse trata solo de cómo funciona por sí misma, sino de las implicaciones que su uso trae a toda la aplicación: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Cris R
fuente