자바에 비해 스칼라의 성능


41

우선, 이것이 어느 것이 더 좋은지를 결정하기위한 language-X-versus-language-Y 질문이 아님을 분명히하고 싶습니다.

오랫동안 Java를 사용하고 있으며 계속 사용하려고합니다. 이와 병행하여, 나는 현재 스칼라에 큰 관심을 가지고 배우고 있습니다.

내 질문은 : Scala로 작성된 소프트웨어는 실행 속도 및 메모리 소비 측면에서 Java로 작성된 소프트웨어와 어떻게 비교됩니까? 물론 이것은 일반적으로 대답하기 어려운 질문이지만 패턴 일치, 고차 함수 등과 같은 높은 수준의 구성에는 약간의 오버 헤드가 발생할 것으로 기대합니다.

그러나 현재 Scala에서의 경험은 50 줄의 코드로 작은 예제로 제한되어 있으며 지금까지 벤치 마크를 실행하지 않았습니다. 따라서 실제 데이터가 없습니다.

스칼라 자바에 약간의 오버 헤드 가있는 것으로 밝혀지면 스칼라 에서 더 복잡한 부분과 자바에서 성능이 중요한 부분을 코딩하는 스칼라 / 자바 프로젝트를 혼합하는 것이 합리적입니까? 이것이 일반적인 관행입니까?

편집 1

작은 벤치 마크를 실행했습니다 : 정수 목록을 작성하고 각 정수에 2를 곱한 다음 새 목록에 넣고 결과 목록을 인쇄하십시오. Java 구현 (Java 6)과 Scala 구현 (Scala 2.9)을 작성했습니다. 우분투 10.04에서 Eclipse Indigo에서 모두 실행했습니다.

결과는 Java의 경우 480ms, 스칼라의 경우 493ms (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이 실행 시간의 대부분을 차지한다고 지적했습니다. 이를 제거하고 목록 크기를 20000 대신 100000으로 설정했습니다. 결과 평균은 Java의 경우 88ms, 스칼라의 경우 49ms입니다.


5
스칼라는 JVM 바이트 코드로 컴파일하기 때문에 성능은 이론적으로 동일한 JVM에서 실행되는 Java와 다른 모든 것들이 동일하다고 생각합니다. 내가 생각하는 차이점은 Scala 컴파일러가 바이트 코드를 작성하는 방법과 그렇게 효율적인지 여부에 있습니다.
maple_shaft

2
@maple_shaft : 아니면 스칼라 컴파일 시간에 오버 헤드가 있습니까?
FrustratedWithFormsDesigner

1
@Giorgio Scala 객체와 Java 객체 사이에는 런타임에 차이가 없으며 바이트 코드별로 정의되고 작동하는 모든 JVM 객체입니다. 예를 들어 언어로서 스칼라는 클로저라는 개념을 가지고 있지만, 컴파일 될 때 바이트 코드를 사용하여 여러 클래스로 컴파일됩니다. 이론적으로 정확히 동일한 바이트 코드로 컴파일 할 수있는 Java 코드를 실제로 작성할 수 있으며 런타임 동작은 정확히 동일합니다.
maple_shaft

2
@maple_shaft : 정확히 내가 목표로하는 것입니다 : 위의 스칼라 코드는 해당 Java 코드보다 훨씬 간결하고 읽기 쉽습니다. 성능상의 이유로 스칼라 프로젝트의 일부를 Java로 작성하는 것이 타당하고 그 부분이 무엇인지 궁금합니다.
Giorgio

2
런타임은 주로 println 호출에 의해 점유됩니다. 보다 계산 집약적 인 테스트가 필요합니다.
케빈 클라인

답변:


39

스칼라에서는 할 수없는 Java로 간결하고 효율적으로 할 수있는 한 가지는 열거입니다. 스칼라 라이브러리에서 느린 구조의 경우에도 스칼라에서 효율적인 버전을 사용할 수 있습니다.

따라서 대부분의 경우 코드에 Java를 추가 할 필요가 없습니다. Java에서 열거 형을 사용하는 코드의 경우에도 스칼라에는 적절하거나 좋은 솔루션이 있습니다. 추가 메서드가 있고 int 상수 값이 사용되는 열거 형에는 예외를 둡니다.

주의해야 할 사항은 다음과 같습니다.

  • rich my library 패턴을 사용하는 경우 항상 클래스로 변환하십시오. 예를 들면 다음과 같습니다.

    // 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스칼라에서 메서드 호출 및 익명 클래스를 통해 구현됩니다.

  • 같은 자바 클래스를 사용하는 경우 String, Array또는 AnyVal대안이 존재하는 경우 클래스의 Java 프리미티브에 그 대응을, 자바에서 제공하는 방법을 선호합니다. 예를 들어 대신 및 lengthon StringArray사용하십시오 size.

  • 의도적 인 변환 대신 실수로 변환을 사용하는 것을 알 수 있으므로 암시 적 변환을주의해서 사용하지 마십시오.

  • 특성 대신 클래스를 확장하십시오. 예를 들어을 Function1확장하는 경우 AbstractFunction1대신 확장하십시오 .

  • -optimise스칼라를 최대한 활용하기 위해 사용 하고 전문화 하십시오 .

  • 무슨 일이 일어나고 있는지 이해 javap하십시오 : 당신의 친구이며, 무슨 일이 일어나고 있는지 보여주는 스칼라 깃발도 있습니다.

  • 스칼라 관용구는 정확성을 개선하고 코드를 더 간결하고 유지 보수 할 수 있도록 설계되었습니다. 속도를 위해 설계되지 않았으므로 중요한 경로 null대신 사용해야 하는 경우 Option그렇게하십시오! 스칼라가 다중 패러다임 인 이유가 있습니다.

  • 실제 성능 측정은 코드를 실행하는 것입니다. 해당 규칙을 무시할 경우 발생할 수있는 예를 보려면 이 질문 을 참조하십시오 .


1
+1 : 여전히 배워야 할 주제에 대한 유용한 정보가 많이 있지만, 정보를보기 전에 힌트를 읽어 보는 것이 좋습니다.
Giorgio

첫 번째 접근 방식이 왜 반사를 사용합니까? 어쨌든 익명 클래스를 생성하므로 리플렉션 대신 사용하지 않는 이유는 무엇입니까?
Oleksandr.Bezhan

@ Oleksandr.Bezhan 익명 클래스는 스칼라 개념이 아닌 Java 개념입니다. 유형 세분화를 생성합니다. 기본 클래스를 재정의하지 않는 익명 클래스 메서드는 외부에서 액세스 할 수 없습니다. 스칼라의 유형 개선에 대해서도 마찬가지이므로이 방법을 사용하는 유일한 방법은 리플렉션입니다.
Daniel C. Sobral

꽤 끔찍하게 들립니다. 특히 : "수집 방법에주의하십시오-대부분 다형성이기 때문에 JVM은이를 최적화하지 않습니다. 피할 필요는 없지만 중요한 부분에주의를 기울이십시오."
matt

21

단일 코어, 32 비트 시스템에 대한 벤치 마크 게임 에 따르면 스칼라는 Java보다 80 % 빠릅니다. Quad Core x64 컴퓨터의 성능은 거의 동일합니다. 메모리 사용 및 코드 밀도 조차도 대부분의 경우 매우 유사합니다. 스칼라가 자바에 약간의 오버 헤드를 추가한다고 주장하는 것이 정확하다는 (비과학적인) 분석에 근거하여 말할 것입니다. 그것은 많은 오버 헤드를 추가하지 않는 것처럼 보이므로 더 많은 공간 / 시간을 차지하는 높은 주문 항목의 진단이 가장 올바른 것으로 의심됩니다.


2
이 답변을 보려면 도움말 페이지에서 제안하는대로 직접 비교를 사용하십시오 ( shootout.alioth.debian.org/help.php#comparetwo )
igouy

18
  • 스칼라에서 Java / C와 같은 코드를 작성하면 스칼라 성능이 매우 좋습니다. 컴파일러에 대한 JVM의 기본 요소를 사용 Int, Char때 할 수있는 등. 스칼라에서도 루프가 효율적입니다.
  • 람다 식은 클래스의 익명 하위 클래스 인스턴스로 컴파일됩니다 Function. 에 람다를 전달 map하면 익명 클래스를 인스턴스화해야하고 (일부 로컬을 전달해야 할 수도 있음) 모든 반복에는 호출에서 추가 함수 호출 오버 헤드 (일부 매개 변수 전달) apply가 있습니다.
  • 같은 많은 클래스 scala.util.Random는 동등한 JRE 클래스를 감싸는 래퍼입니다. 추가 함수 호출은 약간 낭비입니다.
  • 성능이 중요한 코드에 내재 된 사항이 있는지 확인하십시오. 로 변환 하고 되 돌리는 java.lang.Math.signum(x)것보다 훨씬 직접적 입니다.x.signum()RichInt
  • 스칼라의 Java에 비해 주요 성능 이점은 전문화입니다. 전문화는 라이브러리 코드에서 드물게 사용됩니다.

5
  • a) 제한된 지식을 바탕으로 정적 기본 메소드의 코드를 매우 잘 최적화 할 수 없다는 점에 주목해야합니다. 중요한 코드를 다른 위치로 이동해야합니다.
  • b) 오랫동안 관찰 한 결과 성능 테스트에서 많은 결과를 얻지 않는 것이 좋습니다 (정확히 최적화하려는 것이지만 누가 2 백만 값을 읽어야합니까?). 당신은 println을 측정하고 있습니다. println을 max로 교체 :
(1 to 20000).toList.map (_ * 2).max

내 시스템에서 시간을 800ms에서 20으로 줄입니다.

  • c) 이해력은 약간 느리다는 것이 알려져있다 (우리는 항상 나아지고 있음을 인정해야한다). while 또는 tailrecursive 함수를 대신 사용하십시오. 이 예에서는 외부 루프가 아닙니다. @ tailrec-annotation을 사용하여 tairecursiveness를 테스트하십시오.
  • d) C / 어셈블러와 비교할 수 없습니다. 예를 들어 다른 아키텍처에 대한 스칼라 코드를 다시 작성하지 않습니다. 역사적인 상황과 다른 중요한 차이점은
    • 입력 데이터에 따라 즉시 최적화되고 동적으로 최적화되는 JIT 컴파일러
    • 캐시 미스의 중요성
    • 병렬 호출의 중요성이 증가하고 있습니다. 스칼라는 오늘날 많은 오버 헤드없이 동시에 작업 할 수있는 솔루션을 제공합니다. 더 많은 작업을 수행한다는 점을 제외하면 Java에서는 불가능합니다.

2
루프에서 println을 제거했으며 실제로 Scala 코드가 Java 코드보다 빠릅니다.
Giorgio

C 및 어셈블러와의 비교는 다음과 같은 의미로 사용되었습니다. 고급 언어에는 더 강력한 추상화가 있지만 성능을 위해 하위 언어를 사용해야 할 수도 있습니다. 이 병행은 Scala를 고급 언어로, Java를 하위 언어로 간주합니까? 스칼라가 Java와 비슷한 성능을 제공하는 것으로 보이므로 아마도 아닐 수도 있습니다.
Giorgio

Clojure 나 Scala에는별로 중요하지 않다고 생각하지만 jRuby 및 Jython으로 놀았을 때 Java에서 더 성능이 중요한 코드를 작성했을 것입니다. 그 두 가지로 나는 상당한 차이를 보았지만 지금은 몇 년 전 ... 더 좋을 수 있습니다.
Rig
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.