¿Cuándo debo utilizar Struct frente a OpenStruct?
En general, ¿cuáles son las ventajas y desventajas de utilizar OpenStruct en comparación con Struct? ¿Qué tipo de casos de uso generales encajarían en cada uno de estos?
Con un OpenStruct
, puedes crear atributos arbitrariamente. A Struct
, por otro lado, debe tener sus atributos definidos cuando lo creas. La elección de uno u otro debe basarse principalmente en si necesita poder agregar atributos más adelante.
La forma de pensar en ellos es como el punto medio del espectro entre Hashes por un lado y clases por el otro. Implican una relación más concreta entre los datos que a Hash
, pero no tienen los métodos de instancia como lo tendría una clase. Un montón de opciones para una función, por ejemplo, tienen sentido en un hash; sólo están vagamente relacionados. Un nombre, correo electrónico y número de teléfono necesarios para una función se pueden empaquetar juntos en un archivo Struct
o OpenStruct
. Si ese nombre, correo electrónico y número de teléfono necesitaban métodos para proporcionar el nombre en los formatos "Nombre Apellido" y "Apellido, Nombre", entonces deberías crear una clase para manejarlo.
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 quieran tener una idea de los resultados de las pruebas comparativas, sin ejecutarlos ellos mismos, aquí está el resultado 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)
ACTUALIZAR:
Tiempos para crear 1 millón de instancias:
0.357788 seconds elapsed for Class.new (Ruby 2.5.5)
0.764953 seconds elapsed for Struct (Ruby 2.5.5)
0.842782 seconds elapsed for Hash (Ruby 2.5.5)
2.211959 seconds elapsed for OpenStruct (Ruby 2.5.5)
0.213175 seconds elapsed for Class.new (Ruby 2.6.3)
0.335341 seconds elapsed for Struct (Ruby 2.6.3)
0.836996 seconds elapsed for Hash (Ruby 2.6.3)
2.070901 seconds elapsed for OpenStruct (Ruby 2.6.3)
0.936016 seconds elapsed for Class.new (Ruby 2.7.2)
0.453067 seconds elapsed for Struct (Ruby 2.7.2)
1.016676 seconds elapsed for Hash (Ruby 2.7.2)
1.482318 seconds elapsed for OpenStruct (Ruby 2.7.2)
0.421272 seconds elapsed for Class.new (Ruby 3.0.0)
0.322617 seconds elapsed for Struct (Ruby 3.0.0)
0.719928 seconds elapsed for Hash (Ruby 3.0.0)
35.130777 seconds elapsed for OpenStruct (Ruby 3.0.0) (oops!)
0.443975 seconds elapsed for Class.new (Ruby 3.0.1)
0.348031 seconds elapsed for Struct (Ruby 3.0.1)
0.737662 seconds elapsed for Hash (Ruby 3.0.1)
16.264204 seconds elapsed for SmartHash (Ruby 3.0.1) (meh)
53.396924 seconds elapsed for OpenStruct (Ruby 3.0.1) (oops!)
0.407767 seconds elapsed for Class.new (Ruby 3.0.3)
0.326846 seconds elapsed for Struct (Ruby 3.0.3)
0.652807 seconds elapsed for Hash (Ruby 3.0.3)
10.679195 seconds elapsed for SmartHash (Ruby 3.0.3)
35.212618 seconds elapsed for OpenStruct (Ruby 3.0.3)
Ver: El error #18032 de Ruby 3.0.0 se cerró porque es una característica, no un error.
Citas:
OpenStruct ahora se considera "un antipatrón", por lo que le recomiendo que no utilice más OpenStruct.
[el equipo de Ruby] priorizó la corrección sobre el rendimiento y volvió a una solución similar a la de Ruby 2.2
Respuestas antiguas:
A partir de Ruby 2.4.1, OpenStruct y Struct tienen una velocidad mucho más cercana. Ver https://stackoverflow.com/a/43987844/128421
Para completar: Estructura versus Clase versus Hash versus OpenStruct
Ejecutando un código similar al de Burtlo, en Ruby 1.9.2, (1 de 4 núcleos x86_64, 8 GB de RAM) [tabla editada para alinear las columnas]:
creando 1 Mio Structs: 1,43 segundos, 219 MB / 90 MB (virt/res) creación de instancias de 1 Mio Class: 1,43 segundos, 219 MB/90 MB (virt/res) creando 1 Mio Hashes: 4,46 segundos, 493 MB / 364 MB (virt/res) creando 1 Mio OpenStructs: 415,13 segundos, 2464 MB / 2,3 GB (virt/res) # ~100 veces más lento que Hashes creando 100K OpenStructs: 10,96 segundos, 369 MB/242 MB (virt/res)
OpenStructs son lentos , consumen mucha memoria y no se escalan bien para grandes conjuntos de datos.
Aquí está el script para reproducir los resultados:
require 'ostruct'
require 'smart_hash'
MAX = 1_000_000
class C;
attr_accessor :name, :age;
def initialize(name, age)
self.name = name
self.age = age
end
end
start = Time.now
collection = (1..MAX).collect do |i|
C.new('User', 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Class.new (Ruby #{RUBY_VERSION})"
s = Struct.new(:name, :age)
start = Time.now
collection = (1..MAX).collect do |i|
s.new('User', 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Struct (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
{:name => "User" , :age => 21}
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for Hash (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
s = SmartHash[].merge( {:name => "User" , :age => 21} )
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for SmartHash (Ruby #{RUBY_VERSION})"
start = Time.now
collection = (1..MAX).collect do |i|
OpenStruct.new(:name => "User" , :age => 21)
end; 1
stop = Time.now
puts " #{stop - start} seconds elapsed for OpenStruct (Ruby #{RUBY_VERSION})"
Los casos de uso de los dos son bastante diferentes.
Puedes pensar en la clase Struct en Ruby 1.9 como un equivalente a la struct
declaración en C. En Ruby Struct.new
toma un conjunto de nombres de campos como argumentos y devuelve una nueva Clase. De manera similar, en C, una struct
declaración toma un conjunto de campos y permite al programador usar el nuevo tipo complejo tal como lo haría con cualquier tipo integrado.
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;
A continuación se muestran algunos casos de uso comunes.
OpenStructs se puede utilizar para convertir fácilmente hash 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 resultar útiles para definiciones abreviadas de clases.
class MyClass < Struct.new(:a,:b,:c)
end
m = MyClass.new
m.a = 1