一般来说,与Struct相比,使用OpenStruct的优点和缺点是什么?什么样类型的通用用例适合每一个?


看一下与新方法相关的API。这里有很多不同之处。

就我个人而言,我非常喜欢OpenStruct,因为我不需要预先定义对象的结构,只需要添加我想要的东西。我猜这将是它的主要(缺点)优势?

http://www.ruby-doc.org/core/classes/Struct.html http://www.ruby-doc.org/stdlib/libdoc/ostruct/rdoc/classes/OpenStruct.html


使用OpenStruct,您可以任意创建属性。另一方面,Struct必须在创建时定义其属性。选择一种还是另一种应该主要基于您以后是否需要能够添加属性。

The way to think about them is as the middle ground of the spectrum between Hashes on one side and classes on the other. They imply a more concrete relationship amongst the data than does a Hash, but they don't have the instance methods as would a class. A bunch of options for a function, for example, make sense in a hash; they're only loosely related. A name, email, and phone number needed by a function could be packaged together in a Struct or OpenStruct. If that name, email, and phone number needed methods to provide the name in both "First Last" and "Last, First" formats, then you should create a class to handle it.


与Structs相比,OpenStructs使用的内存明显更多,运行速度更慢。

require 'ostruct' 

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

在我的系统上,以下代码在14秒内执行,消耗了1.5 GB内存。你的里程可能有所不同:

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

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

这几乎是在瞬间完成的,消耗了26.6 MB的内存。


其他指标:

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

对于那些没有耐心的人来说,他们想要了解基准测试结果,而不需要自己运行它们,下面是上面代码的输出(在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)

更新:

创建100万个实例的计时:

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!)

参见:Ruby 3.0.0 Bug #18032已经关闭,因为它是一个特性,而不是一个Bug

报价:

OpenStruct现在被认为是“反模式”,所以我建议你不要再使用OpenStruct。

(Ruby团队)将正确性优先于性能,并回到了与Ruby 2.2类似的解决方案


古老的答案:

在Ruby 2.4.1版本中,OpenStruct和Struct在速度上更加接近。参见https://stackoverflow.com/a/43987844/128421


为完整起见:Struct vs. Class vs. Hash vs. OpenStruct

运行与burtlo类似的代码,在Ruby 1.9.2上,(4核x86_64中的1个,8GB RAM)[编辑表以对齐列]:

creating 1 Mio Structs :         1.43 sec ,  219 MB /  90MB (virt/res)
creating 1 Mio Class instances : 1.43 sec ,  219 MB /  90MB (virt/res)
creating 1 Mio Hashes  :         4.46 sec ,  493 MB / 364MB (virt/res)
creating 1 Mio OpenStructs :   415.13 sec , 2464 MB / 2.3GB (virt/res) # ~100x slower than Hashes
creating 100K OpenStructs :     10.96 sec ,  369 MB / 242MB (virt/res)

openstruct运行缓慢,占用大量内存,不能很好地扩展大数据集


下面是重现结果的脚本:

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})"

两者的用例完全不同。

你可以把Ruby 1.9中的Struct类看作相当于c中的Struct声明。new接受一组字段名作为参数,并返回一个新Class。类似地,在C语言中,struct声明接受一组字段,并允许程序员像使用任何内置类型一样使用新的复杂类型。

Ruby:

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

C:

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

newtype n;

OpenStruct类可以与c语言中的匿名结构声明相比较。它允许程序员创建复杂类型的实例。

Ruby:

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;

下面是一些常见的用例。

OpenStructs可以很容易地将散列转换为一次性对象,从而响应所有散列键。

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

struct可以用于简化类定义。

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

m = MyClass.new
m.a = 1

使用@Robert代码,我将Hashie::Mash添加到基准测试项中,得到了这样的结果:

                           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)

结构:

>> 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

Not actually an answer to the question, but a very important consideration if you care about performance. Please notice that every time you create an OpenStruct the operation clears the method cache, which means your application will perform slower. The slowness or not of OpenStruct is not just about how it works by itself, but the implications that using them bring to the whole application: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs