Kinerja Scala dibandingkan dengan Jawa

41

Pertama-tama saya ingin menjelaskan bahwa ini bukan pertanyaan bahasa-X-versus-bahasa-Y untuk menentukan mana yang lebih baik.

Saya telah menggunakan Java untuk waktu yang lama dan saya bermaksud untuk terus menggunakannya. Sejalan dengan ini, saya saat ini sedang mempelajari Scala dengan minat besar: terlepas dari hal-hal kecil yang membuat saya terbiasa dengan kesan saya adalah bahwa saya benar-benar dapat bekerja dengan sangat baik dalam bahasa ini.

Pertanyaan saya adalah: bagaimana peranti lunak yang ditulis dalam Scala dibandingkan dengan peranti lunak yang ditulis dalam Java dalam hal kecepatan eksekusi dan konsumsi memori? Tentu saja, ini adalah pertanyaan yang sulit dijawab secara umum, tetapi saya berharap bahwa konstruksi tingkat yang lebih tinggi seperti pencocokan pola, fungsi tingkat tinggi, dll, memperkenalkan beberapa overhead.

Namun, pengalaman saya saat ini di Scala terbatas pada contoh kecil di bawah 50 baris kode dan saya belum menjalankan tolok ukur apa pun hingga saat ini. Jadi, saya tidak punya data nyata.

Jika ternyata Scala memiliki beberapa overhead overhead Java, apakah masuk akal untuk menggabungkan proyek Scala / Java, di mana orang mengkode bagian yang lebih kompleks di Scala dan bagian yang kritis terhadap kinerja di Jawa? Apakah ini praktik umum?

EDIT 1

Saya telah menjalankan patokan kecil: buat daftar bilangan bulat, kalikan setiap bilangan bulat dengan dua dan masukkan ke dalam daftar baru, cetak daftar yang dihasilkan. Saya menulis implementasi Java (Java 6) dan implementasi Scala (Scala 2.9). Saya telah menjalankan keduanya di Eclipse Indigo di bawah Ubuntu 10.04.

Hasilnya sebanding: 480 ms untuk Java dan 493 ms untuk Scala (rata-rata lebih dari 100 iterasi). Berikut cuplikan yang saya gunakan.

// Java
public static void main(String[] args)
{
    long total = 0;
    final int maxCount = 100;
    for (int count = 0; count < maxCount; count++)
    {
        final long t1 = System.currentTimeMillis();

        final int max = 20000;
        final List<Integer> list = new ArrayList<Integer>();
        for (int index = 1; index <= max; index++)
        {
            list.add(index);
        }

        final List<Integer> doub = new ArrayList<Integer>();
        for (Integer value : list)
        {
            doub.add(value * 2);
        }

        for (Integer value : doub)
        {
            System.out.println(value);
        }

        final long t2 = System.currentTimeMillis();

        System.out.println("Elapsed milliseconds: " + (t2 - t1));
        total += t2 - t1;
    }

    System.out.println("Average milliseconds: " + (total / maxCount));
}

// Scala
def main(args: Array[String])
{
    var total: Long = 0
    val maxCount    = 100
    for (i <- 1 to maxCount)
    {
        val t1   = System.currentTimeMillis()
        val list = (1 to 20000) toList
        val doub = list map { n: Int => 2 * n }

        doub foreach ( println )

        val t2 = System.currentTimeMillis()

        println("Elapsed milliseconds: " + (t2 - t1))
        total = total + (t2 - t1)
    }

    println("Average milliseconds: " + (total / maxCount))
}

Jadi, dalam hal ini tampaknya overhead Scala (menggunakan rentang, peta, lambda) sangat minim, yang tidak jauh dari informasi yang disediakan oleh World Engineer.

Mungkin ada konstruksi Scala lain yang harus digunakan dengan hati-hati karena sangat berat untuk dieksekusi?

EDIT 2

Beberapa dari Anda menunjukkan bahwa println yang ada di loop dalam mengambil sebagian besar waktu eksekusi. Saya telah menghapusnya dan mengatur ukuran daftar menjadi 100.000 bukan 20.000. Rata-rata yang dihasilkan adalah 88 ms untuk Java dan 49 ms untuk Scala.

Giorgio
sumber
5
Saya membayangkan bahwa karena Scala mengkompilasi ke kode byte JVM, maka kinerja secara teoritis bisa setara dengan Java yang berjalan di bawah JVM yang sama, semua hal lain dianggap sama. Perbedaannya saya pikir adalah bagaimana kompiler Scala menciptakan kode byte dan jika melakukannya dengan efisien.
maple_shaft
2
@maple_shaft: Atau mungkin ada overhead dalam waktu kompilasi Scala?
FrustratedWithFormsDesigner
1
@Iorgio Tidak ada perbedaan runtime antara objek Scala dan objek Java, mereka semua adalah objek JVM yang didefinisikan dan berperilaku per kode byte. Scala misalnya sebagai bahasa memiliki konsep closure, tetapi ketika ini dikompilasi mereka dikompilasi ke sejumlah kelas dengan kode byte. Secara teoritis, saya secara fisik dapat menulis kode Java yang dapat dikompilasi ke kode byte yang sama persis dan perilaku runtime akan persis sama.
maple_shaft
2
@maple_shaft: Itulah yang ingin saya tuju: Saya menemukan kode Scala di atas jauh lebih ringkas dan mudah dibaca daripada kode Java yang sesuai. Saya hanya bertanya-tanya apakah masuk akal untuk menulis bagian dari proyek Scala di Jawa untuk alasan kinerja, dan apa bagian-bagian itu nantinya.
Giorgio
2
Runtime sebagian besar akan ditempati oleh panggilan println. Anda memerlukan tes yang lebih intensif komputasi.
kevin cline

Jawaban:

39

Ada satu hal yang dapat Anda lakukan secara ringkas dan efisien di Jawa yang tidak dapat Anda lakukan di Scala: enumerasi. Untuk yang lainnya, bahkan untuk konstruksi yang lambat di perpustakaan Scala, Anda bisa mendapatkan versi efisien yang bekerja di Scala.

Jadi, untuk sebagian besar, Anda tidak perlu menambahkan Java ke kode Anda. Bahkan untuk kode yang menggunakan enumerasi di Jawa, sering ada solusi di Scala yang memadai atau bagus - Saya menempatkan pengecualian pada enumerasi yang memiliki metode tambahan dan yang nilai konstan intnya digunakan.

Adapun apa yang harus diperhatikan, berikut adalah beberapa hal.

  • Jika Anda menggunakan pola memperkaya perpustakaan saya, selalu konversikan ke kelas. Sebagai contoh:

    // WRONG -- the implementation uses reflection when calling "isWord"
    implicit def toIsWord(s: String) = new { def isWord = s matches "[A-Za-z]+" }
    
    // RIGHT
    class IsWord(s: String) { def isWord = s matches "[A-Za-z]+" }
    implicit def toIsWord(s: String): IsWord = new IsWord(s)
    
  • Berhati-hatilah dengan metode pengumpulan - karena mereka sebagian besar polimorfik, JVM tidak mengoptimalkannya. Anda tidak perlu menghindarinya, tetapi perhatikan di bagian-bagian penting. Sadarilah bahwa fordalam Scala diimplementasikan melalui pemanggilan metode dan kelas anonim.

  • Jika menggunakan kelas Java, seperti String, Arrayatau AnyValkelas yang sesuai dengan primitif Java, lebih suka metode yang disediakan oleh Java ketika ada alternatif. Misalnya, gunakan lengthpada Stringdan Arraybukan size.

  • Hindari penggunaan konversi implisit yang ceroboh, karena Anda dapat menemukan diri Anda menggunakan konversi secara tidak sengaja alih-alih oleh desain.

  • Perpanjang kelas bukan sifat. Misalnya, jika Anda memanjang Function1, AbstractFunction1sebaliknya.

  • Gunakan -optimisedan spesialisasi untuk mendapatkan sebagian besar Scala.

  • Memahami apa yang terjadi: javapadalah teman Anda, dan begitu pula sekelompok bendera Scala yang menunjukkan apa yang terjadi.

  • Idi scala dirancang untuk meningkatkan kebenaran dan membuat kode lebih ringkas dan dapat dipelihara. Mereka tidak dirancang untuk kecepatan, jadi jika Anda perlu menggunakan nullalih-alih Optiondi jalur kritis, lakukanlah! Ada alasan mengapa Scala adalah multi-paradigma.

  • Ingat bahwa ukuran kinerja sebenarnya adalah menjalankan kode. Lihat pertanyaan ini untuk contoh tentang apa yang mungkin terjadi jika Anda mengabaikan aturan itu.

Daniel C. Sobral
sumber
1
+1: Banyak informasi berguna, bahkan tentang topik yang masih harus saya pelajari, tetapi bermanfaat untuk membaca beberapa petunjuk sebelum saya bisa melihatnya.
Giorgio
Mengapa pendekatan pertama menggunakan refleksi? Itu menghasilkan kelas anonim, jadi mengapa tidak menggunakannya bukan refleksi?
Oleksandr.Bezhan
@ Oleksandr.Bezhan Kelas anonim adalah konsep Java, bukan konsep Scala. Ini menghasilkan penyempurnaan tipe. Metode kelas anonim yang tidak mengesampingkan kelas dasarnya tidak dapat diakses dari luar. Hal yang sama tidak berlaku untuk perbaikan jenis Scala, jadi satu-satunya cara untuk mendapatkan metode itu adalah melalui refleksi.
Daniel C. Sobral
Ini kedengarannya sangat mengerikan. Terutama: "Berhati-hatilah dengan metode pengumpulan - karena mereka polimorfik untuk sebagian besar, JVM tidak mengoptimalkannya. Anda tidak perlu menghindarinya, tetapi perhatikan pada bagian kritis."
matt
21

Menurut Game Benchmarks untuk sistem single core, 32 bit, Scala berada pada median 80% secepat Java. Performanya kira-kira sama untuk komputer Quad Core x64. Bahkan penggunaan memori dan kepadatan kode sangat mirip dalam banyak kasus. Saya akan mengatakan berdasarkan analisis (agak tidak ilmiah) ini bahwa Anda benar dalam menyatakan bahwa Scala menambahkan beberapa overhead ke Jawa. Tampaknya tidak menambah ton overhead sehingga saya curiga diagnosis item pesanan lebih tinggi mengambil lebih banyak ruang / waktu adalah yang paling benar.

Insinyur Dunia
sumber
2
Untuk jawaban ini, silakan gunakan perbandingan langsung seperti yang disarankan halaman Bantuan ( shootout.alioth.debian.org/help.php#comparetwo )
igouy
18
  • Kinerja Scala sangat baik jika Anda hanya menulis kode Java / C-like di Scala. Compiler akan menggunakan JVM primitif untuk Int, Char, dll ketika bisa. Sementara loop sama efisiennya dalam Scala.
  • Perlu diingat bahwa ekspresi lambda dikompilasi untuk instance dari subclass anonim dari Functionkelas. Jika Anda meneruskan lambda ke map, kelas anonim perlu di-instantiated (dan beberapa penduduk lokal mungkin perlu dilewati), dan kemudian setiap iterasi memiliki overhead panggilan fungsi tambahan (dengan beberapa parameter yang lewat) dari applypanggilan.
  • Banyak kelas seperti scala.util.Randomhanya pembungkus di sekitar kelas JRE yang setara. Panggilan fungsi ekstra agak boros.
  • Watch out for implisit dalam kode kinerja-kritis. java.lang.Math.signum(x)jauh lebih langsung daripada x.signum(), yang mengkonversi ke RichIntdan kembali.
  • Manfaat kinerja utama Scala di Jawa adalah spesialisasi. Perlu diingat bahwa spesialisasi digunakan hemat dalam kode perpustakaan.
Daniel Lubarov
sumber
5
  • a) Dari pengetahuan saya yang terbatas, saya harus berkomentar, bahwa kode dalam metode utama statis tidak dapat dioptimalkan dengan sangat baik. Anda harus memindahkan kode penting ke lokasi yang berbeda.
  • b) Dari pengamatan panjang saya akan merekomendasikan untuk tidak melakukan output yang berat pada tes kinerja (kecuali apa yang ingin Anda optimalkan, tetapi siapa yang seharusnya membaca 2 juta nilai?). Anda mengukur println, yang tidak terlalu menarik. Mengganti println dengan maks:
(1 to 20000).toList.map (_ * 2).max

mengurangi waktu dari 800 ms hingga 20 pada sistem saya.

  • c) Pemahaman untuk diketahui agak lambat (sementara kita harus mengakuinya semakin baik setiap saat). Gunakan fungsi while atau tailrecursive sebagai gantinya. Tidak dalam contoh ini, di mana itu adalah lingkaran luar. Gunakan @ tailrec-annotation, untuk menguji ketepatan waktu.
  • d) Membandingkan dengan C / Assembler gagal. Misalnya, Anda tidak menulis ulang kode skala untuk arsitektur yang berbeda. Perbedaan penting lainnya dengan situasi bersejarah adalah
    • JIT-compiler, mengoptimalkan dengan cepat, dan mungkin secara dinamis, tergantung pada data input
    • Pentingnya cache gagal
    • Meningkatnya pentingnya doa paralel. Scala saat ini memiliki solusi untuk bekerja tanpa banyak overhead paralel. Itu tidak mungkin di Jawa, kecuali Anda melakukan lebih banyak pekerjaan.
Pengguna tidak diketahui
sumber
2
Saya telah menghapus println dari loop dan sebenarnya kode Scala lebih cepat dari kode Java.
Giorgio
Perbandingan dengan C dan Assembler dimaksudkan dalam pengertian berikut: bahasa tingkat yang lebih tinggi memiliki abstraksi yang lebih kuat tetapi Anda mungkin perlu menggunakan bahasa tingkat yang lebih rendah untuk kinerja. Apakah paralel ini menganggap Scala sebagai level yang lebih tinggi dan Java sebagai bahasa level yang lebih rendah? Mungkin tidak, karena Scala tampaknya memberikan kinerja yang mirip dengan Java.
Giorgio
Saya tidak akan berpikir itu akan menjadi masalah besar bagi Clojure atau Scala tetapi ketika saya dulu bermain-main dengan jRuby dan Jython saya mungkin akan menulis kode kinerja yang lebih kritis di Jawa. Dengan keduanya saya melihat perbedaan yang signifikan tetapi itu bertahun-tahun yang lalu sekarang ... bisa lebih baik.
Rig