2014-01-19 3 views
4

나는 머리카락을 되찾기 위해 노력하고 있으며, 무엇이 빠르고 느리게하는지, 그리고 나는 이것에 대해 조금 혼란스러워합니다.무한한 목록을 사용할 때 실행 속도가 느려짐

필자는 특정 값까지 소수 목록을 생성하는 함수의 두 가지 구현을 가지고 있습니다. 제 직선 하스켈 위키 오프이다

primesTo :: (Ord a, Num a, Enum a) => a -> [a] 
primesTo m = eratos [2..m] where 
    eratos []  = [] 
    eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m]) 

두번째는 동일하지만 내부적 무한리스트를 사용하여 다음 두 경우

primes2 :: (Ord a, Num a, Enum a) => a -> [a] 
primes2 m = takeWhile (<= m) (eratos [2..]) where 
    eratos []  = [] 
    eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..]) 

마이너스 함수이다 :

minus :: (Ord a) => [a] -> [a] -> [a] 
minus (x:xs) (y:ys) = case (compare x y) of 
      LT -> x : minus xs (y:ys) 
      EQ ->  minus xs  ys 
      GT ->  minus (x:xs) ys 
minus xs _ = xs 

후자의 구현은 이전보다 훨씬 느리고 (~ 100x), 나는 이유를 알지 못합니다. 나는 haskell의 게으른 평가가 그들을 후드 아래에서 꽤 동등하게 만들 것이라고 생각했을 것이다.

이것은 분명히 질문의 목적을 위해 축소 된 테스트 케이스입니다. 실생활에서 최적화는 아무런 문제가되지 않지만 (필자는 왜 그것이 필요한지는 모르겠지만) 나에게 무한한 것을 생성하는 함수 소수 목록은 유한 목록보다 일반적으로 더 유용하지만 작업 속도가 느립니다.

+0

참고 : 목록 중 하나가 무한 인 경우를 테스트 할 경우이 조금 더 나은를 탐색 할 수 있습니다. 생성 된 Core와 C를 살펴보면 차이점을 볼 수 있습니다. 그러나 간단히 말해서 알고리즘 적으로 생각하면 차이는 무한리스트 버전이 소수보다 m + 1 작업이 적게되는 "겹침"작업을 수행한다는 것입니다. m에 대해 두 번, m + 1에 대해. 그러나 당신이 그 일을 필요로하지 않는다면, 그것을하지 않는 것이 좋습니다. 따라서이 알고리즘을 사용하면 필요한 최대 값을 미리 알고 있다면 작업을 저장할 수 있습니다. – misterbee

+0

비슷한 문제가있는 다른 문제 + 알고리즘을 찾기위한 연습으로 밝혀 줄 것입니다. – misterbee

답변

6

minus 단계를 나열 페어를 통해

(xs `minus` [p*p, p*p+p..m]) -- primesTo 
(xs `minus` [p*p, p*p+p..]) -- primes2 

기능 사이에 큰 차이가있어 하나 개의 목록은 끝에 도달하면 종료하는 것이 나에게처럼 보인다. 위의 첫 번째 minus 식에서 후자의 목록을 모두 사용한 경우에는 (m-p*p)/p 단계가 발생합니다. 두 번째 단계에서는 항상 length xs의 순서로 단계를 수행합니다.

무한한 목록으로 인해 의미가있는 최적화가 하나 이상 사용 중지되었습니다.

+1

나는 후자의 목록이 마이너스의 실행을 끝내고, p가 m쪽으로 갈수록 마이너스 계산이 더 짧아진다는 것을 이해한다.내가 이해하려고 노력하는 것은 게으른 평가가 무한한 목록을 적용하고 적용하지 않는 곳과시기입니다.이 경우 마이너스는 takeWhile (그리고 위의 모든 것을 만족시키는 데 필요한만큼만 평가되었을 것입니다. 그것은 명시 적으로 한정된 경우와 크게 다르지 않았을 것이다). – WillW

+0

'minus'는 목록을 쌍으로 밟지 않습니다. 머리가 같을 때에 만 양쪽 모두에서 '빼기'가 소모됩니다. 따라서 두 단계 모두에서 단계 길이가'길이 xs '에 가깝습니다. 그러나 첫 번째 경우에는 썽크를 피하고'p '의 마지막 배수와 다음에 오는 것 사이의'xs'를 검사하여 소수 목록을 다 써 버릴 수 있습니다. – ollanta

3

두 번째 경우에는 하나의 추가 소수를 생성해야한다는 차이점이 있습니다. takeWhile이 정지 할 시간을 알기 전에 m보다 큰 첫 번째 소수를 생성해야합니다.

또한 필터 목록과 배수 목록에서 모두 [..m] 경계는 계산 횟수를 줄이는 데 도움이됩니다. 이 목록들 중 하나가 비게 될 때마다 minus은 secons 절을 통해 즉시 반환하지만 무한의 경우에는 빼기가 첫 번째 경우에 멈추게됩니다. 당신이 primes2 밖으로 takeWhile을 강화하고, 시도보다 일반적인 소수 기능을 얻을 수 있습니다

--this is also slow 
primes3 :: (Ord a, Num a, Enum a) => a -> [a] 
primes3 m = takeWhile (<= m) (eratos [2..m]) where 
    eratos []  = [] 
    eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..]) 

--this fast 
primes4 :: (Ord a, Num a, Enum a) => a -> [a] 
primes4 m = takeWhile (<= m) (eratos [2..]) where 
    eratos []  = [] 
    eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m]) 
관련 문제