Kapan saya harus menggunakan Struct vs OpenStruct?

184

Secara umum, apa keuntungan dan kerugian menggunakan OpenStruct dibandingkan dengan Struct? Apa jenis kasus penggunaan umum yang cocok untuk masing-masing kasus ini?

ehsanul
sumber
1
Saya punya beberapa komentar tentang Struct vs OpenStruct vs Hash di komentar blog saya baru-baru ini "Structs inside out" , kalau-kalau ada yang tertarik.
Robert Klemme
Informasi mengenai kecepatan Hash, Struct dan OpenStruct sudah usang. Lihat stackoverflow.com/a/43987844/128421 untuk benchmark yang lebih baru.
the Tin Man

Jawaban:

173

Dengan OpenStruct, Anda dapat membuat atribut secara sewenang-wenang. A Struct, di sisi lain, harus memiliki atribut yang ditentukan ketika Anda membuatnya. Pilihan satu di atas yang lain harus didasarkan terutama pada apakah Anda harus dapat menambahkan atribut nanti.

Cara untuk berpikir tentang mereka adalah sebagai jalan tengah dari spektrum antara Hash di satu sisi dan kelas di sisi lain. Mereka menyiratkan hubungan yang lebih konkret antara data daripada Hash, tetapi mereka tidak memiliki metode instan seperti halnya kelas. Banyak opsi untuk suatu fungsi, misalnya, masuk akal dalam hash; mereka hanya terkait longgar. Nama, email, dan nomor telepon yang dibutuhkan oleh suatu fungsi dapat dikemas bersama dalam suatu Structatau OpenStruct. Jika nama, email, dan nomor telepon itu memerlukan metode untuk memberikan nama dalam format "First Last" dan "Last, First", maka Anda harus membuat kelas untuk menanganinya.

pesto
sumber
49
"Tetapi mereka tidak memiliki metode instance seperti halnya kelas". baik, ada pola yang cukup umum untuk menggunakannya sebagai "kelas normal":class Point < Struct.new(:x, :y); methods here; end
tokland
10
@tokland seperti untuk hari ini, pendekatan "pilihan" untuk mengkustomisasi struct dengan metode adalah meneruskan blok ke konstruktor Point = Struct.new(:x, :y) { methods here }. ( sumber ) Tentu saja, { ... }ada dapat ditulis sebagai blok multi-line ( do ... end) dan, saya pikir, itulah cara yang disukai.
Ivan Kolmychek
1
@IvanKolmychek: Keren, sebenarnya saya lebih suka pendekatan blok.
tokland
@tokland bagus. Saya hanya ingin mengklarifikasi bahwa sekarang ada pendekatan yang lebih baik, mengingat komentar Anda sangat banyak dipilih, jadi, orang yang baru mengenal ruby ​​sebenarnya dapat berpikir "OK, jadi begitulah seharusnya dilakukan, karena semua orang setuju dengan itu, kan ? " :)
Ivan Kolmychek
4
Sebuah pertanyaan: begitu Anda tiba pada saat Anda ingin menambahkan metode ke struct Anda, mengapa tidak menggunakan kelas?
jaydel
82

Tolok ukur lainnya:

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

Untuk yang tidak sabar yang ingin mendapatkan gambaran tentang hasil benchmark, tanpa menjalankannya sendiri, berikut adalah output dari kode di atas (pada 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)
Robert Klemme
sumber
5
dengan ruby ​​2.14 perbedaannya lebih kecil 0.94-0.97 dengan OpenStruct vs 0.02-0.03 dengan Ostruct (MB Pro 2.2Ghz i7)
basex
1
OpenStruct setara dalam kecepatan untuk menggunakan Struct. Lihat stackoverflow.com/a/43987844/128421 .
the Tin Man
57

MEMPERBARUI:

Pada Ruby 2.4.1 OpenStruct dan Struct lebih cepat dalam hal kecepatan. Lihat https://stackoverflow.com/a/43987844/128421

SEBELUMNYA:

Untuk kelengkapan: Struct vs. Class vs Hash vs OpenStruct

Menjalankan kode yang sama seperti burtlo's, di Ruby 1.9.2, (1 dari 4 core x86_64, 8GB RAM) [tabel diedit untuk menyelaraskan kolom]:

membuat 1 Mio Structs: 1,43 detik, 219 MB / 90MB (virt / res)
membuat 1 instance Mio Class: 1,43 detik, 219 MB / 90MB (virt / res)
membuat 1 Mio Hash: 4,46 detik, 493 MB / 364MB (virt / res)
membuat 1 Mio OpenStructs: 415.13 detik, 2464 MB / 2.3GB (virt / res) # ~ 100x lebih lambat dari Hash
membuat 100K OpenStructs: 10,96 detik, 369 MB / 242MB (virt / res)

OpenStructs adalah sloooooow dan memori intensif , dan tidak skala dengan baik untuk set data besar

Membuat 1 Mio OpenStructs adalah ~ 100x lebih lambat daripada membuat 1 Mio Hash .

start = Time.now

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

stop = Time.now

puts "#{stop - start} seconds elapsed"
Tilo
sumber
Informasi yang sangat berguna untuk pecandu kinerja seperti saya. Terima kasih.
Bernardo Oliveira
Saya mengacu pada implementasi Matz tentang Ruby (MRI)
Tilo
1
Hai @Tilo, bisakah Anda membagikan kode Anda untuk mendapatkan hasil di atas? Saya ingin menggunakannya untuk membandingkan Struct & OStruct dengan Hashie :: Mash. Terima kasih.
Donny Kurnia
1
Hei @ Donny, saya baru saja melihat upvote dan menyadari bahwa ini diukur pada tahun 2011 - Saya perlu menjalankannya kembali dengan Ruby 2.1: P tidak yakin apakah saya memiliki kode itu, tetapi seharusnya mudah untuk mereproduksi. Saya akan mencoba memperbaikinya segera.
Tilo
2
Pada Ruby 2.4.1 OpenStruct dan Struct lebih cepat dalam hal kecepatan. Lihat stackoverflow.com/a/43987844/128421
the Tin Man
34

Kasus penggunaan untuk keduanya sangat berbeda.

Anda bisa menganggap kelas Struct di Ruby 1.9 sebagai setara dengan structdeklarasi di C. Di Ruby Struct.newmengambil seperangkat nama bidang sebagai argumen dan mengembalikan Kelas baru. Demikian pula, dalam C, structdeklarasi mengambil set bidang dan memungkinkan pemrogram untuk menggunakan tipe kompleks baru seperti yang ia lakukan pada tipe bawaan apa pun.

Rubi:

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

C:

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

newtype n;

Kelas OpenStruct dapat dibandingkan dengan deklarasi struct anonim dalam C. Ini memungkinkan programmer untuk membuat turunan dari tipe yang kompleks.

Rubi:

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;

Berikut adalah beberapa kasus penggunaan umum.

OpenStructs dapat digunakan untuk dengan mudah mengkonversi hash menjadi objek satu kali yang merespons semua kunci hash.

h = { a: 1, b: 2 }
o = OpenStruct.new(h)
o.a = 1
o.b = 2

Struct dapat berguna untuk definisi kelas steno.

class MyClass < Struct.new(:a,:b,:c)
end

m = MyClass.new
m.a = 1
skryl
sumber
3
Ini adalah jawaban yang bagus untuk perbedaan konseptual di antara mereka. Terima kasih telah menunjukkan anonimitas OpenStruct, saya merasa seperti itu membuatnya jauh lebih jelas.
bryant
Penjelasan hebat!
Yuri Ghensev
24

OpenStructs menggunakan lebih banyak memori dan berkinerja lebih lambat dibandingkan Structs.

require 'ostruct' 

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

Pada sistem saya, kode berikut dijalankan dalam 14 detik dan menghabiskan memori 1,5 GB. Jarak tempuh Anda mungkin bervariasi:

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

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

Itu selesai hampir secara instan dan menghabiskan 26,6 MB memori.

burtlo
sumber
3
Tetapi Anda sadar bahwa tes OpenStruct menciptakan banyak hash sementara. Saya sarankan patokan yang sedikit dimodifikasi - yang masih mendukung putusan Anda (lihat di bawah).
Robert Klemme
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
Dorian
sumber
Terima kasih untuk contohnya. Sangat membantu untuk memahami dalam praktek.
Ahsan
3

Menggunakan kode @Robert, saya menambahkan Hashie :: Mash ke item patokan dan mendapatkan hasil ini:

                           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)
Donny Kurnia
sumber
Benchmark Anda sangat aneh. Saya mendapat hasil berikut dengan ruby2.1.1 pada i5 mac: gist.github.com/nicolas-besnard/…
cappie013
Nah, hasilnya akan bervariasi antara versi ruby ​​yang digunakan, dan perangkat keras yang digunakan untuk menjalankannya. Tetapi polanya masih sama, OpenStruct adalah yang paling lambat, Struct adalah yang tercepat. Hashie jatuh di tengah.
Donny Kurnia
0

Sebenarnya bukan jawaban untuk pertanyaan, tetapi pertimbangan yang sangat penting jika Anda peduli dengan kinerja . Harap perhatikan bahwa setiap kali Anda membuat OpenStructoperasi menghapus cache metode, yang berarti aplikasi Anda akan berjalan lebih lambat. Kelambatan atau tidaknya OpenStructbukan hanya tentang cara kerjanya dengan sendirinya, tetapi implikasi yang menggunakan mereka membawa ke seluruh aplikasi: https://github.com/charliesome/charlie.bz/blob/master/posts/things-that -clear-rubys-method-cache.md # openstructs

Cris R
sumber