一般来说,与 Struct 相比,使用 OpenStruct 的优点和缺点是什么?哪种类型的一般用例适合这些用例?
使用 OpenStruct
,您可以任意创建属性。另一方面,Struct
必须在创建时定义其属性。选择一个而不是另一个应该主要基于您以后是否需要能够添加属性。
考虑它们的方式是作为一方面的哈希和另一方面的类之间的频谱的中间立场。它们暗示数据之间的关系比 Hash
更具体,但它们没有类的实例方法。例如,函数的一堆选项在散列中有意义;它们只是松散的相关。函数所需的姓名、电子邮件和电话号码可以一起打包在 Struct
或 OpenStruct
中。如果该名称、电子邮件和电话号码需要方法以“First Last”和“Last, First”两种格式提供名称,那么您应该创建一个类来处理它。
其他基准:
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
为了完整性:Struct vs. Class vs. Hash vs. OpenStruct
在 Ruby 1.9.2 上运行与 burtlo 类似的代码(4 个内核中的 1 个 x86_64,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)
OpenStructs 速度慢且占用大量内存,并且不能很好地适应大型数据集
这是重现结果的脚本:
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
声明采用一组字段并允许程序员使用新的复杂类型,就像他使用任何内置类型一样。
红宝石:
Newtype = Struct.new(:data1, :data2)
n = Newtype.new
C:
typedef struct {
int data1;
char data2;
} newtype;
newtype n;
OpenStruct 类可以比作 C 中的匿名结构声明。它允许程序员创建复杂类型的实例。
红宝石:
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
结构可用于速记类定义。
class MyClass < Struct.new(:a,:b,:c)
end
m = MyClass.new
m.a = 1
与 Struct 相比,OpenStruct 使用显着更多的内存并且执行速度较慢。
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 的内存。
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
查看有关新方法的 API。在那里可以找到很多不同之处。
就个人而言,我非常喜欢 OpenStruct,因为我不必事先定义对象的结构,只需添加我想要的东西。我想这将是它的主要(不利)优势?
http://www.ruby-doc.org/core/classes/Struct.html
http://www.ruby-doc.org/stdlib/libdoc/ostruct/rdoc/classes/OpenStruct.html
使用@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)
实际上并不是问题的答案,而是一个非常重要的考虑因素,如果您关心性能。请注意,每次创建 OpenStruct
时,该操作都会清除方法缓存,这意味着您的应用程序执行速度会变慢。 OpenStruct
的缓慢与否不仅在于它本身的工作方式,还在于使用它们给整个应用程序带来的含义:https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs
不定期副业成功案例分享
class Point < Struct.new(:x, :y); methods here; end
Point = Struct.new(:x, :y) { methods here }
。 (source) 当然,{ ... }
可以写成多行块 (do ... end
),我认为这是首选方式。