В целом, каковы преимущества и недостатки использования OpenStruct по сравнению со Struct? Какой тип общих вариантов использования подошел бы каждому из них?
Когда мне следует использовать Struct или OpenStruct?
Ответы (8)
С OpenStruct
вы можете произвольно создавать атрибуты. С другой стороны, Struct
должен иметь атрибуты, определенные при его создании. Выбор одного из них должен основываться прежде всего на том, нужно ли вам добавлять атрибуты позже.
Их можно рассматривать как середину спектра между хешами с одной стороны и классами с другой. Они предполагают более конкретную взаимосвязь между данными, чем Hash
, но у них нет методов экземпляра, как у класса. Набор опций для функции, например, имеет смысл в хэше; они лишь слабо связаны. Имя, адрес электронной почты и номер телефона, необходимые функции, могут быть объединены в Struct
или OpenStruct
. Если для этого имени, адреса электронной почты и номера телефона необходимы методы для предоставления имени как в формате «Первый, последний», так и в формате «Фамилия, первый», тогда вам следует создать класс для его обработки.
class Point < Struct.new(:x, :y); methods here; end
- person tokland; 22.12.2011
Point = Struct.new(:x, :y) { methods here }
. (источник) Конечно, { ... }
там можно записать в виде многострочного блока (do ... end
), и я думаю, что это предпочтительный способ.
- person Ivan Kolmychek; 09.03.2016
Другой тест:
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)
ОБНОВЛЕНИЕ:
Сроки создания 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})"
Сценарии использования этих двух совершенно разные.
Вы можете рассматривать класс 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
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 МБ памяти.
Взгляните на 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