Apakah orientasi objek benar-benar mempengaruhi kinerja algoritma?

14

Orientasi objek telah banyak membantu saya dalam mengimplementasikan banyak algoritma. Namun, bahasa berorientasi objek terkadang memandu Anda dalam pendekatan "langsung" dan saya ragu apakah pendekatan ini selalu merupakan hal yang baik.

OO sangat membantu dalam pengkodean algoritma dengan cepat dan mudah. Tetapi mungkinkah OOP ini menjadi kerugian bagi perangkat lunak berdasarkan kinerja, yaitu seberapa cepat program dijalankan?

Misalnya, menyimpan node grafik dalam struktur data tampaknya "langsung" pada awalnya, tetapi jika objek Node mengandung banyak atribut dan metode, dapatkah ini mengarah pada algoritma yang lambat?

Dengan kata lain, dapatkah banyak referensi antara banyak objek yang berbeda, atau menggunakan banyak metode dari banyak kelas, menghasilkan implementasi yang "berat"?

Florents Tselai
sumber
1
Pertanyaan yang cukup aneh. Saya dapat memahami bagaimana OOP membantu pada tingkat arsitektur. Tetapi tingkat implementasi algoritma biasanya dibangun di atas abstraksi yang sangat asing bagi OOP. Jadi, kemungkinan besar, kinerja bukan masalah terbesar untuk implementasi algoritma OOP Anda. Adapun kinerja, dengan OOP hambatan tunggal terbesar biasanya terkait dengan panggilan virtual.
SK-logic
@ SK-logic> orientasi objek cenderung memanipulasi everithing dengan pointer, yang menyiratkan beban kerja yang lebih penting pada sisi alokasi memori, dan data yang tidak terlokalisasi cenderung tidak berada dalam cache CPU dan, yang tak kalah pentingnya, menyiratkan banyak hal tidak langsung percabangan (fungsi virtual) yang mematikan untuk pipa CPU. OO adalah hal yang baik, tetapi dalam beberapa kasus tentu dapat memiliki biaya kinerja.
deadalnix
Jika node dalam grafik Anda memiliki seratus atribut, Anda akan memerlukan tempat untuk menyimpannya terlepas dari paradigma yang digunakan untuk implementasi aktual, dan saya tidak melihat bagaimana satu paradigma memiliki keunggulan dalam hal ini secara umum. @deadalnix: Mungkin factores yang konstan bisa lebih buruk karena membuat optimasi tertentu lebih sulit. Tapi perhatikan bahwa saya katakan lebih keras , bukan tidak mungkin - misalnya, PyPy dapat membuka kotak objek dalam loop ketat dan JVM telah inlining panggilan fungsi virtual sejak selamanya.
Python bagus untuk algoritma prototyping, namun Anda seringkali tidak memerlukan kelas ketika mengimplementasikan algoritma tipikal di dalamnya.
Ayub
1
+1 Untuk mengaitkan orientasi objek dengan algoritma, sesuatu yang diabaikan hari ini, baik di industri perangkat lunak, dan akademi ...
umlcat

Jawaban:

16

Orientasi objek dapat mencegah optimasi algoritmik tertentu, karena enkapsulasi. Dua algoritma mungkin bekerja sangat baik bersama-sama, tetapi jika mereka tersembunyi di balik antarmuka OO, kemungkinan untuk menggunakan sinergi mereka hilang.

Lihatlah perpustakaan numerik. Banyak dari mereka (tidak hanya yang ditulis pada tahun 60an atau 70an) bukan OOP. Ada alasan untuk itu - algoritma numerik bekerja lebih baik sebagai seperangkat dipisahkan modulesdaripada sebagai hierarki OO dengan antarmuka dan enkapsulasi.

quant_dev
sumber
2
Alasan utama untuk itu adalah hanya C ++ yang tahu untuk menggunakan templat ekspresi untuk membuat versi OO seefisien.
DeadMG
4
Lihatlah perpustakaan C ++ modern (STL, Boost) - mereka juga bukan OOP. Dan bukan hanya karena kinerja. Algoritma biasanya tidak dapat direpresentasikan dengan baik dalam gaya OOP. Hal-hal seperti pemrograman generik jauh lebih cocok untuk algoritma level rendah.
SK-logic
3
Ap-ap-apa? Saya kira saya berasal dari planet yang berbeda dari quant_dev dan SK-logic. Tidak, alam semesta yang berbeda. Dengan berbagai hukum fisika dan segalanya.
Mike Nakis
5
@MikeNakis: perbedaan dalam sudut pandang terletak pada (1) apakah sepotong kode komputasi tertentu dapat mengambil manfaat dalam hal keterbacaan manusia dari OOP sama sekali (yang resep numerik tidak); (2) apakah desain kelas OOP sejajar dengan struktur data dan algoritma yang optimal (lihat jawaban saya); dan (3) apakah setiap lapisan tipuan memberikan "nilai" yang cukup (dalam hal pekerjaan yang dilakukan per panggilan fungsi atau kejelasan konseptual per lapisan) membenarkan overhead (karena tipuan, panggilan fungsi, lapisan, atau menyalin data). (4) Akhirnya, kecanggihan kompiler / JIT / optimizer adalah faktor pembatas.
rwong
2
@ MikeNakis, apa maksudmu? Apakah menurut Anda STL adalah perpustakaan OOP? Pemrograman generik tidak berjalan baik dengan OOP. Dan tidak perlu menyebutkan bahwa OOP adalah kerangka kerja yang terlalu sempit, hanya cocok untuk beberapa tugas praktis, asing untuk hal lain.
SK-logic
9

Apa yang menentukan kinerja?

Fundamental: struktur data, algoritma, arsitektur komputer, perangkat keras. Ditambah overhead.

Program OOP dapat dirancang untuk menyelaraskan dengan tepat dengan pilihan struktur data dan algoritma yang dianggap optimal oleh teori CS. Ini akan memiliki karakteristik kinerja yang sama dengan program optimal, ditambah beberapa overhead. Overhead biasanya dapat diminimalkan.

Namun, sebuah program yang awalnya dirancang hanya dengan keprihatinan OOP, tanpa memperhatikan dasar-dasarnya, mungkin awalnya kurang optimal. Sub-optimalitas kadang-kadang dapat dilepas dengan refactoring; kadang-kadang tidak - membutuhkan penulisan ulang yang lengkap.

Peringatan: apakah kinerja penting dalam perangkat lunak bisnis?

Ya, tetapi time-to-market (TTM) lebih penting, berdasarkan pesanan besarnya. Perangkat lunak bisnis menekankan pada kemampuan adaptasi kode pada aturan bisnis yang kompleks. Pengukuran kinerja harus dilakukan sepanjang siklus hidup pengembangan. (Lihat bagian: apa arti kinerja optimal? ) Hanya perangkat tambahan yang dapat dipasarkan harus dibuat, dan harus secara bertahap diperkenalkan di versi yang lebih baru.

Apa arti kinerja optimal?

Secara umum, masalah dengan kinerja perangkat lunak adalah: untuk membuktikan bahwa "versi yang lebih cepat ada", versi yang lebih cepat harus ada terlebih dahulu (yaitu tidak ada bukti selain dari dirinya sendiri).

Terkadang versi yang lebih cepat pertama kali dilihat dalam bahasa atau paradigma yang berbeda. Ini harus diambil sebagai petunjuk untuk perbaikan, bukan penilaian inferioritas dari beberapa bahasa atau paradigma lain.

Mengapa kami melakukan OOP jika hal itu dapat menghambat pencarian kami untuk kinerja yang optimal?

OOP memperkenalkan overhead (dalam ruang dan eksekusi), sebagai imbalan untuk meningkatkan "kemampuan kerja" dan karenanya nilai bisnis dari kode. Ini mengurangi biaya pengembangan lebih lanjut dan optimalisasi. Lihat @MikeNakis .

Bagian mana dari OOP yang dapat mendorong desain yang awalnya kurang optimal?

Bagian-bagian OOP yang (i) mendorong kesederhanaan / intuisi, (ii) penggunaan metode desain sehari-hari alih-alih fundamental, (iii) mencegah beberapa implementasi yang dirancang khusus untuk tujuan yang sama.

  • CIUMAN
  • YAGNI
  • KERING
  • Desain objek (misalnya dengan kartu CRC) tanpa memberikan pertimbangan yang sama dengan fundamental)

Penerapan ketat beberapa pedoman OOP (enkapsulasi, pengiriman pesan, melakukan satu hal dengan baik) memang akan menghasilkan kode lebih lambat pada awalnya. Pengukuran kinerja akan membantu mendiagnosis masalah-masalah tersebut. Selama struktur data dan algoritma sejalan dengan desain optimal yang diprediksi oleh teori, overhead biasanya dapat diminimalkan.

Apa mitigasi umum untuk overhead OOP?

Seperti yang dinyatakan sebelumnya, menggunakan struktur data yang optimal untuk desain.

Beberapa bahasa mendukung inlining kode yang dapat memulihkan beberapa kinerja runtime.

Bagaimana kita bisa mengadopsi OOP tanpa mengorbankan kinerja?

Pelajari dan terapkan OOP dan fundamentalnya.

Memang benar bahwa kepatuhan yang ketat terhadap OOP dapat mencegah Anda menulis versi yang lebih cepat. Terkadang versi yang lebih cepat hanya dapat ditulis dari awal. Inilah sebabnya mengapa membantu untuk menulis beberapa versi kode menggunakan algoritma dan paradigma yang berbeda (OOP, generik, fungsional, matematis, spageti), dan kemudian menggunakan alat optimisasi untuk membuat setiap versi mendekati kinerja maksimal yang diamati.

Apakah ada jenis kode yang tidak akan mendapat manfaat dari OOP?

(Diperluas dari diskusi antara [@quant_dev], [@ SK-logic] dan [@MikeNakis])

  1. Resep numerik, yang berasal dari matematika.
    • Persamaan matematika dan transformasi itu sendiri dapat dipahami sebagai objek.
    • Diperlukan teknik transformasi kode yang sangat canggih untuk menghasilkan kode yang dapat dieksekusi yang efisien. Implementasi naif ("papan tulis") akan memiliki kinerja buruk.
    • Namun, kompiler arus utama saat ini tidak dapat melakukannya.
    • Perangkat lunak khusus (MATLAB dan Mathematica, dll.) Memiliki JIT dan pemecah simbolis yang dapat menghasilkan kode efisien untuk beberapa sub-masalah. Pemecah khusus ini dapat dilihat sebagai kompiler tujuan khusus (mediator antara kode yang dapat dibaca manusia dan kode yang dapat dieksekusi mesin) yang akan mendapat manfaat dari desain OOP.
    • Setiap sub-masalah membutuhkan "kompiler" dan "transformasi kode" sendiri. Oleh karena itu, ini adalah area penelitian terbuka yang sangat aktif dengan hasil baru muncul setiap tahun.
    • Karena penelitian membutuhkan waktu lama, penulis perangkat lunak harus melakukan optimasi di atas kertas dan menuliskan kode yang dioptimalkan ke dalam perangkat lunak. Kode yang ditranskripsi mungkin memang tidak dapat dipahami.
  2. Kode tingkat sangat rendah.
      *
rwong
sumber
8

Itu tidak benar-benar tentang orientasi objek, tetapi tentang wadah. Jika Anda menggunakan daftar ditautkan ganda untuk menyimpan piksel dalam pemutar video Anda, itu akan menderita.

Namun jika Anda menggunakan wadah yang benar, tidak ada alasan std :: vector lebih lambat daripada array, dan karena Anda memiliki semua algoritma umum yang telah ditulis untuk itu - oleh para ahli - mungkin lebih cepat daripada kode array digulung di rumah Anda.

Martin Beckett
sumber
1
Karena kompiler kurang optimal (atau aturan bahasa pemrograman melarang mengambil keuntungan dari asumsi atau optimisasi tertentu), memang ada overhead yang tidak dapat dihapus. Juga, optimasi tertentu misalnya vektorisasi memiliki persyaratan organisasi data (misalnya struktur-array bukan struktur-array) yang OOP dapat meningkatkan atau menghambat. (Baru-baru ini saya baru saja mengerjakan tugas optimasi vektor std ::.)
rwong
5

OOP jelas merupakan ide yang bagus, dan seperti halnya ide bagus, itu bisa digunakan secara berlebihan. Dalam pengalaman saya itu terlalu banyak digunakan. Kinerja buruk dan hasil pemeliharaan yang buruk.

Ini tidak ada hubungannya dengan overhead memanggil fungsi virtual, dan tidak banyak hubungannya dengan apa yang dilakukan optimizer / jitter.

Ini semua berhubungan dengan struktur data yang, walaupun memiliki kinerja big-O terbaik, memiliki faktor konstan yang sangat buruk. Ini dilakukan dengan asumsi bahwa jika ada masalah yang membatasi kinerja dalam aplikasi, itu ada di tempat lain.

Salah satu cara manifes ini adalah berapa kali per detik baru dilakukan, yang dianggap memiliki kinerja O (1), tetapi dapat menjalankan ratusan hingga ribuan instruksi (termasuk penghapusan yang cocok atau waktu GC). Itu dapat dikurangi dengan menyimpan objek yang digunakan, tetapi itu membuat kode kurang "bersih".

Cara lain yang dimanifestasikan adalah cara orang didorong untuk menulis fungsi properti, penangan notifikasi, panggilan ke fungsi kelas dasar, semua jenis panggilan fungsi bawah tanah yang ada untuk mencoba mempertahankan konsistensi. Untuk mempertahankan konsistensi, keberhasilan mereka terbatas, tetapi mereka sangat sukses dalam membuang-buang siklus. Pemrogram memahami konsep data yang dinormalisasi tetapi mereka cenderung menerapkannya hanya untuk desain database. Mereka tidak menerapkannya pada desain struktur data, setidaknya sebagian karena OOP memberi tahu mereka bahwa mereka tidak perlu melakukannya. Sesederhana mengatur bit yang Dimodifikasi dalam suatu objek dapat mengakibatkan tsunami pembaruan yang berjalan melalui struktur data, karena tidak ada kelas yang layak untuk dikodekan oleh panggilan yang dimodifikasi dan hanya menyimpannya .

Mungkin kinerja aplikasi yang diberikan baik-baik saja seperti yang tertulis.

Di sisi lain, jika ada masalah kinerja, berikut adalah contoh bagaimana saya menyetelnya. Ini adalah proses multi-tahap. Pada setiap tahap, beberapa aktivitas tertentu menyumbang sebagian besar waktu dan dapat digantikan oleh sesuatu yang lebih cepat. (Saya tidak mengatakan "bottleneck". Ini bukan hal-hal yang dapat ditemukan oleh para pembuat profil.) Proses ini sering kali mengharuskan, untuk mendapatkan penggantian cepat, penggantian struktur data grosir. Seringkali struktur data itu ada hanya karena itu direkomendasikan praktik OOP.

Mike Dunlavey
sumber
3

Secara teori, ini bisa menyebabkan kelambatan, tetapi meskipun begitu, itu bukan algoritma yang lambat, itu akan menjadi implementasi yang lambat. Dalam praktiknya, orientasi objek akan memungkinkan Anda untuk mencoba berbagai skenario bagaimana-jika (atau meninjau kembali algoritma di masa depan) dan dengan demikian memberikan peningkatan algoritmik untuk itu, yang Anda tidak akan pernah bisa capai jika Anda telah menuliskannya dengan cara spageti di awal tempat, karena tugas itu akan menakutkan. (Anda pada dasarnya harus menulis ulang semuanya.)

Misalnya, dengan membagi berbagai tugas dan entitas ke objek yang terpotong bersih, Anda mungkin dapat dengan mudah masuk nanti dan, misalnya, menyematkan fasilitas penyimpanan di antara beberapa objek, (transparan untuknya,) yang dapat menghasilkan ribuan- perbaikan lipat.

Secara umum, jenis perbaikan yang dapat Anda capai dengan menggunakan bahasa tingkat rendah (atau trik pintar dengan bahasa tingkat tinggi) memberikan peningkatan waktu (linier) konstan, yang tidak memperhitungkan notasi oh besar. Dengan peningkatan algoritmik Anda mungkin dapat mencapai peningkatan non-linear. Itu sangat berharga.

Mike Nakis
sumber
1
+1: perbedaan antara spaghetti dan kode berorientasi objek (atau kode yang ditulis dalam paradigma yang terdefinisi dengan baik) adalah: setiap versi kode yang baik ditulis ulang membawa pemahaman baru ke dalam masalah. Setiap versi spaghetti yang ditulis ulang tidak pernah membawa wawasan apa pun.
rwong
@rwong tidak bisa menjelaskan lebih baik ;-)
umlcat
3

Tetapi mungkinkah OOP ini menjadi kerugian bagi perangkat lunak berdasarkan kinerja, yaitu seberapa cepat program dijalankan?

Sering ya !!! TAPI...

Dengan kata lain, dapatkah banyak referensi antara banyak objek yang berbeda, atau menggunakan banyak metode dari banyak kelas, menghasilkan implementasi yang "berat"?

Belum tentu. Ini tergantung pada bahasa / kompiler. Misalnya, kompiler C ++ yang mengoptimalkan, asalkan Anda tidak menggunakan fungsi virtual, sering akan menurunkan overhead objek Anda menjadi nol. Anda dapat melakukan hal-hal seperti menulis pembungkus di intsana atau pointer cerdas scoping atas pointer lama polos yang melakukan secepat menggunakan tipe data lama polos ini secara langsung.

Dalam bahasa lain seperti Jawa, ada sedikit overhead pada suatu objek (seringkali cukup kecil dalam banyak kasus, tetapi astronomi dalam beberapa kasus langka dengan objek yang sangat kecil). Sebagai contoh, Integerada jauh lebih efisien daripada int(mengambil 16 byte dibandingkan dengan 4 pada 64-bit). Namun ini bukan hanya limbah terang-terangan atau semacamnya. Sebagai gantinya, Java menawarkan hal-hal seperti refleksi pada setiap jenis tunggal yang ditetapkan pengguna secara seragam, serta kemampuan untuk menimpa fungsi apa pun yang tidak ditandai final.

Namun mari kita ambil skenario kasus terbaik: kompiler C ++ yang mengoptimalkan yang dapat mengoptimalkan antarmuka objek hingga nol overhead. Meski begitu, OOP akan sering menurunkan kinerja dan mencegahnya mencapai puncak. Itu mungkin terdengar seperti paradoks lengkap: bagaimana mungkin? Masalahnya terletak pada:

Desain dan Enkapsulasi Antarmuka

Masalahnya adalah bahwa bahkan ketika kompiler dapat menekan struktur objek hingga nol overhead (yang setidaknya sangat sering benar untuk mengoptimalkan kompiler C ++), enkapsulasi dan desain antarmuka (dan dependensi terakumulasi) dari objek berbutir halus akan sering mencegah sebagian besar representasi data optimal untuk objek yang dimaksudkan untuk dikumpulkan oleh massa (yang sering terjadi pada perangkat lunak yang sangat kritis terhadap kinerja).

Ambil contoh ini:

class Particle
{
public:
    ...

private:
    double birth;                // 8 bytes
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
    /*padding*/                  // 4 bytes of padding
};
Particle particles[1000000];     // 1mil particles (~24 megs)

Katakanlah pola akses memori kita adalah dengan hanya melalui partikel-partikel ini secara berurutan dan memindahkannya di sekitar setiap frame berulang kali, memantulkannya dari sudut layar dan kemudian memberikan hasilnya.

Kita sudah dapat melihat overhead padding 4 byte yang mencolok diperlukan untuk menyelaraskan birthanggota dengan benar ketika partikel-partikel dikumpulkan secara berdekatan. Sudah ~ 16,7% dari memori terbuang sia-sia dengan ruang yang digunakan untuk penyelarasan.

Ini mungkin tampak diperdebatkan karena kami memiliki gigabyte DRAM hari ini. Namun, bahkan mesin paling kejam yang kita miliki saat ini sering hanya memiliki hanya 8 megabyte ketika datang ke wilayah cache CPU yang paling lambat dan terbesar (L3). Semakin sedikit yang dapat kami muat di sana, semakin banyak kami membayar untuk itu dalam hal akses DRAM berulang, dan semakin lambat hasilnya. Tiba-tiba, membuang 16,7% dari memori tidak lagi seperti kesepakatan sepele.

Kami dapat dengan mudah menghilangkan overhead ini tanpa berdampak pada penyelarasan lapangan:

class Particle
{
public:
    ...

private:
    float x;                     // 4 bytes
    float y;                     // 4 bytes
    float z;                     // 4 bytes
};
Particle particles[1000000];     // 1mil particles (~12 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Sekarang kami telah mengurangi memori dari 24 MB menjadi 20 MB. Dengan pola akses berurutan, mesin sekarang akan mengkonsumsi data ini sedikit lebih cepat.

Tapi mari kita lihat birthbidang ini sedikit lebih dekat. Katakanlah itu mencatat waktu mulai ketika sebuah partikel dilahirkan (dibuat). Bayangkan bidang hanya diakses ketika sebuah partikel pertama kali dibuat, dan setiap 10 detik untuk melihat apakah sebuah partikel akan mati dan terlahir kembali di lokasi acak di layar. Dalam hal ini, birthadalah bidang yang dingin. Itu tidak diakses di loop kinerja-kritis kami.

Akibatnya, data kritis kinerja aktual bukan 20 megabyte tetapi sebenarnya blok bersebelahan 12 megabyte. Memori panas aktual yang sering kita akses telah menyusut menjadi setengah ukurannya! Harapkan peningkatan yang signifikan atas solusi 24 megabyte kami yang asli (tidak perlu diukur - sudah melakukan hal semacam ini ribuan kali, tapi jangan ragu jika ragu).

Namun perhatikan apa yang kami lakukan di sini. Kami benar-benar memecahkan enkapsulasi objek partikel ini. Keadaannya sekarang dibagi antara Particlebidang pribadi jenis dan array paralel yang terpisah. Dan di situlah desain berorientasi objek granular menghalangi.

Kami tidak dapat mengekspresikan representasi data yang optimal saat terbatas pada desain antarmuka objek tunggal, sangat granular seperti partikel tunggal, piksel tunggal, bahkan vektor 4 komponen tunggal, bahkan mungkin objek "makhluk" tunggal dalam game , dll. Kecepatan seekor cheetah akan sia-sia jika itu berdiri di sebuah pulau kecil yang hanya 2 meter persegi, dan itulah yang sering dilakukan oleh desain berorientasi objek dalam hal kinerja. Ini membatasi representasi data ke sifat yang tidak optimal.

Untuk mengambil ini lebih jauh, katakanlah karena kita hanya memindahkan partikel, kita sebenarnya dapat mengakses bidang x / y / z dalam tiga loop terpisah. Dalam hal ini, kita dapat mengambil manfaat dari intrinsik SIMD gaya SoA dengan register AVX yang dapat membuat vektor 8 operasi SPFP secara paralel. Tetapi untuk melakukan ini, kita sekarang harus menggunakan representasi ini:

float particle_x[1000000];       // 1mil particle X positions (~4 megs)
float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
double particle_birth[1000000];  // 1mil particle births (~8 bytes)

Sekarang kita terbang dengan simulasi partikel, tetapi lihat apa yang terjadi pada desain partikel kita. Itu telah benar-benar dihancurkan, dan kami sekarang melihat 4 array paralel dan tidak ada objek untuk mengagregasi mereka sama sekali. ParticleDesain berorientasi objek kami telah menjadi sayonara.

Ini terjadi pada saya berkali-kali bekerja di bidang kinerja kritis di mana pengguna menuntut kecepatan dengan hanya kebenaran yang menjadi satu hal yang lebih mereka tuntut. Desain berorientasi objek kecil kecil ini harus dihancurkan, dan kerusakan berjenjang sering mengharuskan kami menggunakan strategi depresiasi lambat menuju desain yang lebih cepat.

Larutan

Skenario di atas hanya menyajikan masalah dengan desain berorientasi objek granular . Dalam kasus-kasus tersebut, kita seringkali harus menghancurkan struktur untuk mengekspresikan representasi yang lebih efisien sebagai akibat dari repetisi SoA, pemisahan medan panas / dingin, pengurangan padding untuk pola akses berurutan (padding terkadang membantu kinerja dengan akses acak) pola dalam kasus AoS, tetapi hampir selalu menjadi penghalang untuk pola akses berurutan), dll.

Namun kita dapat mengambil representasi akhir yang kita tentukan dan masih memodelkan antarmuka berorientasi objek:

// Represents a collection of particles.
class ParticleSystem
{
public:
    ...

private:
    double particle_birth[1000000];  // 1mil particle births (~8 bytes)
    float particle_x[1000000];       // 1mil particle X positions (~4 megs)
    float particle_y[1000000];       // 1mil particle Y positions (~4 megs)
    float particle_z[1000000];       // 1mil particle Z positions (~4 megs)
};

Sekarang kita baik-baik saja. Kita bisa mendapatkan semua barang berorientasi objek yang kita sukai. Cheetah memiliki seluruh negara untuk berlari secepat mungkin. Desain antarmuka kami tidak lagi menjebak kami ke sudut kemacetan.

ParticleSystembahkan berpotensi abstrak dan menggunakan fungsi virtual. Ini diperdebatkan sekarang, kami membayar biaya overhead pada pengumpulan tingkat partikel , bukan pada tingkat per-partikel . Overhead adalah 1/1000.000 dari yang seharusnya jika kita memodelkan objek pada tingkat partikel individu.

Jadi itulah solusi di bidang kritis kinerja sejati yang menangani beban berat, dan untuk semua jenis bahasa pemrograman (teknik ini menguntungkan C, C ++, Python, Java, JavaScript, Lua, Swift, dll). Dan itu tidak dapat dengan mudah dilabeli sebagai "optimasi prematur", karena ini berkaitan dengan desain antarmuka dan arsitektur . Kami tidak dapat menulis basis kode yang memodelkan partikel tunggal sebagai objek dengan muatan kapal dari dependensi klien ke aParticle'santarmuka publik dan kemudian berubah pikiran nanti. Saya telah melakukan banyak hal ketika dipanggil untuk mengoptimalkan basis kode lama, dan itu bisa berakhir berbulan-bulan menulis ulang puluhan ribu baris kode dengan hati-hati untuk menggunakan desain bulkier. Ini idealnya mempengaruhi bagaimana kita mendesain sesuatu di muka asalkan kita bisa mengantisipasi beban yang berat.

Saya terus menggemakan jawaban ini dalam beberapa bentuk atau yang lain di banyak pertanyaan kinerja, dan terutama yang berhubungan dengan desain berorientasi objek. Desain berorientasi objek masih dapat kompatibel dengan kebutuhan kinerja permintaan tertinggi, tetapi kita harus mengubah sedikit cara berpikir kita tentangnya. Kita harus memberi cheetah ruang itu untuk berlari secepat mungkin, dan itu seringkali mustahil jika kita mendesain objek kecil mungil yang nyaris tidak menyimpan keadaan apa pun.


sumber
Fantastis. Inilah yang sebenarnya saya cari dalam hal menggabungkan OOP dengan permintaan kinerja tinggi. Saya benar-benar tidak bisa mengerti mengapa itu tidak lebih dipilih.
pbx
2

Ya, pola pikir berorientasi objek pasti bisa netral atau negatif ketika datang ke pemrograman kinerja tinggi, baik pada level algoritmik maupun implementasi. Jika OOP menggantikan analisis algoritmik, ini dapat mengarahkan Anda ke implementasi prematur dan, pada level terendah, abstraksi OOP harus disingkirkan.

Masalahnya berasal dari penekanan OOP pada pemikiran tentang contoh individu. Saya pikir itu adil untuk mengatakan bahwa cara berpikir OOP tentang suatu algoritma adalah dengan memikirkan serangkaian nilai tertentu dan mengimplementasikannya seperti itu. Jika itu adalah jalur level tertinggi Anda, Anda tidak mungkin mewujudkan transformasi atau restrukturisasi yang akan menghasilkan keuntungan Big O.

Pada tingkat algoritmik, sering kali dipikirkan tentang gambaran yang lebih besar dan kendala atau hubungan antara nilai-nilai yang mengarah pada perolehan Big O. Contohnya mungkin bahwa tidak ada dalam pola pikir OOP yang akan mengarahkan Anda untuk mengubah "menjumlahkan bilangan bulat yang berkelanjutan" dari satu loop ke(max + min) * n/2

Pada tingkat implementasi, meskipun komputer "cukup cepat" untuk sebagian besar algoritma tingkat aplikasi, dalam kode tingkat kinerja rendah yang kritis, banyak yang khawatir tentang lokalitas. Sekali lagi, OOP menekankan pada berpikir tentang contoh individu dan nilai-nilai satu melewati loop dapat menjadi negatif. Dalam kode berperforma tinggi, alih-alih menulis loop langsung, Anda mungkin ingin membuka sebagian loop, mengelompokkan beberapa instruksi pemuatan di bagian atas, kemudian mentransformasikannya dalam grup, lalu menuliskannya dalam grup. Sementara itu Anda akan memperhatikan perhitungan menengah dan, sangat, untuk akses cache dan memori; masalah di mana abstraksi OOP tidak lagi valid. Dan, jika diikuti, bisa menyesatkan: pada level ini, Anda harus tahu dan memikirkan tentang representasi level mesin.

Ketika Anda melihat sesuatu seperti Intel Performance Primitives, Anda benar-benar memiliki ribuan implementasi Fast Fourier Transform, masing-masing di-tweak untuk bekerja lebih baik untuk ukuran data dan arsitektur mesin tertentu. (Menariknya, ternyata sebagian besar implementasi ini dihasilkan oleh mesin: Markus Püschel Automatic Performance Programming )

Tentu saja, seperti yang sebagian besar jawaban katakan, untuk sebagian besar pengembangan, untuk sebagian besar algoritma, OOP tidak relevan dengan kinerja. Selama Anda tidak "pesimis prematur" dan menambahkan banyak panggilan non-lokal, thispointer tidak ada di sini atau di sana.

Larry OBrien
sumber
0

Ini terkait, dan sering diabaikan.

Ini bukan jawaban yang mudah, tergantung pada apa yang ingin Anda lakukan.

Beberapa algoritma lebih baik dalam kinerja menggunakan pemrograman terstruktur biasa, sementara yang lain, lebih baik menggunakan orientasi objek.

Sebelum Orientasi Objek, banyak sekolah mengajarkan (ed) desain algoritma dengan pemrograman terstruktur. Saat ini, banyak sekolah, mengajarkan pemrograman berorientasi objek, mengabaikan desain & kinerja algoritma ..

Tentu saja, ada sekolah yang mengajarkan pemrograman terstruktur, yang sama sekali tidak peduli dengan algoritma.

umlcat
sumber
0

Kinerja semua turun ke siklus CPU dan memori pada akhirnya. Tetapi perbedaan persentase antara overhead pengiriman OOP dan enkapsulasi dan semantik pemrograman yang lebih luas mungkin atau mungkin tidak menjadi persentase yang cukup signifikan untuk membuat perbedaan yang nyata dalam kinerja aplikasi Anda. Jika suatu aplikasi disk atau data-cache-miss terikat, overhead OOP mungkin benar-benar hilang dalam kebisingan.

Namun, di loop dalam sinyal real-time dan pemrosesan gambar dan aplikasi terikat numerik lainnya, perbedaannya mungkin merupakan persentase signifikan dari siklus CPU dan memori, yang dapat membuat biaya overhead OOP jauh lebih mahal untuk dieksekusi.

Semantik dari bahasa OOP tertentu mungkin atau mungkin tidak mengekspos peluang yang cukup bagi kompiler untuk mengoptimalkan siklus-siklus itu, atau agar sirkuit prediksi cabang CPU selalu menebak dengan benar dan menutup siklus-siklus itu dengan pra-pengambilan dan pemipaan.

hotpaw2
sumber
0

Desain berorientasi objek yang baik membantu saya mempercepat aplikasi. A harus menghasilkan grafik yang kompleks dengan cara algoritmik. Saya melakukannya melalui otomasi Microsoft Visio. Saya bekerja, tetapi sangat lambat. Untungnya, saya telah memasukkan level abstraksi ekstra antara logika (algoritma) dan hal-hal Visio. Komponen Visio saya mengekspos fungsinya melalui antarmuka. Ini memungkinkan saya untuk dengan mudah mengganti komponen yang lambat dengan membuat file SVG lain, yang setidaknya 50 kali lebih cepat! Tanpa pendekatan berorientasi objek yang bersih, kode untuk algoritma dan kontrol Visi akan terjerat dengan cara, yang akan mengubah perubahan menjadi mimpi buruk.

Olivier Jacot-Descombes
sumber
maksud Anda OO Design diterapkan dengan bahasa prosedural, atau OO Design & OO bahasa pemrograman?
umlcat
Saya berbicara tentang aplikasi C #. Baik desain dan bahasanya adalah OO. Ketika OO-iness bahasa akan memperkenalkan beberapa hit kinerja kecil (panggilan metode virtual, pembuatan objek, akses anggota melalui antarmuka), desain OO membantu saya dalam menciptakan aplikasi yang jauh lebih cepat. Yang ingin saya katakan adalah: Lupakan hit kinerja karena OO (bahasa dan desain). Kecuali jika Anda melakukan perhitungan berat dengan jutaan iterasi, OO tidak akan membahayakan Anda. Di mana Anda biasanya kehilangan banyak waktu adalah I / O.
Olivier Jacot-Descombes