ChatGPT解决这个技术问题 Extra ChatGPT

什么时候应该使用 Struct 与 OpenStruct?

一般来说,与 Struct 相比,使用 OpenStruct 的优点和缺点是什么?哪种类型的一般用例适合这些用例?

在我最近的博客评论 "Structs inside out" 中,我有一些关于 Struct 与 OpenStruct 与 Hash 的评论,以防万一有人感兴趣。
有关 Hash、Struct 和 OpenStruct 速度的信息已过时。有关更新的基准,请参阅 stackoverflow.com/a/43987844/128421

S
Smar

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

考虑它们的方式是作为一方面的哈希和另一方面的类之间的频谱的中间立场。它们暗示数据之间的关系比 Hash 更具体,但它们没有类的实例方法。例如,函数的一堆选项在散列中有意义;它们只是松散的相关。函数所需的姓名、电子邮件和电话号码可以一起打包在 StructOpenStruct 中。如果该名称、电子邮件和电话号码需要方法以“First Last”和“Last, First”两种格式提供名称,那么您应该创建一个类来处理它。


“但他们没有类的实例方法”。好吧,有一个很常见的模式可以将它用作“普通类”:class Point < Struct.new(:x, :y); methods here; end
@tokland 今天,使用方法自定义结构的“首选”方法是将块传递给构造函数 Point = Struct.new(:x, :y) { methods here }。 (source) 当然,{ ... } 可以写成多行块 (do ... end),我认为这是首选方式。
@tokland 好。我只是想澄清一下,现在有一个更好的方法,因为您的评论被高度评价,所以,红宝石新手实际上可以认为“好的,这就是应该这样做的,因为每个人都同意这一点,对?” :)
一个问题:一旦你到了你想要为你的结构添加方法的那一刻,为什么不使用一个类呢?
C
Community

其他基准:

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)

使用 ruby 2.14,OpenStruct 的差异更小 0.94-0.97 与 Ostruct 的 0.02-0.03 (MB Pro 2.2Ghz i7)
OpenStruct 在速度上与使用 Struct 相当。请参阅stackoverflow.com/a/43987844/128421
T
Tilo

更新:

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

我指的是 Matz 的 Ruby 实现(MRI)
嗨@Tilo,你能分享你的代码来得到上面的结果吗?我想用它来比较 Struct & OStruct 与 Hashie::Mash。谢谢。
嘿@Donny,我刚刚看到了赞成票,并意识到这是在 2011 年测量的——我需要用 Ruby 2.1 重新运行它:P 不确定我是否有那个代码,但它应该很容易重现。我会尽快修复它。
从 Ruby 2.4.1 开始,OpenStruct 和 Struct 在速度上更加接近。请参阅stackoverflow.com/a/43987844/128421
t
the Tin Man

两者的用例完全不同。

您可以将 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

t
the Tin Man

与 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 的内存。


但是您知道 OpenStruct 测试会创建很多临时哈希。我建议稍微修改一下基准 - 它仍然支持您的判断(见下文)。
D
Dorian

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

t
the Tin Man

查看有关新方法的 API。在那里可以找到很多不同之处。

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

http://www.ruby-doc.org/core/classes/Struct.html

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


D
Donny Kurnia

使用@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)

你的基准真的很奇怪。我在 i5 mac 上使用 ruby2.1.1 得到以下结果:gist.github.com/nicolas-besnard/…
嗯,结果会因使用的 ruby 版本和用于运行它的硬件而异。但是模式还是一样的,OpenStruct 最慢,Struct 最快。哈希落在中间。
C
Cris R

实际上并不是问题的答案,而是一个非常重要的考虑因素,如果您关心性能。请注意,每次创建 OpenStruct 时,该操作都会清除方法缓存,这意味着您的应用程序执行速度会变慢。 OpenStruct 的缓慢与否不仅在于它本身的工作方式,还在于使用它们给整个应用程序带来的含义https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs