Bisakah bahasa OO modern bersaing dengan kinerja toko array C ++?

40

Saya hanya memperhatikan bahwa setiap bahasa pemrograman OO modern yang paling tidak saya kenal (yang pada dasarnya hanya Java, C # dan D) memungkinkan array kovarian. Artinya, array string adalah array objek:

Object[] arr = new String[2];   // Java, C# and D allow this

Array kovarian adalah lubang pada sistem tipe statis. Mereka membuat kesalahan tipe mungkin yang tidak dapat dideteksi pada waktu kompilasi, jadi setiap penulisan ke array harus diperiksa saat runtime:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Sepertinya ini adalah kinerja yang buruk jika saya melakukan banyak toko array.

C ++ tidak memiliki array kovarian, jadi tidak perlu melakukan pemeriksaan runtime, yang berarti tidak ada penalti kinerja.

Apakah ada analisis yang dilakukan untuk mengurangi jumlah pemeriksaan runtime yang diperlukan? Misalnya, jika saya katakan:

arr[1] = arr[0];

orang bisa berpendapat bahwa toko itu tidak mungkin gagal. Saya yakin ada banyak kemungkinan optimasi lain yang belum saya pikirkan.

Apakah kompiler modern benar-benar melakukan optimasi semacam ini, atau apakah saya harus hidup dengan kenyataan bahwa, misalnya, Quicksort selalu melakukan O (n log n) pemeriksaan runtime yang tidak perlu?

Bisakah bahasa OO modern menghindari overhead yang dibuat dengan mendukung array co-varian?

fredoverflow
sumber
7
Saya bingung mengapa Anda menyarankan C ++ lebih cepat daripada bahasa lain di fitur yang C ++ bahkan tidak mendukung.
17
@eco: C ++ lebih cepat dengan akses array karena tidak mendukung array co-variant. Fred ingin tahu apakah itu mungkin bagi "bahasa OO modern" untuk menghindari overhead array co-varian untuk lebih dekat dengan kecepatan C ++.
Mooing Duck
13
"kode yang mengkompilasi tetapi melempar pengecualian pada saat run-time adalah berita buruk"
4
@Jesse Dan jutaan orang menulis kode yang andal dan sangat skalabel dalam bahasa dinamis. Imo: Jika Anda tidak menulis kasus uji untuk kode Anda, saya tidak peduli apakah ada kesalahan kompiler atau tidak, saya tidak akan mempercayainya.
Voo
7
@Jesse Dan kapan lagi Anda mengharapkan pengecualian tetapi saat runtime? Masalahnya bukan kode yang melempar pengecualian pada saat runtime - ada banyak kasus yang masuk akal - masalahnya adalah kode yang dijamin salah yang tidak ditangkap secara statis oleh kompiler tetapi menghasilkan pengecualian pada runtime.
Jonathan M Davis

Jawaban:

33

D tidak memiliki array kovarian. Itu memungkinkan mereka sebelum rilis terbaru ( dmd 2.057 ), tetapi bug itu telah diperbaiki.

Array dalam D secara efektif hanya sebuah struct dengan sebuah pointer dan panjang:

struct A(T)
{
    T* ptr;
    size_t length;
}

Pemeriksaan batas dilakukan secara normal ketika mengindeks array, tetapi dihapus ketika Anda kompilasi -release. Jadi, dalam mode rilis, tidak ada perbedaan kinerja nyata antara array di C / C ++ dan yang di D.

Jonathan M Davis
sumber
Muncul tidak - d-programming-language.org/arrays.html mengatakan "A statis array yang T[dim]dapat implisit dikonversi ke salah satu dari berikut: ... U[]... Sebuah array dinamis T[]dapat implisit dikonversi ke salah satu dari berikut: U[]. .. dimana Ukelas dasar dari T. "
Ben Voigt
2
@ BenVoigt Maka dokumen online perlu diperbarui. Sayangnya, mereka tidak selalu 100% terbaru. Konversi akan bekerja dengan const U[], sejak itu Anda tidak dapat menetapkan jenis yang salah untuk elemen array, tetapi T[]pasti tidak akan dikonversi U[]selama U[]dapat diubah. Mengizinkan array kovarian seperti yang dilakukannya sebelumnya adalah cacat desain serius yang sekarang telah diperbaiki.
Jonathan M Davis
@ JonathanMDavis: Array kovarian adalah icky, tetapi bekerja dengan baik untuk skenario kode tertentu yang hanya akan menulis ke elemen array yang telah dibaca dari array yang sama . Bagaimana D mengizinkan seseorang untuk menulis metode yang dapat mengurutkan array dari tipe arbitrer?
supercat
@supercat Jika Anda ingin menulis suatu fungsi yang mengurutkan tipe yang sewenang-wenang, maka templatize. misalnya dlang.org/phobos/std_algorithm.html#sort
Jonathan M Davis
21

Ya, satu optimasi penting adalah ini:

sealed class Foo
{
}

Di C #, kelas ini tidak bisa menjadi supertipe untuk tipe apa pun, jadi Anda bisa menghindari pemeriksaan untuk array tipe Foo.

Dan untuk pertanyaan kedua, dalam F # co-varian array tidak diperbolehkan (tapi saya rasa cek akan tetap di CLR kecuali ditemukan tidak perlu dalam optimasi saat runtime)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Masalah yang agak terkait adalah memeriksa array terikat. Ini mungkin merupakan bacaan yang menarik (tetapi lama) tentang optimisasi yang dilakukan di CLR (kovarians juga disebutkan 1 tempat): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds -check-eliminasi-di-the-clr.aspx

Lasse Espeholt
sumber
2
Scala juga mencegah konstruk ini: val a = Array("st"); val b: Array[Any] = ailegal. (Namun, array di Scala adalah ... sihir khusus ... karena JVM yang mendasarinya digunakan.)
pst
13

Jawaban Jawa:

Saya kira Anda belum benar-benar membandingkan kode, bukan? Secara umum 90% dari semua gips dinamis di Jawa adalah gratis karena JIT dapat mengeliminasi mereka (quicksort harus menjadi contoh yang baik untuk ini) dan sisanya adalah satu ld/cmp/brurutan yang benar-benar dapat diprediksi (jika tidak, mengapa juga kode Anda melemparkan semua pengecualian pemeran dinamis itu?).

Kami melakukan pemuatan jauh lebih awal daripada perbandingan yang sebenarnya, cabang diprediksi dengan benar di 99,9999% (dibuat statistik!) Dari semua kasus sehingga kami tidak menghentikan pipa (dengan asumsi kami tidak menekan memori dengan beban, jika tidak baik itu akan terlihat, tetapi kemudian beban diperlukan bagaimanapun). Karenanya biayanya adalah 1 siklus clock JIKA JIT tidak bisa menghindari cek sama sekali.

Beberapa overhead? Tentu, tapi saya ragu Anda akan menyadarinya ..


Untuk membantu mendukung jawaban saya, silakan lihat blogpost Dr. Cliff Click ini membahas kinerja Java vs C.

Voo
sumber
10

D tidak mengizinkan array kovarian.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Seperti yang Anda katakan, itu akan menjadi lubang di sistem tipe untuk memungkinkan ini.

Anda dapat dimaafkan atas kesalahan tersebut, karena bug ini baru saja diperbaiki pada DMD terbaru, dirilis pada 13 Desember.

Akses array di D sama cepatnya dengan di C atau C ++.

Peter Alexander
sumber
Menurut d-programming-language.org/arrays.html "Array statis T [redup] dapat secara implisit dikonversi ke salah satu dari berikut: ... U [] ... Array dinamis T [] dapat secara implisit dikonversi ke salah satu dari yang berikut: U [] ... di mana U adalah kelas dasar dari T. "
Ben Voigt
1
@ BenVoigt: Dokumen kedaluwarsa.
BCS
1
@ BenVoigt: Saya telah membuat permintaan tarik untuk memperbarui dokumentasi. Semoga ini akan segera teratasi. Terima kasih telah menunjukkannya.
Peter Alexander
5

Dari tes yang saya lakukan pada laptop murah, perbedaan antara menggunakan int[]dan Integer[]sekitar 1,0 ns. Perbedaannya mungkin karena pemeriksaan tambahan untuk jenisnya.

Umumnya Objek hanya digunakan untuk logika tingkat yang lebih tinggi ketika tidak setiap ns diperhitungkan. Jika Anda perlu menyimpan setiap ns, saya akan menghindari penggunaan konstruksi tingkat tinggi seperti Objects. Tugas saja cenderung menjadi faktor yang sangat kecil dalam setiap program nyata. misalnya membuat Obyek baru pada mesin yang sama adalah 5 ns.

Panggilan untuk membandingkanKemungkinan jauh lebih mahal, terutama jika Anda menggunakan objek kompleks seperti String.

Peter Lawrey
sumber
2

Anda bertanya tentang bahasa OO modern lainnya? Nah, Delphi menghindari masalah ini sepenuhnya dengan stringmenjadi primitif, bukan objek. Jadi array string adalah array string dan tidak ada yang lain, dan operasi apa pun pada mereka secepat kode asli, tanpa jenis memeriksa overhead.

Namun, array string jarang digunakan; Pemrogram Delphi cenderung menyukai TStringListkelas. Untuk jumlah overhead yang minimal, ia menyediakan serangkaian metode string-group yang berguna dalam banyak situasi sehingga kelasnya telah dibandingkan dengan Knife Swiss Army. Jadi itu idiomatis untuk menggunakan objek daftar string daripada array string.

Adapun objek lain secara umum, masalahnya tidak ada karena di Delphi, seperti di C ++, array tidak kovarian, untuk mencegah jenis lubang sistem jenis yang dijelaskan di sini.

Mason Wheeler
sumber
1

atau apakah saya harus hidup dengan fakta bahwa, misalnya, Quicksort selalu melakukan pemeriksaan runtime O (n log n) yang tidak perlu?

Kinerja CPU tidak monoton, yang berarti bahwa program yang lebih lama bisa lebih cepat daripada yang lebih pendek (ini tergantung CPU, dan itu berlaku untuk arsitektur x86 dan amd64 yang umum). Jadi ada kemungkinan bahwa sebuah program melakukan pengecekan terikat pada array sebenarnya lebih cepat daripada program yang disimpulkan dari sebelumnya dengan menghapus pengecekan terikat ini.

Alasan perilaku ini adalah pengecekan terikat memodifikasi penyelarasan kode dalam memori, akan memodifikasi frekuensi hit cache, dll.

Jadi ya, hiduplah dengan fakta bahwa Quicksort selalu melakukan O (n log n) pemeriksaan palsu dan mengoptimalkan setelah profil.

pengguna40989
sumber
1

Scala adalah bahasa OO yang memiliki array yang invarian, bukan kovarian. Ini menargetkan JVM, jadi tidak ada kinerja yang menang di sana, tetapi menghindari kesalahan umum untuk Java dan C # yang membahayakan keamanan tipe mereka untuk alasan kompatibilitas ke belakang.

Pillsy
sumber