Когда мне следует использовать Struct или OpenStruct?

В целом, каковы преимущества и недостатки использования OpenStruct по сравнению со Struct? Какой тип общих вариантов использования подошел бы каждому из них?


person ehsanul    schedule 24.07.2009    source источник
comment
У меня есть несколько замечаний о Struct, OpenStruct и Hash в моем недавнем комментарии в блоге Конструкции наизнанку, на всякий случай кому интересно.   -  person Robert Klemme    schedule 11.10.2009
comment
Информация о скорости Hash, Struct и OpenStruct устарела. См. stackoverflow.com/a/43987844/128421 для более свежего теста.   -  person the Tin Man    schedule 16.05.2017


Ответы (8)


С OpenStruct вы можете произвольно создавать атрибуты. С другой стороны, Struct должен иметь атрибуты, определенные при его создании. Выбор одного из них должен основываться прежде всего на том, нужно ли вам добавлять атрибуты позже.

Их можно рассматривать как середину спектра между хешами с одной стороны и классами с другой. Они предполагают более конкретную взаимосвязь между данными, чем Hash, но у них нет методов экземпляра, как у класса. Набор опций для функции, например, имеет смысл в хэше; они лишь слабо связаны. Имя, адрес электронной почты и номер телефона, необходимые функции, могут быть объединены в Struct или OpenStruct. Если для этого имени, адреса электронной почты и номера телефона необходимы методы для предоставления имени как в формате «Первый, последний», так и в формате «Фамилия, первый», тогда вам следует создать класс для его обработки.

person Pesto    schedule 24.07.2009
comment
но у них нет методов экземпляра, как у класса. ну, есть довольно распространенный шаблон для использования его как обычного класса: class Point < Struct.new(:x, :y); methods here; end - person tokland; 22.12.2011
comment
@tokland на сегодняшний день предпочтительным подходом к настройке структуры с помощью методов является передача блока конструктору Point = Struct.new(:x, :y) { methods here }. (источник) Конечно, { ... } там можно записать в виде многострочного блока (do ... end), и я думаю, что это предпочтительный способ. - person Ivan Kolmychek; 09.03.2016
comment
@tokland хорошо. Я просто хотел уточнить, что теперь есть более приятный подход, поскольку ваш комментарий получил большое количество голосов, поэтому люди, плохо знакомые с Ruby, действительно могут думать нормально, так что это должно быть сделано, потому что все согласны с этим, верно? :) - person Ivan Kolmychek; 09.03.2016
comment
Вопрос: как только вы дойдете до того момента, когда захотите добавить методы в свою структуру, почему бы не использовать класс? - person jaydel; 13.10.2017

Другой тест:

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

Для нетерпеливых, которые хотят получить представление о результатах тестов, не запуская их самостоятельно, вот результат кода, приведенного выше (на i7 MB Pro 2,4 ГГц)

                          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)
person Robert Klemme    schedule 16.12.2010
comment
с ruby ​​2.14 разница меньше 0.94-0.97 с OpenStruct по сравнению с 0.02-0.03 с Ostruct (MB Pro 2.2Ghz i7) - person basex; 23.03.2015
comment
OpenStruct по скорости эквивалентен использованию Struct. См. stackoverflow.com/a/43987844/128421. - person the Tin Man; 15.05.2017

ОБНОВЛЕНИЕ:

Сроки создания 1 миллиона экземпляров:

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 № 18032 была закрыта, потому что это функция, не ошибка


Старые ответы:

Начиная с Ruby 2.4.1 OpenStruct и Struct намного ближе по скорости. См. https://stackoverflow.com/a/43987844/128421


Для полноты: Struct против Class против Hash против OpenStruct.

Выполнение кода, аналогичного Burtlo, на Ruby 1.9.2 (1 из 4 ядер x86_64, 8 ГБ ОЗУ) [таблица отредактирована для выравнивания столбцов]:

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})"
person Tilo    schedule 26.03.2011
comment
Я имею в виду реализацию Matz Ruby (MRI) - person Tilo; 05.06.2012
comment
Привет, @Tilo, не могли бы вы поделиться своим кодом, чтобы получить результаты, указанные выше? Я хочу использовать его для сравнения Struct & OStruct с Hashie :: Mash. Спасибо. - person Donny Kurnia; 30.03.2014
comment
Привет, @Donny, я только что увидел голосование и понял, что это было измерено в 2011 году - мне нужно повторно запустить это с Ruby 2.1: P не уверен, есть ли у меня этот код, но его должно быть легко воспроизвести. Постараюсь в ближайшее время исправить. - person Tilo; 15.02.2015
comment
Начиная с Ruby 2.4.1 OpenStruct и Struct намного ближе по скорости. См. stackoverflow.com/a/43987844/128421 - person the Tin Man; 15.05.2017

Сценарии использования этих двух совершенно разные.

Вы можете рассматривать класс Struct в Ruby 1.9 как эквивалент объявления struct в C. В 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
person skryl    schedule 13.07.2012

OpenStructs используют значительно больше памяти и работают медленнее, чем Structs.

require 'ostruct' 

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

В моей системе следующий код выполнялся за 14 секунд и занимал 1,5 ГБ памяти. Ваш пробег может отличаться:

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

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

Это завершилось почти мгновенно и заняло 26,6 МБ памяти.

person burtlo    schedule 09.11.2010
comment
Но вы знаете, что тест OpenStruct создает много временных хешей. Я предлагаю немного модифицированный тест, который по-прежнему поддерживает ваш вердикт (см. Ниже). - person Robert Klemme; 16.12.2010

Взгляните на API в отношении нового метода. Здесь можно найти много отличий.

Лично мне очень нравится OpenStruct, так как мне не нужно заранее определять структуру объекта и просто добавлять что-то, что я хочу. Полагаю, это было бы его основным (не) преимуществом?

person Omar Qureshi    schedule 24.07.2009

Используя код @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)
person Donny Kurnia    schedule 30.03.2014
comment
Ваш тест действительно странный. Я получил следующий результат с ruby2.1.1 на i5 Mac: gist. github.com/nicolas-besnard/ - person cappie013; 17.12.2014
comment
Что ж, результат будет варьироваться в зависимости от используемой версии Ruby и оборудования, используемого для ее запуска. Но картина все та же, OpenStruct - самый медленный, Struct - самый быстрый. Хаши попадает в середину. - person Donny Kurnia; 30.12.2014

На самом деле это не ответ на вопрос, но очень важное соображение, если вы заботитесь о производительности. Обратите внимание, что каждый раз, когда вы создаете OpenStruct, операция очищает кеш метода, что означает, что ваше приложение будет работать медленнее. Медленность OpenStruct связана не только с тем, как он работает сам по себе, но и с последствиями, которые их использование приносит всему приложению: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that-clear-rubys-method-cache.md#openstructs

person Cris R    schedule 03.12.2019