Коли я повинен використовувати Struct vs. OpenStruct?


184

Загалом, які переваги та недоліки використання OpenStruct порівняно зі структурою? Який тип випадків загального користування підходив би до кожного з них?


1
Я маю кілька зауважень щодо Struct vs. OpenStruct vs. Hash у своєму недавньому коментарі до блогу "Structs inside out" , на випадок, якщо хтось зацікавиться.
Роберт Клемме

Інформація про швидкість Hash, Struct та OpenStruct застаріла. Дивіться stackoverflow.com/a/43987844/128421 для більш недавнього еталону.
Олов'яний чоловік

Відповіді:


172

За допомогою OpenStruct, ви можете довільно створювати атрибути. А Struct, з іншого боку, повинні бути визначені його атрибути, коли ви створюєте їх. Вибір одного над іншим повинен ґрунтуватися насамперед на тому, чи потрібно мати змогу додавати атрибути пізніше.

Спосіб подумати про них - це середина спектру між хешами з одного боку та класами з іншого. Вони мають на увазі більш конкретну залежність між даними, ніж у a Hash, але у них немає методів екземпляра, як у класу. Купа варіантів функції, наприклад, має сенс у хеші; вони лише слабко пов'язані. Ім'я, електронна адреса та номер телефону, необхідні функції, можуть бути упаковані разом у Structабо OpenStruct. Якщо для цього імені, електронної пошти та номера телефону потрібні способи надання імені у форматах "Перше останнє" та "Останнє, перше", тоді вам слід створити клас для його обробки.


49
"але у них немає методів екземпляра, як у класу". ну, є досить поширена модель, щоб використовувати його як "звичайний клас":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 хороший. Я просто хотів уточнити, що зараз є приємніший підхід, бачачи, як за ваш коментар високо голосують, тож люди, які знаходяться в рубіні, насправді можуть думати: «Добре, так це і потрібно робити», тому що всі згодні з цим, правда ? " :)
Іван Колмичек

4
Питання: як тільки ви прийшли в той момент, коли ви хочете додати методи до своєї структури, чому б не використовувати клас?
jaydel

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
з рубіном 2,14 різниця менша 0,94-0,97 з OpenStruct проти 0,02-0,03 з Ostruct (MB Pro 2,2 ГГц i7)
basex

1
OpenStruct за швидкістю еквівалентний використанню Struct. Дивіться stackoverflow.com/a/43987844/128421 .
Олов'яний чоловік

57

ОНОВЛЕННЯ:

Станом на Ruby 2.4.1 OpenStruct і Struct набагато ближче за швидкістю. Дивіться https://stackoverflow.com/a/43987844/128421

ПОПЕРЕДНЯ:

Для повноти: Структура проти Class vs. Hash vs. OpenStruct

Запуск аналогічного коду, як burtlo, на Ruby 1.9.2, (1 з 4 ядер x86_64, 8 Гб оперативної пам’яті) [таблиця відредагована для вирівнювання стовпців]:

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

OpenStructs є sloooooow і великим об'ємом пам'яті , і НЕ дуже добре масштабуються для великих наборів даних

Створення 1 Міо OpenStructs на 100 разів повільніше, ніж створення 1 Міо хешів .

start = Time.now

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

stop = Time.now

puts "#{stop - start} seconds elapsed"

Дуже корисна інформація для наркоманів, як я. Дякую.
Бернардо Олівейра

Я маю на увазі реалізацію Рубі (МРТ) Маца
Тіло

1
Привіт @Tilo, ти можеш поділитися своїм кодом, щоб отримати результати вище? Я хочу використовувати його для порівняння Struct & OStruct з Hashie :: Mash. Дякую.
Донні Курня

1
Привіт @Donny, я щойно побачив підсумок і зрозумів, що це було виміряно у 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приймається набір полів імен як аргументів і повертає новий Class. Аналогічно, в C structдекларація приймає набір полів і дозволяє програмісту використовувати новий складний тип так само, як і будь-який вбудований тип.

Ruby:

Newtype = Struct.new(:data1, :data2)
n = Newtype.new

C:

typedef struct {
  int data1;
  char data2;
} newtype;

newtype n;

Клас OpenStruct можна порівняти з анонімним декларацією структури в C. Це дозволяє програмісту створити екземпляр складного типу.

Ruby:

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

Дякую за приклад. Це дуже допомагає зрозуміти на практиці.
Ахсан

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

Ну, результат буде залежати від використовуваної рубінової версії та обладнання, яке використовується для її запуску. Але шаблон все одно той самий, 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.