什么时候使用Struct vs. OpenStruct?

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

73123 次浏览

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

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

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

考虑它们的方法是作为光谱的中间地带,一边是哈希,另一边是类。它们意味着数据之间比Hash更具体的关系,但它们不像类那样具有实例方法。例如,一个函数的一堆选项在哈希中是有意义的;他们只有松散的联系。函数所需的姓名、电子邮件和电话号码可以打包在StructOpenStruct中。如果名称、电子邮件和电话号码需要以“First Last”和“Last, First”格式提供名称的方法,那么您应该创建一个类来处理它。

与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被关闭,因为它是一个功能,而不是一个错误

报价:

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

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


古老的答案:

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


为了完整性:结构体 vs. vs. 哈希 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是sloooooow内存密集型不能很好地扩展大型数据集


下面是重现结果的脚本:

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声明。在Ruby中,Struct.new接受一组字段名作为参数并返回一个新类。类似地,在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)

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

实际上这并不是问题的答案,但是如果你关心性能,这是一个非常重要的 的考虑。请注意,每次创建OpenStruct时,操作都会清除方法缓存,这意味着你的应用程序将执行得更慢。OpenStruct的慢不慢不仅与它自身的工作方式有关,还与使用它们给整个应用程序带来的影响有关:https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs