Когда я должен использовать Struct vs. OpenStruct?


184

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


1
У меня есть несколько замечаний о «Структуре против OpenStruct против Хэша» в моем недавнем комментарии к блогу «Структуры наизнанку» , на случай, если кто-то заинтересован.
Роберт Клемм

Информация о скорости работы Hash, Struct и OpenStruct устарела. См. Stackoverflow.com/a/43987844/128421 для более позднего теста.
Оловянный Человек

Ответы:


173

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

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


49
msgstr "но у них нет методов экземпляра, как у класса". ну, есть довольно распространенный шаблон, чтобы использовать его как «нормальный класс»:class Point < Struct.new(:x, :y); methods here; end
tokland

10
На сегодняшний день, @tokland, «предпочтительным» подходом настройки структуры с помощью методов является передача блока в конструктор Point = Struct.new(:x, :y) { methods here }. ( источник ) Конечно, { ... }это может быть записано как многострочный блок ( do ... end), и, я думаю, это предпочтительный способ.
Иван Колмычек

1
@IvanKolmychek: Круто, на самом деле я предпочитаю блочный подход.
Tokland

@tokland хорошо. Я просто хотел уточнить, что теперь есть более приятный подход, так как ваш комментарий высоко оценен, поэтому люди, плохо знакомые с ruby, могут на самом деле думать: «Хорошо, вот как это должно быть сделано, потому что все согласны с этим, верно ?» :)
Иван Колмычек

4
Вопрос: как только вы прибудете в тот момент, когда хотите добавить методы в свою структуру, почему бы не использовать класс?
Джейдел

82

Другой тест:

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)

5
с ruby ​​2.14 разница меньше на 0.94-0.97 с OpenStruct против 0.02-0.03 с Ostruct (MB Pro 2.2Ghz i7)
basex

1
OpenStruct по скорости эквивалентен использованию Struct. См. Stackoverflow.com/a/43987844/128421 .
Железный Человек

57

ОБНОВИТЬ:

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

РАНЕЕ:

Для полноты: Struct против класса против Hash против OpenStruct

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

создание 1 Mio Structs: 1,43 с, 219 МБ / 90 МБ (вирт / разрешение)
создание 1 экземпляров класса Mio: 1,43 с, 219 МБ / 90 МБ (вирт / разрешение)
создание 1 хеша Mio: 4,46 с, 493 МБ / 364 МБ (вирт / разрешение)
создание 1 Mio OpenStructs: 415,13 с, 2464 МБ / 2,3 ГБ (вирт / разрешение) # ~ в 100 раз медленнее, чем хэши
создание 100K OpenStructs: 10,96 с, 369 МБ / 242 МБ (вирт / разрешение)

OpenStructs являются sloooooow и большим объемом памяти , и не очень хорошо масштабируются для больших наборов данных

Создание 1 Mio OpenStructs в ~ 100 раз медленнее, чем создание 1 Mio Hashes .

start = Time.now

collection = (1..10**6).collect do |i|
  {:name => "User" , :age => 21}
end; 1

stop = Time.now

puts "#{stop - start} seconds elapsed"

Очень полезная информация для таких наркоманов, как я. Спасибо.
Бернардо Оливейра

Я имею в виду реализацию Matz Ruby (MRI)
Tilo

1
Привет @ Тило, не могли бы вы поделиться своим кодом, чтобы получить результаты выше? Я хочу использовать его для сравнения Struct & OStruct с Hashie :: Mash. Спасибо.
Донни Курния

1
Привет @ Донни, я только что увидел upvote и понял, что это было измерено в 2011 году - мне нужно перезапустить это с Ruby 2.1: P не уверен, что у меня есть этот код, но он должен быть простым для воспроизведения. Я постараюсь исправить это в ближайшее время.
Тило

2
Начиная с Ruby 2.4.1 OpenStruct и Struct намного ближе по скорости. См. Stackoverflow.com/a/43987844/128421
Железный человек

34

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

Вы можете думать о классе 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

3
Это отличный ответ на концептуальную разницу между ними. Спасибо за указание на анонимность OpenStruct, я чувствую, что это делает его намного более понятным.
Брайант

Отличное объяснение!
Юрий Генсев

24

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 МБ памяти.


3
Но вы знаете, что тест OpenStruct создает много временных хэшей. Я предлагаю немного измененный тест - который все еще поддерживает ваш вердикт (см. Ниже).
Роберт Клемм

6

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

Спасибо за пример. Это очень помогает понять на практике.
Ahsan

5

Посмотрите на API в отношении нового метода. Много различий можно найти там.

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


3

Используя код @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)

Ваш эталон действительно странный. Я получил следующий результат с ruby2.1.1 на i5 mac: gist.github.com/nicolas-besnard/…
cappie013

Что ж, результат будет зависеть от используемой версии ruby ​​и используемого оборудования. Но шаблон все тот же, OpenStruct самый медленный, Struct самый быстрый. Хэши посередине.
Донни Курния

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.