문제는 두 부분으로 나뉩니다. 첫 번째는 개념입니다. 다음은 Scala에서 동일한 질문을보다 구체적으로 살펴 봅니다.
- 프로그래밍 언어에서 변경 불가능한 데이터 구조 만 사용하면 실제로 특정 알고리즘 / 로직을 구현하는 데 본질적으로 계산 비용이 더 많이 듭니까? 이것은 불변성이 순전히 기능적인 언어의 핵심 신조라는 사실을 이끌어냅니다. 이에 영향을 미치는 다른 요인이 있습니까?
- 좀 더 구체적인 예를 들어 보겠습니다. Quicksort 는 일반적으로 메모리 내 데이터 구조에서 변경 가능한 작업을 사용하여 학습되고 구현됩니다. 변경 가능한 버전과 비교 가능한 계산 및 저장 오버 헤드를 사용하여 PURE 기능적 방식으로 그러한 것을 어떻게 구현합니까? 특히 Scala에서. 아래에 몇 가지 조잡한 벤치 마크를 포함했습니다.
자세한 내용은:
저는 명령형 프로그래밍 배경 (C ++, Java)에서 왔습니다. 저는 함수형 프로그래밍, 특히 Scala를 탐구 해 왔습니다.
순수 함수형 프로그래밍의 몇 가지 기본 원칙 :
- 기능은 일류 시민입니다.
- 함수에는 부작용이 없으므로 객체 / 데이터 구조는 변경할 수 없습니다 .
현대의 JVM 은 객체 생성에 매우 효율적이고 가비지 수집 은 수명이 짧은 객체의 경우 매우 저렴하지만 객체 생성을 최소화하는 것이 더 낫습니다. 적어도 동시성과 잠금이 문제가되지 않는 단일 스레드 애플리케이션에서. Scala는 하이브리드 패러다임이므로 필요한 경우 변경 가능한 객체로 명령형 코드를 작성하도록 선택할 수 있습니다. 그러나 객체를 재사용하고 할당을 최소화하기 위해 수년을 보냈던 사람입니다. 나는 그것을 허용하지 않을 생각의 학교에 대해 잘 이해하고 싶습니다.
특정한 경우에 저는 이 튜토리얼 6 의이 코드 스 니펫에 약간 놀랐습니다 . Java 버전의 Quicksort와 깔끔하게 보이는 Scala 구현이 있습니다.
여기에 구현을 벤치마킹하려는 시도가 있습니다. 상세한 프로파일 링을하지 않았습니다. 그러나 제 생각에는 할당 된 개체 수가 선형 (재귀 호출 당 하나씩)이기 때문에 Scala 버전이 더 느립니다. 테일 콜 최적화가 작동 할 가능성이 있습니까? 내가 맞다면 Scala는 자체 재귀 호출에 대한 꼬리 호출 최적화를 지원합니다. 그래서 그것은 단지 그것을 돕고 있어야합니다. Scala 2.8을 사용하고 있습니다.
자바 버전
public class QuickSortJ {
public static void sort(int[] xs) {
sort(xs, 0, xs.length -1 );
}
static void sort(int[] xs, int l, int r) {
if (r >= l) return;
int pivot = xs[l];
int a = l; int b = r;
while (a <= b){
while (xs[a] <= pivot) a++;
while (xs[b] > pivot) b--;
if (a < b) swap(xs, a, b);
}
sort(xs, l, b);
sort(xs, a, r);
}
static void swap(int[] arr, int i, int j) {
int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
}
}
Scala 버전
object QuickSortS {
def sort(xs: Array[Int]): Array[Int] =
if (xs.length <= 1) xs
else {
val pivot = xs(xs.length / 2)
Array.concat(
sort(xs filter (pivot >)),
xs filter (pivot ==),
sort(xs filter (pivot <)))
}
}
구현을 비교하기위한 스칼라 코드
import java.util.Date
import scala.testing.Benchmark
class BenchSort(sortfn: (Array[Int]) => Unit, name:String) extends Benchmark {
val ints = new Array[Int](100000);
override def prefix = name
override def setUp = {
val ran = new java.util.Random(5);
for (i <- 0 to ints.length - 1)
ints(i) = ran.nextInt();
}
override def run = sortfn(ints)
}
val benchImmut = new BenchSort( QuickSortS.sort , "Immutable/Functional/Scala" )
val benchMut = new BenchSort( QuickSortJ.sort , "Mutable/Imperative/Java " )
benchImmut.main( Array("5"))
benchMut.main( Array("5"))
결과
연속 5 회 실행에 대한 시간 (밀리 초)
Immutable/Functional/Scala 467 178 184 187 183
Mutable/Imperative/Java 51 14 12 12 12
O(n)
목록 연결을 사용하기 때문에 여전히 개가 느립니다 . 하지만 의사 코드 버전보다 짧습니다.)