몇 가지를 살펴 봅시다.
벤치마킹
첫 번째 : 우리가 실제로 우리가 가서 개선을하고 있는지 만들자!이를 위해 몇 가지 벤치 마크가 필요합니다. criterion 패키지가 이에 적합합니다. 또한 최적화 (GHC 호출시 모두 -O2
)로 컴파일해야합니다.
이
import Criterion.Main
-- your code goes here
main = defaultMain
[ bench "findNums 100 2" (nf (uncurry findNums) (100, 2))
, bench "findNums 800 2" (nf (uncurry findNums) (800, 2))
]
하나는 nf (findNums 100) 2
로 벤치 마크를 구현할 수 있지만, 우리가 100
에 대한 조회 테이블을 미리 계산에 의해 "속임수"할 수 있도록 내가 이렇게 밀어,이 방법을 선택합니다 : 여기에 벤치 마크가 될 수있는 방법을 간단한 설정이야 모든 작업은 벤치 마크가 실제로 실행되는 부분이 아닌 벤치 마크 설정으로 진행됩니다.
benchmarking 100 2
time 762.7 ns (757.4 ns .. 768.5 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 762.5 ns (760.4 ns .. 765.3 ns)
std dev 7.706 ns (6.378 ns .. 10.59 ns)
benchmarking 800 2
time 29.17 s (28.28 s .. 29.87 s)
1.000 R² (1.000 R² .. 1.000 R²)
mean 29.26 s (29.08 s .. 29.35 s)
std dev 159.2 ms (0.0 s .. 165.2 ms)
variance introduced by outliers: 19% (moderately inflated)
를 사용하여 라이브러리
이제, 저 매달려 열매는 가지의 기존의 구현을 사용하고 그 저자가 우리보다 더 나은 뭔가를했다 희망하는
다음은 원래의 구현에 대한 결과입니다. 이를 위해
root
대신에
pow
대신
(^)
이라는 표준 함수를 사용하고 패키지의
integerRoot
을 사용합니다. 또한, 우리는 게으른
foldr
을 엄격한
foldl
으로 교체 할 것입니다. 내 자신의 온건함을 위해 나는 또한 매우 긴 줄을 짧은 줄로 다시 포맷했다. 전체 결과는 이제 다음과 같습니다
import Criterion.Main
import Data.List
import Math.NumberTheory.Powers
sum' :: Num a => [a] -> a
sum' = foldl' (+) 0
findNums :: Int -> Int -> Int
findNums a b = length
[ xs
| xs <- drop 1 . subsequences $ [x^b | x <- [1..c]]
, sum' xs == a
] where c = integerRoot b a
main = defaultMain
[ bench "100 2" (nf (uncurry findNums) (100, 2))
, bench "800 2" (nf (uncurry findNums) (800, 2))
]
벤치 마크 결과는 지금과 같이 :
benchmarking 100 2
time 722.8 ns (721.3 ns .. 724.3 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 722.6 ns (721.4 ns .. 724.1 ns)
std dev 4.440 ns (3.738 ns .. 5.674 ns)
benchmarking 800 2
time 17.16 s (16.93 s .. 17.64 s)
1.000 R² (1.000 R² .. 1.000 R²)
mean 17.05 s (16.99 s .. 17.15 s)
std dev 88.10 ms (0.0 s .. 94.58 ms)
작은 배에서 빠른 속도로 아주 작은 노력으로. 좋은!
더 나은 알고리즘
subsequences
에 중요한 문제는 우리가 sum' [x,y,z] > a
, 우리는 여전히 [x,y,z]
로 시작하는 모든 이상 시퀀스 볼 것을 계산하는 경우에도, 그. subsequences
'반환 유형의 구조를 감안할 때 우리가 할 수있는 일은별로 없습니다. 그래서 우리에게 좀 더 많은 구조를 제공하는 구현을 설계합시다. 루트에서 임의의 내부 노드까지의 경로가 서브 시퀀스를 제공하는 트리를 만듭니다.
이 (그냥 재미를 위해, 이것은 매우 작은 공간 사용에 기하 급수적으로 큰 의미 나무를 생산 - 원래 목록과 거의 같은 정도의 공간 -. 공격적인 서브 트리 공유에)
import Data.Tree
subsequences :: [a] -> Forest a
subsequences [] = []
subsequences (x:xs) = Node x rest : rest where
rest = subsequences xs
이 표현에 대한 멋진 무엇이며, 우리의 경우 검색을 중단하면, 우리는 흥미로운 결과를 엄청나게 잘라냅니다. 이것은 목록에 대한
takeWhile
뭔가를 구현하여 실현 될 수있다 :
takeWhileTree :: Monoid m => (m -> Bool) -> Forest m -> Forest m
takeWhileTree predicate = goForest mempty where
goForest m forest = forest >>= goTree m
goTree m (Node m' children) =
[Node m (goForest (m <> m') children) | predicate m']
것은 그것은 시도 줘 보자. 전체 코드는 이제 :
는
import Criterion.Main
import Data.Foldable
import Data.Monoid
import Data.Tree
import Math.NumberTheory.Powers
subsequencesTree :: [a] -> Forest a
subsequencesTree [] = []
subsequencesTree (x:xs) = Node x rest : rest where
rest = subsequencesTree xs
takeWhileTree :: Monoid m => (m -> Bool) -> Forest m -> Forest m
takeWhileTree predicate = goForest mempty where
goForest m forest = forest >>= goTree m
goTree m (Node m' children) = let m'' = m <> m' in
[Node m' (goForest m'' children) | predicate m'']
leaves :: Forest a -> [[a]]
leaves [] = [[]]
leaves forest = do
Node x children <- forest
xs <- leaves children
return (x:xs)
findNums :: Int -> Int -> Int
findNums a b = length
[ xs
| xs <- leaves
. takeWhileTree (<= Sum a)
. subsequencesTree
$ [Sum (x^b) | x <- [1..c]]
, fold xs == Sum a
] where c = integerRoot b a
main = defaultMain
[ bench "100 2" (nf (uncurry findNums) (100, 2))
, bench "800 2" (nf (uncurry findNums) (800, 2))
]
이 많은 작업처럼 보이지만 타이밍에서, 정말 떨어져 지불 : findNums 800 2
에 약 1000의 속도 향상 요인이다
benchmarking 100 2
time 16.67 μs (16.53 μs .. 16.77 μs)
0.999 R² (0.999 R² .. 1.000 R²)
mean 16.60 μs (16.52 μs .. 16.72 μs)
std dev 325.4 ns (270.5 ns .. 444.1 ns)
variance introduced by outliers: 17% (moderately inflated)
benchmarking 800 2
time 22.59 ms (22.26 ms .. 22.89 ms)
0.999 R² (0.999 R² .. 1.000 R²)
mean 22.44 ms (22.34 ms .. 22.57 ms)
std dev 260.3 μs (191.6 μs .. 332.2 μs)
합니다. 나무의 별도의 가지가 병렬로 탐구 될 수 있도록
병렬화
나는, takeWhileTree
대신 (>>=)
에 concat
및 parMap
를 사용하여이 작업을 병렬에서 이동을했다.모든 경우에 병렬 처리의 오버 헤드가 여러 스레드를 갖는 이점을 훨씬 능가했습니다. 처음에 그 벤치 마크를 제자리에 두는 것이 좋습니다!
오 마이 주. 어디로 가는지 이해하기 시작 해야할지 모르겠습니다. 벤치마킹의 목적을 이해합니다. 나는 당신이 subsequencesTree를 가진 "숲"을 창조하고 있다는 것을 이해합니다. 우선 그 함수로 시작하겠습니다. 이 부분에서'subsequencesTree (x : xs) = Node x rest : rest where rest = subsequencesTree xs'에서 노드를 만드는 방법을 볼 수 있지만 어떻게 포리스트에 배치 할 지 확신하지 못합니다. 이 행동에 대한 깊이있는 설명이 있습니까? – terminix00
@ user2977382 글쎄, '포레스트 타입 a = [트리 a]'맞습니까? 그래서 숲은 단지 나무의 목록 일뿐입니다. 그리고'xs'의 서브 시퀀스는'xs'의 첫 번째 요소를 포함하거나 그렇지 않습니다. 그래서'Node x rest'는 모든 경로가'x' 요소로 시작하는 트리이고,'rest'는 각 트리가'x'로 시작하지 않는 경로를 갖는 포리스트입니다. 그리고'Node x rest : rest'는 둘 모두를 포함하는 포리스트입니다 - 그러므로'x'를 포함하는 경로와 그렇지 않은 경로가 있습니다. –
좋아,이 나무는 가능한 모든 순서를 구성합니까? 그리고 당신은 나무의 합계의 술어를 기반으로 takeWhileTree를 사용하여 관련된 나무를 썰어서 a보다 작습니까? – terminix00