ประสิทธิภาพของ Scala เทียบกับ Java


41

ก่อนอื่นฉันต้องการทำให้ชัดเจนว่านี่ไม่ใช่คำถามภาษา -X- กับภาษา -Y เพื่อตรวจสอบที่ดีกว่า

ฉันใช้ Java มานานแล้วและตั้งใจจะใช้มันต่อไป ขนานไปกับสิ่งนี้ฉันกำลังเรียน Scala ด้วยความสนใจอย่างยิ่งนอกเหนือจากสิ่งเล็ก ๆ น้อย ๆ ที่ทำให้ฉันคุ้นเคยกับความประทับใจของฉันก็คือฉันสามารถทำงานได้ดีมากในภาษานี้

คำถามของฉันคือซอฟต์แวร์ที่เขียนใน Scala เปรียบเทียบกับซอฟต์แวร์ที่เขียนใน Java ในแง่ของความเร็วในการดำเนินการและการใช้หน่วยความจำได้อย่างไร แน่นอนว่านี่เป็นคำถามที่ตอบยากโดยทั่วไป แต่ฉันคาดหวังว่าการสร้างระดับที่สูงขึ้นเช่นการจับคู่รูปแบบฟังก์ชันการเรียงลำดับที่สูงขึ้น ฯลฯ แนะนำค่าใช้จ่ายบางอย่าง

อย่างไรก็ตามประสบการณ์ปัจจุบันของฉันใน Scala จำกัด เพียงตัวอย่างเล็ก ๆ ภายใต้รหัส 50 บรรทัดและฉันยังไม่ได้ใช้การวัดประสิทธิภาพใด ๆ จนถึงตอนนี้ ดังนั้นฉันไม่มีข้อมูลจริง

ถ้าปรากฎว่า Scala มีค่าใช้จ่ายในการเขียน Java ค่อนข้างสมเหตุสมผลหรือไม่ที่จะรวมโครงการ Scala / Java เข้าด้วยกันโดยที่หนึ่งรหัสในส่วนที่ซับซ้อนมากขึ้นใน Scala และส่วนที่มีความสำคัญต่อประสิทธิภาพใน Java นี่เป็นเรื่องธรรมดาไหม

แก้ไข 1

ฉันใช้เกณฑ์มาตรฐานขนาดเล็ก: สร้างรายการจำนวนเต็มคูณแต่ละจำนวนเต็มด้วยสองและใส่ไว้ในรายการใหม่พิมพ์รายการผลลัพธ์ ฉันเขียนการติดตั้ง Java (Java 6) และการปรับใช้ Scala (Scala 2.9) ฉันรันทั้ง Eclipse Indigo ภายใต้ Ubuntu 10.04

ผลการเปรียบเทียบ: 480 ms สำหรับ Java และ 493 ms สำหรับ Scala (เฉลี่ยมากกว่า 100 ซ้ำ) นี่คือตัวอย่างที่ฉันใช้

// 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))
}

ดังนั้นในกรณีนี้ดูเหมือนว่าค่าใช้จ่ายสกาล่า (โดยใช้ช่วง, แผนที่, แลมบ์ดา) นั้นน้อยมากจริง ๆ ซึ่งไม่ไกลจากข้อมูลที่วิศวกรโลกมอบให้

อาจมีโครงสร้างแบบสกาล่าอื่น ๆ ที่ควรใช้ด้วยความระมัดระวังเพราะมีความหนักเป็นพิเศษในการดำเนินการหรือไม่

แก้ไข 2

บางท่านชี้ให้เห็นว่า println ในลูปด้านในใช้เวลาส่วนใหญ่ในการประมวลผล ฉันได้ลบออกแล้วตั้งขนาดของรายการเป็น 100000 แทนที่จะเป็น 20,000 ผลลัพธ์เฉลี่ยคือ 88 ms สำหรับ Java และ 49 ms สำหรับ Scala


5
ฉันคิดว่าเนื่องจาก Scala รวบรวมรหัส JVM แล้วประสิทธิภาพอาจเทียบเท่ากับ Java ที่ทำงานภายใต้ JVM เดียวกันทุกสิ่งอื่น ๆ เท่าเทียมกัน ความแตกต่างที่ฉันคิดว่าเป็นวิธีคอมไพเลอร์ Scala สร้างรหัสไบต์และถ้ามันทำอย่างมีประสิทธิภาพ
maple_shaft

2
@maple_shaft: หรืออาจจะมีค่าใช้จ่ายในการรวบรวมสกาล่าเวลา?
FrustratedWithFormsDesigner

1
@Giorgio ไม่มีความแตกต่าง runtime ระหว่างวัตถุ Scala และวัตถุ Java พวกเขาเป็นวัตถุ JVM ทั้งหมดที่กำหนดไว้และปฏิบัติตามรหัสไบต์ Scala เช่นเป็นภาษาที่มีแนวคิดของการปิด แต่เมื่อมีการรวบรวมพวกเขาจะรวบรวมเป็นจำนวนชั้นเรียนที่มีรหัสไบต์ ในทางทฤษฎีแล้วฉันสามารถเขียนโค้ด Java ที่สามารถคอมไพล์โค้ดไบต์เดียวกันและพฤติกรรมรันไทม์จะเหมือนกันทุกประการ
maple_shaft

2
@maple_shaft: นั่นคือสิ่งที่ฉันตั้งใจจะ: ฉันพบรหัส Scala ข้างต้นกระชับและอ่านง่ายกว่าโค้ด Java ที่ตรงกัน ฉันแค่สงสัยว่ามันจะสมเหตุสมผลหรือไม่ที่จะเขียนบางส่วนของโครงการ Scala ใน Java ด้วยเหตุผลด้านประสิทธิภาพและสิ่งเหล่านั้นจะเป็นเช่นไร
Giorgio

2
รันไทม์ส่วนใหญ่จะถูกครอบครองโดยการเรียก println คุณต้องการการทดสอบที่ต้องใช้ความพยายามมากขึ้น
วินไคลน์

คำตอบ:


39

มีสิ่งหนึ่งที่คุณสามารถทำได้อย่างกระชับและมีประสิทธิภาพใน Java ที่คุณไม่สามารถทำได้ใน Scala: การแจกแจง สำหรับทุกสิ่งทุกอย่างแม้แต่สำหรับสิ่งก่อสร้างที่ช้าในห้องสมุดของ Scala คุณสามารถรับเวอร์ชันที่มีประสิทธิภาพที่ทำงานใน Scala ได้

ดังนั้นส่วนใหญ่คุณไม่จำเป็นต้องเพิ่ม Java ลงในโค้ดของคุณ แม้แต่รหัสที่ใช้การแจงนับใน Java ก็มักจะมีวิธีแก้ปัญหาใน Scala ที่เพียงพอหรือดี - ฉันวางข้อยกเว้นในการแจกแจงที่มีวิธีการพิเศษและมีการใช้ค่าคงที่ int

สำหรับสิ่งที่ต้องระวังนี่คือบางสิ่ง

  • หากคุณใช้เพิ่มรูปแบบห้องสมุดของฉันให้แปลงเป็นคลาสเสมอ ตัวอย่างเช่น:

    // 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)
    
  • ระวังวิธีการรวบรวมเพราะส่วนใหญ่เป็นโพลีมอร์ฟิค JVM ไม่ปรับให้เหมาะสม คุณไม่จำเป็นต้องหลีกเลี่ยงพวกเขา แต่ให้ใส่ใจกับมันในส่วนที่สำคัญ โปรดทราบว่าforใน Scala จะดำเนินการผ่านการเรียกใช้เมธอดและคลาสที่ไม่ระบุชื่อ

  • ถ้าใช้ระดับ Java เช่นString, ArrayหรือAnyValชั้นเรียนที่สอดคล้องกับพื้นฐาน Java ชอบวิธีการที่ให้บริการโดย Java เมื่อทางเลือกที่มีอยู่ ตัวอย่างเช่นใช้lengthบนStringและแทนArraysize

  • หลีกเลี่ยงการใช้การแปลงโดยปริยายเนื่องจากคุณสามารถใช้ตัวแปลงโดยไม่ได้ตั้งใจแทนที่จะออกแบบ

  • ขยายคลาสแทนลักษณะ ตัวอย่างเช่นหากคุณกำลังขยายFunction1ให้ขยายAbstractFunction1แทน

  • ใช้-optimiseและความเชี่ยวชาญเพื่อให้ได้ประโยชน์สูงสุดจาก Scala

  • ทำความเข้าใจกับสิ่งที่เกิดขึ้น: javapเป็นเพื่อนของคุณและเพื่อให้เป็นจำนวนมากของธงสกาล่าที่แสดงสิ่งที่เกิดขึ้น

  • สำนวน Scala ถูกออกแบบมาเพื่อปรับปรุงความถูกต้องและทำให้รหัสกระชับและบำรุงรักษามากขึ้น พวกมันไม่ได้ถูกออกแบบมาเพื่อความเร็วดังนั้นหากคุณต้องการใช้nullแทนOptionเส้นทางที่มีความสำคัญให้ทำเช่นนั้น! มีเหตุผลที่สกาล่าเป็นกระบวนทัศน์ที่หลากหลาย

  • จำไว้ว่าการวัดประสิทธิภาพที่แท้จริงคือการใช้รหัส ดูคำถามนี้สำหรับตัวอย่างของสิ่งที่อาจเกิดขึ้นหากคุณไม่สนใจกฎนั้น


1
+1: ข้อมูลที่มีประโยชน์มากมายแม้ในหัวข้อที่ฉันยังต้องเรียนรู้ แต่ก็มีประโยชน์ที่จะอ่านคำแนะนำก่อนที่ฉันจะดู
Giorgio

ทำไมวิธีแรกใช้การสะท้อน มันสร้างคลาสที่ไม่ระบุชื่อดังนั้นทำไมไม่ใช้มันแทนการสะท้อน?
Oleksandr.Bezhan

@ Oleksandr.Bezhan Anonymous class เป็นแนวคิด Java ไม่ใช่ Scala มันสร้างการปรับแต่งประเภท วิธีการเรียนที่ไม่ระบุชื่อที่ไม่ได้แทนที่ชั้นฐานของมันไม่สามารถเข้าถึงได้จากภายนอก สิ่งเดียวกันนั้นไม่เป็นความจริงสำหรับการปรับแต่งประเภทของสกาล่าดังนั้นวิธีเดียวที่จะได้มาซึ่งวิธีการนี้คือผ่านการไตร่ตรอง
Daniel C. Sobral

ฟังดูแย่มาก โดยเฉพาะอย่างยิ่ง: "จงระวังวิธีการเก็บรวบรวม - เพราะส่วนใหญ่เป็นโพลีมอร์ฟิคส่วนใหญ่ JVM ไม่ได้ปรับให้เหมาะสมคุณไม่จำเป็นต้องหลีกเลี่ยงพวกเขา แต่ให้ใส่ใจกับมันในส่วนสำคัญ"
แมตต์

21

ตามเกม Benchmarksสำหรับแกนเดียวระบบ 32 บิต, Scala อยู่ที่ค่ามัธยฐาน 80% เร็วเท่า Java ประสิทธิภาพจะใกล้เคียงกันสำหรับคอมพิวเตอร์ Quad Core x64 แม้แต่การใช้หน่วยความจำและความหนาแน่นของรหัสก็คล้ายกันมากในกรณีส่วนใหญ่ ฉันจะบอกว่าจากการวิเคราะห์เหล่านี้ (ค่อนข้างไม่มีหลักวิทยาศาสตร์) ว่าคุณถูกต้องในการยืนยันว่า Scala เพิ่มค่าใช้จ่ายใน Java ดูเหมือนจะไม่เพิ่มค่าใช้จ่ายจำนวนมากดังนั้นฉันสงสัยว่าการวินิจฉัยรายการสั่งซื้อที่สูงขึ้นซึ่งใช้พื้นที่มากขึ้น / เวลาเป็นสิ่งที่ถูกต้องที่สุด


2
สำหรับคำตอบนี้โปรดใช้การเปรียบเทียบโดยตรงเป็นหน้าช่วยเหลือแนะนำ ( shootout.alioth.debian.org/help.php#comparetwo )
igouy

18
  • ประสิทธิภาพของ Scala นั้นดีมากถ้าคุณเพิ่งเขียนโค้ด Java / C-like ใน Scala คอมไพเลอร์จะใช้วิทยาการ JVM สำหรับInt, Charฯลฯ เมื่อมันสามารถ ในขณะที่ลูปนั้นมีประสิทธิภาพเทียบเท่ากับสกาล่า
  • โปรดจำไว้ว่าการแสดงออกแลมบ์ดานั้นได้รับการรวบรวมเป็นอินสแตนซ์ของคลาสย่อยที่ไม่ระบุชื่อของFunctionคลาส ถ้าคุณผ่านแลมบ์ดาให้mapความต้องการระดับที่ไม่ระบุชื่อที่จะ instantiated (และชาวบ้านบางคนอาจจะต้องมีการส่งผ่านไป) แล้วทุกลูปจะมีค่าใช้จ่ายในการเรียกใช้ฟังก์ชันพิเศษ (มีบางส่วนที่ผ่านพารามิเตอร์) จากapplyการโทร
  • หลาย ๆ คลาสที่ชอบscala.util.Randomนั้นจะล้อมรอบคลาส JRE ที่เทียบเท่ากัน การเรียกฟังก์ชั่นพิเศษนั้นสิ้นเปลืองเพียงเล็กน้อย
  • ระวังรหัสในประสิทธิภาพที่สำคัญ java.lang.Math.signum(x)ตรงกว่าx.signum()มากซึ่งแปลงไปRichIntและกลับ
  • ประโยชน์หลักของ Scala ที่มีต่อประสิทธิภาพเหนือ Java คือความเชี่ยวชาญ โปรดทราบว่ามีการใช้ความเชี่ยวชาญอย่างไม่ จำกัด ในโค้ดห้องสมุด

5
  • a) จากความรู้ที่ จำกัด ของฉันฉันต้องสังเกตว่ารหัสในวิธีการหลักคงไม่สามารถเพิ่มประสิทธิภาพที่ดีมาก คุณควรย้ายรหัสสำคัญไปยังตำแหน่งอื่น
  • b) จากการสังเกตที่ยาวนานฉันไม่แนะนำให้ทำการทดสอบประสิทธิภาพ (ยกเว้นว่าเป็นสิ่งที่คุณต้องการเพิ่มประสิทธิภาพ แต่ใครควรอ่านค่า 2 ล้านครั้ง) คุณกำลังวัด println ซึ่งไม่น่าสนใจมาก แทนที่ println ด้วย max:
(1 to 20000).toList.map (_ * 2).max

ลดเวลาจาก 800 ms เป็น 20 ในระบบของฉัน

  • c) ความเข้าใจในการรู้ตัวนั้นค่อนข้างช้า (ในขณะที่เราต้องยอมรับว่ามันเริ่มดีขึ้นตลอดเวลา) ใช้ฟังก์ชัน while หรือ tailrecursive แทน ไม่ได้อยู่ในตัวอย่างนี้ซึ่งมันคือลูปด้านนอก ใช้คำสั่ง @ tailrec-annotation เพื่อทดสอบความน่าเบื่อ
  • d) การเปรียบเทียบกับ C / Assembler ล้มเหลว คุณไม่ได้เขียนโค้ดสกาล่าสำหรับสถาปัตยกรรมที่แตกต่างกันเช่น ความแตกต่างที่สำคัญอื่น ๆ กับสถานการณ์ทางประวัติศาสตร์คือ
    • คอมไพเลอร์ JIT, การเพิ่มประสิทธิภาพได้ทันทีและอาจจะแบบไดนามิกขึ้นอยู่กับข้อมูลอินพุต
    • ความสำคัญของการพลาดแคช
    • ความสำคัญที่เพิ่มขึ้นของการเรียกขนาน วันนี้สกาล่ามีวิธีแก้ปัญหาในการทำงานโดยไม่มีค่าใช้จ่ายขนานกัน นั่นเป็นไปไม่ได้ใน Java ยกเว้นคุณจะทำงานมากขึ้น

2
ฉันลบ println ออกจากลูปแล้วและจริงๆแล้วรหัส Scala นั้นเร็วกว่าโค้ด Java
Giorgio

การเปรียบเทียบกับ C และแอสเซมเบลอร์มีความหมายดังต่อไปนี้: ภาษาระดับสูงขึ้นมี abstractions ที่ทรงพลังกว่า แต่คุณอาจจำเป็นต้องใช้ภาษาระดับล่างเพื่อประสิทธิภาพ สิ่งนี้ขนานกันหรือไม่เมื่อพิจารณาว่าสกาล่าเป็นภาษาระดับสูงกว่าและภาษาจาวาเป็นภาษาระดับล่าง อาจจะไม่ใช่เพราะ Scala ดูเหมือนจะให้ประสิทธิภาพที่คล้ายกับ Java
Giorgio

ฉันไม่คิดว่ามันจะมีความสำคัญอะไรสำหรับ Clojure หรือ Scala แต่เมื่อฉันเคยเล่นกับ jRuby และ Jython ฉันอาจจะเขียนโค้ดที่มีประสิทธิภาพมากขึ้นใน Java กับสองคนนั้นฉันเห็นความแตกต่างที่สำคัญ แต่เมื่อหลายปีก่อนตอนนี้ ... น่าจะดีกว่า
Rig
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.