0

어제 스터디 그룹과 공유 한 코드는 다음과 같습니다. https://gist.github.com/natemurthy/019e49e6f5f0d1be8719.스칼라 컬렉션에 산술 연산을 매핑하고 결과를 합산합니다.

$ scala -J"-Xmx4G" map 

을 4 개 별도의 테스트를 위해 다음과 같은 결과를 얻을 : 컴파일 후, 나는 다음과 같은 힙 PARAMS와 map.scala을 실행

// (1L to 20000000L).map(_*2) 
(Map) multiplying 20 million elements by 2 
(Reduce) sum: 400000020000000 
Total MapReduce time: 7.562381 

// (1L to 20000000L).toArray.map(_*2) 
(Map) multiplying 20 million elements by 2 
(Reduce) sum: 400000020000000 
Total MapReduce time: 1.233997 

// (1L to 20000000L).toVector.map(_*2) 
(Map) multiplying 20 million elements by 2 
(Reduce) sum: 400000020000000 
Total MapReduce time: 15.041896 

// (1L to 20000000L).par.map(_*2) 
(Map) multiplying 20 million elements by 2 
(Reduce) sum: 400000020000000 
Total MapReduce time: 18.586220 

나는이 결과에서 차이가 이유를 알아 내려고 노력하고있어를 다른 콜렉션 유형, 더 중요한 것은, 직관적으로 더 빨리 평가되어야하는 콜렉션의 경우 성능이 왜 나쁜 것처럼 보이는 이유입니다. 이러한 결과에 대한 귀하의 통찰력을 듣고 싶습니다. 나는 또한 Breeze과 Saddle (동일한 테스트에서 더 나은 성능을 보임)에서 이러한 작업을 수행하는 방법을 실험했지만 내장 된 Scala Collections API를 얼마나 멀리 밀어 낼 수 있는지보고 싶습니다.

이 테스트는 Asus Zenbook UX31A, Intel Core i7 3517U 1.9GHz 듀얼 코어/하이퍼 스레딩, 4GB RAM, Ubuntu 12.04 Desktop에서 실행되었습니다. JDK와 스칼라 2.11.1을 사용하여 1.7

+0

좋은 답변이 주어졌습니다. 예, 문제는 실제 작업 외에 다양한 유형으로 * 변환 *을 벤치마킹한다는 것입니다. 실제 성능을 알아 내려면 작업을 변환과 분리하십시오. – samthebest

답변

5

이 여기에 무슨 일이 많이 분명하지만 여기에 몇 가지 있습니다 :

첫째, to 방법은 점에서 매우 효율적인 데이터 구조를하는 Range 작성 실제로 2 천만 개의 요소를 가진 콜렉션을 생성하지는 않습니다. 반복 할 때 다음 요소를 얻는 방법을 알고 있습니다. Rangemap을 호출하면 출력은 Vector이므로 Range (저가)를 반복하고 각 숫자에 2를 곱한 다음 여전히 Vector (비용이 많이 드는 것, 7.5 초 정도 예상)을 만들어야합니다.

두 번째로 .toVectorRange에 호출하면 실제로는 Vector을 만들고이 2 천만 개의 값을 모두 생성해야합니다. 이것은 (다시, 7.5 초) 시간이 걸립니다. map을 호출하면 벡터 (값싼)를 반복하고, 각 숫자에 2를 곱한 다음 (값싼) 결과를 Vector으로 작성해야합니다 (값 비싼). 따라서 동일한 작업을 수행했지만 이번에는 두 개의 벡터를 새로 만들었습니다. (7.5 * 2 = 15 초)

셋째, 배열은 매우 단순한 데이터 구조이며 오버 헤드가 매우 낮습니다. 생성, 색인 및 삽입이 빠르기 때문에 큰 배열을 구성한 다음 요소를 새로운 배열에 삽입하기 위해 매핑하는 것이 놀랄만큼 빠릅니다.

마지막으로 Range을 호출하면 ParRange가 생성됩니다. map의 결과는 ParVector이므로 해당 개체를 만들고 2 천만 개 요소를 입력하는 데 비용이 듭니다. .map을 호출하면 계산을 수행 할 스레드가 생성됩니다. 그러나 매핑 작업은 매우 빠르므로 병렬 작업을 수행하는 것이 실제로 이점이 없습니다. 곱셈을 실제로 계산하는 것보다 병렬 처리를 다루는 오버 헤드로 더 많은 시간을 보내고 있습니다.

이렇게 생각하십시오. 당신이 실생활에서이 작업을하고 싶다면, 당신은 당신의 스레드가 될 많은 친구들을 모을 것입니다. 그런 다음 2 천만 개의 숫자를 나눠서 각 친구에게 곱셈을 수행하기 위해 몇 가지를 주어야합니다. 그런 다음 친구는 각 숫자에 2를 곱하고 두 배로 된 숫자를 돌려주고 다음 숫자 세트를 나눠 줄 때까지 기다립니다. 그런 다음 곱한 각 숫자를 새 테이블에 입력해야합니다.그러나 숫자를 2로 곱하는 작업은 너무 빠르므로 친구를 모으고 메시지를 앞뒤로 전달하는 데 걸리는 시간보다 짧은 시간에 직접 할 수 있습니다. 또한 두 개의 코어 만 있으면 병렬화의 여지가 없습니다. 따라서 두 개의 스레드가 동시에 작동하므로 오버 헤드와 작업 비율이 좋지 않습니다.

+0

배열이 벡터 캐시보다 더 멋지게 재생되는지 궁금합니다. 거의 확실하게 – 9000

+0

@ 9000입니다. – dhg

+0

수집보기가 갈 길이라는 것을 알게되었습니다 : 'val dbls = (1L to 20000000L) .view.map (_ * 2); dbls.sum' map 작업은 sum()이 호출 될 때까지 느리게 평가됩니다. 결과는 다음과 같습니다. '(지도) 2 천만 개 요소에 2를 곱합니다. (줄이기) 합계 : 400000020000000; 총지도 축소 시간 : 0.783316' @dhg – nmurthy