Haruskah STL dihindari dalam aplikasi besar?

24

Ini mungkin terdengar sebagai pertanyaan aneh, tetapi di departemen saya, kami mengalami masalah dengan situasi berikut:

Kami sedang bekerja di sini pada aplikasi server, yang semakin besar dan semakin besar, bahkan pada titik yang kami pertimbangkan untuk membaginya menjadi bagian-bagian yang berbeda (file DLL), secara dinamis memuat ketika diperlukan dan membongkar sesudahnya, agar dapat menangani masalah kinerja.

Tetapi: fungsi yang kami gunakan, melewatkan parameter input dan output sebagai objek STL, dan seperti yang disebutkan dalam jawaban Stack Overflow , ini adalah ide yang sangat buruk. (Posting ini berisi beberapa ± solusi dan peretasan, tetapi semuanya tidak terlihat sangat solid.)

Jelas kita bisa mengganti parameter input / output dengan tipe C ++ standar dan membuat objek STL dari yang dulu ada di dalam fungsi, tetapi ini mungkin menyebabkan penurunan kinerja.

Apakah boleh untuk menyimpulkan bahwa, jika Anda mempertimbangkan untuk membangun sebuah aplikasi, yang mungkin tumbuh sebesar itu sehingga satu PC tidak bisa mengatasinya lagi, Anda tidak boleh menggunakan STL sebagai teknologi sama sekali?

Latar belakang lebih lanjut tentang pertanyaan ini:
Tampaknya ada beberapa kesalahpahaman tentang pertanyaan: masalahnya adalah sebagai berikut:
Aplikasi saya menggunakan sejumlah besar kinerja (CPU, memori) untuk menyelesaikan pekerjaannya, dan saya ingin membagi pekerjaan ini menjadi beberapa bagian (karena program ini sudah dibagi menjadi beberapa fungsi), tidak sulit untuk membuat beberapa DLL dari aplikasi saya dan meletakkan beberapa fungsi di tabel ekspor DLL tersebut. Ini akan menghasilkan situasi berikut:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 adalah turunan dari aplikasi, diinstal pada Machine1, sementara App_Inst2 adalah turunan dari aplikasi yang sama, diinstal pada Machine2.
DLL1.x adalah DLL, diinstal pada Machine1, sedangkan DLL2.x adalah DLL, diinstal pada Machine2.
DLLx.1 mencakup fungsi yang diekspor1.
DLLx.2 mencakup fungsi yang diekspor2.

Sekarang di Machine1 saya ingin menjalankan function1 dan function2. Saya tahu bahwa ini akan membebani Machine1, jadi saya ingin mengirim pesan ke App_Inst2, meminta instance aplikasi untuk melakukan function2.

Parameter input / output dari function1 dan function2 adalah objek STL (C ++ Standard Type Library), dan secara teratur saya mungkin mengharapkan pelanggan untuk melakukan pembaruan dari App_Inst1, App_Inst2, DLLx.y (tetapi tidak semuanya, pelanggan mungkin meningkatkan Machine1 tetapi bukan Machine2, atau hanya memutakhirkan aplikasi tetapi bukan DLL atau sebaliknya, ...). Tentunya jika antarmuka (parameter input / output) berubah, maka pelanggan terpaksa melakukan upgrade lengkap.

Namun, seperti yang disebutkan dalam URL StackOverflow yang dimaksud, kompilasi ulang App_Inst1 atau salah satu DLL yang sederhana dapat menyebabkan seluruh sistem berantakan, karenanya judul asli dari posting ini, tidak menganjurkan penggunaan STL (C ++ Template Standar Perpustakaan) untuk aplikasi besar.

Saya berharap bahwa dengan ini saya telah menyelesaikan beberapa pertanyaan / keraguan.

Dominique
sumber
44
Apakah Anda yakin mengalami masalah kinerja karena ukuran yang dapat dieksekusi ? Bisakah Anda menambahkan beberapa perincian tentang apakah realistis untuk mengasumsikan semua perangkat lunak Anda dikompilasi dengan kompiler yang sama (misalnya dalam sekali jalan di server build) atau jika Anda benar-benar ingin dipecah menjadi tim independen?
novigt
5
Pada dasarnya Anda membutuhkan orang yang pekerjaan khususnya adalah "build manager" dan "release manager", untuk memastikan bahwa semua proyek C ++ dikompilasi pada versi kompiler yang sama dan dengan pengaturan kompiler C ++ yang identik, dikompilasi dari snapshot (versi) sumber yang konsisten kode, dll. Biasanya ini dijaga di bawah panji "integrasi berkelanjutan". Jika Anda mencari secara online, Anda akan menemukan banyak artikel dan alat. Praktik yang ketinggalan jaman dapat memperkuat diri sendiri - satu praktik yang ketinggalan jaman dapat menyebabkan semua praktik menjadi usang.
rwong
8
Jawaban yang diterima dalam pertanyaan yang ditautkan menyatakan bahwa masalahnya ada pada panggilan C ++ secara umum. Jadi "C ++ tetapi bukan STL" tidak membantu, Anda harus menggunakan bare C untuk berada di sisi yang aman (tetapi juga melihat jawabannya, serialisasi kemungkinan merupakan solusi yang lebih baik).
Frax
52
memuat secara dinamis saat diperlukan dan membongkar sesudahnya, agar dapat menangani masalah kinerja Apa "masalah kinerja"? Saya tidak tahu masalah apa pun selain menggunakan terlalu banyak memori yang dapat diperbaiki dengan menurunkan hal-hal seperti DLL dari memori - dan jika itu masalahnya, perbaikan termudah adalah dengan hanya membeli lebih banyak RAM. Sudahkah Anda membuat profil aplikasi Anda untuk mengidentifikasi hambatan kinerja yang sebenarnya? Karena ini terdengar seperti masalah XY - Anda memiliki "masalah kinerja" yang tidak ditentukan dan seseorang telah memutuskan solusinya.
Andrew Henle
4
@MaxBarraclough "The STL" diterima dengan baik sebagai nama alternatif untuk wadah templated dan fungsi yang telah dimasukkan ke dalam C ++ Standard Library. Bahkan Pedoman Inti C ++, yang ditulis oleh Bjarne Stroustrup dan Herb Sutter, berulang kali membuat referensi ke "STL" ketika membicarakan hal ini. Anda tidak bisa mendapatkan sumber yang lebih otoritatif dari itu.
Sean Burton

Jawaban:

110

Ini adalah masalah XY klasik yang dingin sekali.

Masalah Anda sebenarnya adalah masalah kinerja. Namun pertanyaan Anda membuatnya jelas bahwa Anda tidak melakukan profiling atau evaluasi lain dari mana masalah kinerja sebenarnya berasal. Alih-alih, Anda berharap bahwa memecah kode Anda menjadi DLL akan secara ajaib menyelesaikan masalah (yang tidak akan, sebagai catatan), dan sekarang Anda khawatir tentang satu aspek dari non-solusi itu.

Sebaliknya, Anda harus menyelesaikan masalah yang sebenarnya. Jika Anda memiliki beberapa file yang dapat dieksekusi, periksa yang mana yang menyebabkan pelambatan. Saat Anda melakukannya, pastikan itu benar-benar program Anda yang menghabiskan semua waktu pemrosesan, dan bukan driver Ethernet yang tidak dikonfigurasi dengan benar atau semacamnya. Dan setelah itu, mulailah membuat profil berbagai tugas dalam kode Anda. Timer presisi tinggi adalah teman Anda di sini. Solusi klasiknya adalah memonitor waktu pemrosesan rata-rata dan kasus terburuk untuk sejumlah kode.

Saat Anda memiliki data, Anda bisa mengetahui cara mengatasi masalah, dan kemudian Anda bisa mengetahui di mana harus mengoptimalkan.

Graham
sumber
54
"Alih-alih, Anda berharap bahwa memecah kode Anda menjadi DLL akan secara ajaib menyelesaikan masalah (yang tidak akan, sebagai catatan)" - +1 untuk ini. Sistem operasi Anda hampir pasti mengimplementasikan permintaan paging yang mencapai hasil yang persis sama dengan fungsi bongkar muat di DLL, hanya secara otomatis daripada membutuhkan intervensi manual. Bahkan jika Anda lebih baik dalam memprediksi berapa lama sepotong kode harus bertahan setelah digunakan daripada sistem memori virtual OS (yang sebenarnya tidak mungkin), OS akan men-cache file DLL dan meniadakan upaya Anda .
Jules
@ Jules See update - mereka telah mengklarifikasi bahwa DLL hanya ada di mesin yang terpisah, jadi saya mungkin bisa melihat solusi ini berfungsi. Sekarang ada overhead komunikasi, jadi sulit untuk memastikan.
Izkata
2
@Izkata - masih belum sepenuhnya jelas, tapi saya pikir apa yang dijelaskan adalah bahwa mereka ingin memilih secara dinamis (berdasarkan konfigurasi runtime) versi dari masing-masing fungsi baik lokal atau jauh. Tetapi setiap bagian dari file EXE yang tidak pernah digunakan pada mesin yang diberikan tidak akan pernah dimuat ke dalam memori, jadi penggunaan DLL untuk tujuan ini tidak diperlukan. Cukup sertakan kedua versi dari semua fungsi di build standar, dan buat tabel pointer fungsi (atau objek yang dapat dipanggil C ++, atau metode apa pun yang Anda inginkan) untuk menjalankan versi yang sesuai dari masing-masing fungsi.
Jules
38

Jika Anda harus membagi perangkat lunak antara beberapa mesin fisik, Anda harus memiliki beberapa bentuk serialisasi ketika mengirimkan data antar mesin karena hanya dalam beberapa kasus Anda dapat benar-benar mengirim biner yang persis sama di antara mesin. Sebagian besar metode serialisasi tidak memiliki masalah dalam menangani tipe STL sehingga case bukan sesuatu yang akan membuat saya khawatir.

Jika Anda harus membagi aplikasi menjadi Shared Libraries (DLLs) (sebelum melakukan itu karena alasan kinerja, Anda benar-benar harus memastikan bahwa itu benar-benar akan memecahkan masalah kinerja Anda) melewati objek STL bisa menjadi masalah tetapi tidak harus begitu. Seperti yang telah dijelaskan oleh tautan yang Anda berikan, meneruskan objek STL berfungsi jika Anda menggunakan kompiler yang sama dan pengaturan kompiler yang sama. Jika pengguna menyediakan DLL, Anda mungkin tidak dapat dengan mudah mengandalkan ini. Jika Anda memberikan semua DLL dan mengkompilasi semuanya bersama-sama namun Anda mungkin dapat mengandalkannya dan menggunakan objek STL melintasi batas-batas DLL menjadi sangat mungkin. Anda masih harus berhati-hati untuk pengaturan kompiler Anda sehingga Anda tidak mendapatkan beberapa tumpukan berbeda jika Anda melewati kepemilikan objek, meskipun itu bukan masalah khusus STL.

Pierre Andersson
sumber
1
Ya, dan terutama bagian tentang meneruskan objek yang dialokasikan melintasi batas DLL / jadi. Secara umum satu-satunya cara untuk benar-benar menghindari masalah pengalokasi ganda adalah untuk memastikan bahwa DLL / lebih (atau pustaka!) Yang dialokasikan struktur juga membebaskannya. Itulah sebabnya Anda melihat banyak dan banyak API gaya-C ditulis dengan cara ini: API gratis eksplisit untuk setiap API yang meneruskan kembali array / struct yang dialokasikan. Masalah tambahan dengan STL adalah bahwa penelepon mungkin berharap untuk dapat mengubah struktur data kompleks yang disahkan (menambah / menghapus elemen) dan itu juga tidak dapat diizinkan. Tetapi sulit untuk ditegakkan.
davidbak
1
Jika saya harus membagi aplikasi seperti ini, saya mungkin akan menggunakan COM, tetapi ini umumnya meningkatkan ukuran kode karena setiap komponen membawa pustaka C dan C ++ mereka sendiri (yang dapat dibagikan ketika keduanya sama, tetapi dapat berbeda bila perlu, misalnya selama masa transisi, saya tidak yakin bahwa ini adalah tindakan yang tepat untuk masalah OP
Simon Richter
2
Sebagai contoh spesifik, program ini sangat mungkin di suatu tempat ingin mengirim beberapa teks ke komputer lain. Pada titik tertentu, akan ada penunjuk ke beberapa karakter yang terlibat dalam mewakili teks itu. Anda benar - benar tidak bisa hanya mentransmisikan bit dari pointer tersebut dan mengharapkan perilaku yang pasti di sisi penerima
Caleth
20

Kami sedang bekerja di sini pada aplikasi server, yang semakin besar dan semakin besar, bahkan pada titik yang kami pertimbangkan untuk membaginya menjadi bagian-bagian yang berbeda (DLL), secara dinamis memuat ketika diperlukan dan membongkar sesudahnya, agar dapat menangani masalah kinerja

RAM murah dan karenanya kode tidak aktif murah. Memuat dan membongkar kode (terutama bongkar) adalah proses yang rapuh dan tidak mungkin memiliki pengaruh signifikan pada kinerja program Anda pada perangkat keras desktop / server modern.

Cache lebih mahal tetapi itu hanya mempengaruhi kode yang baru-baru ini aktif, bukan kode yang duduk di memori yang tidak digunakan.

Secara umum program melebihi komputer mereka karena ukuran data atau waktu CPU, bukan ukuran kode. Jika ukuran kode Anda semakin besar sehingga menyebabkan masalah besar, Anda mungkin ingin melihat mengapa hal itu terjadi.

Tetapi: fungsi yang kami gunakan, melewati parameter input dan output sebagai objek STL, dan seperti yang disebutkan dalam URL StackOverflow ini, ini adalah ide yang sangat buruk.

Seharusnya ok selama dll dan executable dibangun dengan kompiler yang sama dan secara dinamis dihubungkan dengan pustaka runtime C ++ yang sama. Oleh karena itu jika aplikasi dan dll terkait itu dibangun dan digunakan sebagai satu unit maka itu seharusnya tidak menjadi masalah.

Yang bisa menjadi masalah adalah ketika perpustakaan dibangun oleh orang yang berbeda atau dapat diperbarui secara terpisah.

Apakah boleh untuk menyimpulkan bahwa, jika Anda mempertimbangkan untuk membangun sebuah aplikasi, yang mungkin tumbuh sebesar itu sehingga satu PC tidak bisa mengatasinya lagi, Anda tidak boleh menggunakan STL sebagai teknologi sama sekali?

Tidak juga.

Setelah Anda mulai menyebarkan aplikasi di beberapa mesin, Anda memiliki banyak pertimbangan tentang bagaimana Anda melewatkan data di antara mesin-mesin itu. Rincian apakah tipe STL atau lebih tipe dasar yang digunakan kemungkinan akan hilang dalam kebisingan.

Peter Green
sumber
2
Kode tidak aktif kemungkinan tidak pernah dimuat ke dalam RAM di tempat pertama. Sebagian besar sistem operasi hanya memuat halaman dari executable jika memang diperlukan.
Jules
1
@ Jules: Jika kode mati dicampur dengan kode langsung (dengan ukuran halaman = 4k rincian), maka akan dipetakan + dimuat. Cache bekerja pada granularity yang jauh lebih halus (64B), jadi sebagian besar masih benar bahwa fungsi yang tidak digunakan tidak terlalu menyakitkan. Setiap halaman membutuhkan entri TLB, dan (tidak seperti RAM) yang merupakan sumber daya runtime yang langka. (Pemetaan yang didukung file biasanya tidak menggunakan hugepage, setidaknya tidak di Linux; Satu hugepage adalah 2MiB pada x86-64, sehingga Anda dapat mencakup lebih banyak kode atau data tanpa mendapatkan TLB yang hilang dengan hugepage.)
Peter Cordes
1
Apa yang dicatat oleh @PeterCordes: Jadi, pastikan untuk menggunakan "PGO" sebagai bagian dari proses build-for-release Anda!
JDługosz
13

Tidak, saya tidak berpikir bahwa kesimpulannya mengikuti. Bahkan jika program Anda didistribusikan di beberapa mesin, tidak ada alasan bahwa menggunakan STL secara internal memaksa Anda untuk menggunakannya dalam komunikasi antar-modul / proses.

Bahkan, saya berpendapat bahwa Anda harus memisahkan desain antarmuka eksternal dari implementasi internal sejak awal, karena yang pertama akan lebih solid / sulit untuk diubah dibandingkan dengan apa yang digunakan secara internal.

Bwmat
sumber
7

Anda melewatkan inti pertanyaan itu.

Pada dasarnya ada dua jenis DLL. Anda sendiri, dan milik orang lain. "Masalah STL" adalah Anda dan mereka mungkin tidak menggunakan kompiler yang sama. Jelas, itu bukan masalah bagi DLL Anda sendiri.

MSalters
sumber
5

Jika Anda membangun DLL dari hierarki sumber yang sama secara bersamaan dengan kompiler dan opsi bangun yang sama, maka itu akan berfungsi dengan baik.

Namun cara "Windows flavoured" untuk memisahkan aplikasi menjadi beberapa bagian yang beberapa dapat digunakan kembali adalah komponen COM . Ini bisa kecil (kontrol individu atau codec) atau besar (IE tersedia sebagai kontrol COM, di mshtml.dll).

memuat secara dinamis saat dibutuhkan dan membongkar setelahnya

Untuk aplikasi server, ini mungkin akan memiliki efisiensi yang sangat buruk ; itu hanya benar-benar layak ketika Anda memiliki aplikasi yang bergerak melalui beberapa fase dalam jangka waktu yang lama sehingga Anda tahu kapan sesuatu tidak akan dibutuhkan lagi. Ini mengingatkan saya pada game DOS menggunakan mekanisme overlay.

Selain itu, jika sistem memori virtual Anda berfungsi dengan baik, ia akan menangani ini untuk Anda dengan membuka halaman kode yang tidak digunakan.

mungkin tumbuh sebesar itu sehingga satu PC tidak bisa mengatasinya lagi

Beli PC yang lebih besar.

Jangan lupa bahwa dengan optimasi yang tepat laptop dapat mengungguli cluster hadoop.

Jika Anda benar - benar membutuhkan banyak sistem, Anda harus berpikir dengan sangat hati-hati tentang batas di antara mereka, karena di situlah biaya serialisasi. Di sinilah Anda harus mulai melihat kerangka kerja seperti MPI.

pjc50
sumber
1
"Itu hanya benar-benar layak ketika Anda memiliki aplikasi yang bergerak melalui beberapa fase dalam jangka waktu yang lama sehingga Anda tahu kapan sesuatu tidak akan dibutuhkan lagi" - bahkan kemudian tidak mungkin banyak membantu, karena OS akan cache file DLL, yang kemungkinan akan berakhir dengan mengambil lebih banyak memori dari sekadar menyertakan fungsi langsung di basis Anda yang dapat dieksekusi. Overlay hanya berguna dalam sistem tanpa memori virtual, atau ketika ruang alamat virtual adalah faktor pembatas (saya kira aplikasi ini 64-bit, bukan 32 ...).
Jules
3
"Beli PC yang lebih besar" +1. Anda sekarang dapat memperoleh sistem dengan beberapa terabyte RAM. Anda dapat menyewa satu dari Amazon dengan tarif kurang dari satu jam dari satu pengembang. Berapa lama waktu pengembang yang Anda habiskan untuk mengoptimalkan kode Anda untuk mengurangi penggunaan memori?
Jules
2
Masalah terbesar yang saya hadapi dengan "beli PC yang lebih besar" terkait dengan pertanyaan "seberapa jauh skala aplikasi Anda?". Jawaban saya adalah "berapa banyak yang mau Anda keluarkan untuk tes? Karena saya berharap skala untuk sejauh ini bahwa menyewa mesin yang tepat dan menyiapkan tes yang benar-benar besar akan menelan biaya ribuan dolar. Tidak ada pelanggan kami yang bahkan tutup. untuk apa yang dapat dilakukan oleh PC CPU tunggal. " Banyak programmer yang lebih tua tidak memiliki gagasan realistis berapa banyak PC yang tumbuh; kartu video sendirian di PC modern adalah superkomputer dengan standar abad ke-20.
MSalters
Komponen COM? Pada 1990-an mungkin, tapi sekarang?
Peter Mortensen
@MSalters - benar ... siapa pun yang memiliki pertanyaan tentang seberapa jauh suatu aplikasi dapat menskala pada satu PC harus melihat spesifikasi untuk tipe contoh Amazon EC2 x1e.32xlarge - 72 total core prosesor fisik dalam mesin yang menyediakan 128 core virtual di 2.3GHz (burstable to 3.1GHz), berpotensi sebanyak 340GB / s bandwidth memori (tergantung pada jenis memori yang dipasang, yang tidak dijelaskan dalam spesifikasi), dan 3,9TiB RAM. Ini memiliki cache yang cukup untuk menjalankan sebagian besar aplikasi tanpa pernah menyentuh RAM utama. Bahkan tanpa GPU itu sekuat gugus superkomputer 500-simpul dari tahun 2000.
Jules
0

Kami sedang bekerja di sini pada aplikasi server, yang semakin besar dan semakin besar, bahkan pada titik yang kami pertimbangkan untuk membaginya menjadi bagian-bagian yang berbeda (file DLL), secara dinamis memuat ketika diperlukan dan membongkar sesudahnya, agar dapat menangani masalah kinerja.

Bagian pertama masuk akal (memisahkan aplikasi ke mesin yang berbeda, untuk alasan kinerja).

Bagian kedua (memuat dan membongkar perpustakaan) tidak masuk akal, karena ini adalah upaya ekstra untuk membuat, dan itu tidak akan (benar-benar) memperbaiki keadaan.

Masalah yang Anda gambarkan lebih baik diselesaikan dengan mesin perhitungan khusus, tetapi ini tidak akan bekerja dengan aplikasi (utama) yang sama.

Solusi klasik terlihat seperti ini:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Antara mesin front-end dan komputasi, Anda mungkin memiliki hal-hal tambahan, seperti penyeimbang beban dan pemantauan kinerja, dan menggunakan pemrosesan khusus pada mesin khusus baik untuk caching dan optimisasi throughput.

Ini sama sekali tidak menyiratkan pemuatan / pembongkaran DLL tambahan, atau apa pun yang berkaitan dengan STL.

Yaitu, gunakan STL secara internal seperti yang diperlukan, dan serialkan data Anda di antara elemen-elemen (lihat grpc dan buffer protokol dan jenis masalah yang mereka pecahkan).

Ini mengatakan, dengan informasi terbatas yang Anda berikan, ini memang terlihat seperti masalah xy klasik (seperti kata @Graham).

utnapistim
sumber