하스켈 성능 튜닝 2
목 차
1. 상황 요약
2. 성능 개선
3. 새로운 방법
4. 결론
예시로 들었던 문제
온라인 저지 사이트 ALGOSPOT
문제 ID: WEIRD (https://www.algospot.com/judge/problem/read/WEIRD)
In mathematics, weird numbers are natural numbers that are abundant but not semiperfect. In other words, a
natural number N is a weird number if and only if:
• Sum of its proper divisors (i.e. less than N ) is greater than the number.
• No subset of its divisors sum to N.
For example, the set of proper divisors of 12 is { 1, 2, 3, 4, 6 } . The sum of these numbers exceed 12, however,
12 is not a weird number since 1 + 2 + 3 + 6 = 12.
However, 70 is a weird number since its proper divisors are {1, 2, 5, 7, 10, 14, 35} and no subset sums to 70 .
Write a program to determine if the given numbers are weird or not.
결국 풀었다
1년 걸림
이전에 썼던 알고리즘
• S(N)을 알면 abundant 검사는 단순하다
• semiperfect 검사는 0-1 knapsack 문제
• 약수들은 정수이므로 다이나믹 프로그래밍으로 해결 가능
• 𝑆 𝑁 = 𝑠1, 𝑠2, … , 𝑠 𝑘 (𝑠1 ≤ 𝑠2 ≤ … )
• 𝑇 𝑖, 𝑗 = 𝑆 𝑁 의 𝑠1, 𝑠2, … , 𝑠𝑖 까지 써서 만들 수 있는, j 이하의 최대 합
• 𝑇 𝑖, 𝑗 =
0 𝑖 = 0
𝑇 𝑖 − 1, 𝑗 𝑠𝑖 > 𝑗
max 𝑇 𝑖 − 1, 𝑗 , 𝑇 𝑖 − 1, 𝑗 − 𝑠𝑖 + 𝑠𝑖 𝑜𝑡ℎ𝑒𝑟𝑤𝑖𝑠𝑒
• 𝑇 𝑘, 𝑁 = 𝑁 이면 N은 semiperfect
불리언 문제로 변환
• S(N)을 알면 abundant 검사는 단순하다
• semiperfect 검사는 0-1 knapsack 문제
• 약수들은 정수이므로 다이나믹 프로그래밍으로 해결 가능
• 𝑆 𝑁 = 𝑠1, 𝑠2, … , 𝑠 𝑘 (𝑠1 ≤ 𝑠2 ≤ … )
• 𝑇 𝑖, 𝑗 = 𝑆 𝑁 의 𝑠1, 𝑠2, … , 𝑠𝑖 까지 써서 만들 수 있는 최대 합이 j인가?
• 𝑇 𝑖, 𝑗 =
𝑇𝑟𝑢𝑒 𝑖 = 1, 𝑗 = 1
𝐹𝑎𝑙𝑠𝑒 𝑖 = 1, 𝑗 ≠ 1
𝑇 𝑖 − 1, 𝑗 𝑠𝑖 > 𝑗
𝑇 𝑖 − 1, 𝑗 𝐴𝑁𝐷 𝑇 𝑖 − 1, 𝑗 − 𝑠𝑖 𝑜𝑡ℎ𝑒𝑟𝑤𝑖𝑠𝑒
• 𝑇 𝑘, 𝑁 = 𝑇𝑟𝑢𝑒 이면 N은 semiperfect
이전 0-1 KNAPSACK 코드
Knapsack :: Int -> Uarray Int Int -> Int -> UArray Int Int
knapsack n divs len = a 0 (runSTUArray $ newArray (0,n) (0::Int)) where
a prevRow prevA
| prevRow == len = prevA
| True = currA `seq` a (prevRow+1) currA where
currA = runSTUArray $ newListArray (0,n) [w `seq` (v `seq` v) | w <- [0..n],
let ith = divs ! prevRow, let v = maximum [at w, ith + at (w-ith)]]
at w = if w < 0 then -n else prevA ! w
필요없는 배열이 바로 가비지 컬렉션되지만, 매 호출마다 새 배열을 생성한다
수정된 0-1 KNAPSACK 코드
knapsack :: Int -> [Int] -> Bool
knapsack n divs =
let ary = runSTUArray $ do
ary <- newArray (1,n) False
writeArray ary 1 True
forM_ divs i -> do
forM_ [n,n-1..i+1] $ j -> do
x <- readArray ary j
y <- readArray ary (j-i)
writeArray ary j (x || y)
writeArray ary i True
return ary
in ary ! n
• 약수들의 배열에서 1은 제외
(base case로 직접 처리)
• 단 하나의 배열 ary 사용
• C 코드처럼 보인다
• IO 모나드 안도 아닌데 ary가 변경 가능?
• knapsack은 순수 함수가 맞다
• Rank-2 type 덕에 가능
• 자세한 설명은 다음 기회에…
수정된 0-1 KNAPSACK 코드
knapsack :: Int -> [Int] -> Bool
knapsack n divs =
let ary = runSTUArray $ do
ary <- newArray (1,n) False
writeArray ary 1 True
forM_ divs i -> do
forM_ [n,n-1..i+1] $ j -> do
x <- readArray ary j
y <- readArray ary (j-i)
writeArray ary j (x || y)
writeArray ary i True
만약 (ary ! n = True) 이면 여기서
종료
return ary
in ary ! n
• forM, forM_은 무조건 모든 액션을 수행한다
• 일부 약수만으로 N을 완성할 수 있으면
더 계산할 필요가 없다
• C의 break 같은 구문은 없나?
• MonadPlus의 guard가 그런 역할
• 하지만 forM은 Monad에 대해 정의됨
• forM_ :: Monad m => [a] -> (a -> m b) -> m ()
• class (Monad m) => MonadPlus m
• guard 적용 불가
수정된 0-1 KNAPSACK 코드
for :: Monad m => [a] -> (b -> Bool) -> (a -> m b) -> m [b]
for [] _ _ = return []
for (x:xs) test f = f x >>= y ->
if test y
then for xs test f >>= ys -> return (y:ys)
else return []
knapsack :: Int -> [Int] -> Bool
knapsack n divs =
let ary = runSTUArray $ do
ary <- newArray (1,n) False
writeArray ary 1 True
for divs not i -> do
form_ [n,n-1..i+1] $ j -> do
x <- readArray ary j
y <- readArray ary (j-i)
writeArray ary j (x || y)
writeArray ary i True
fin <- readArray ary n
return False
return ary
in ary ! n
• 중간에 멈출 수 있는 for를 직접 구현
• 액션을 실행할 때마다 모나드 내부의 값을 추출한다
• 추출한 값에 test 함수 적용한 결과가 False면 정지
• 루프 조기 탈출을 구현
성능을 측정해보자
• 또다시 N = 500000에 대해 테스트
• 메모리 사용 18MB -> 1MB
• 실행 8.94초 -> 0.19초
이건 통과를 안 할 수가 없다
응 아냐
모든 수단을 활용했다
• 철저한 평가(Strict Evaluation)
• 초고속 계산을 위한 불리언 원시 타입
• 불필요한 값 생성 방지
• 탐색 공간 줄이기
계산 테이블 재고
N = 12 1 2 3 4 5 6 7 8 9 10 11 12
s1 = 1 1 1 1 1 1 1 1 1 1 1 1 1
s2 = 2 1 2 3 3 3 3 3 3 3 3 3 3
s3 = 3 1 2 3 4 5 6 6 6 6 6 6 6
s4 = 4 1 2 3 4 5 6 7 8 9 10 10 10
s5 = 6 1 2 3 4 5 6 7 8 9 10 11 12
• N = 12인 경우 총 계산: 6 * 12 = 72칸 / 실제 필요한 계산: 23칸
• N = 18인 경우 총 계산: 5 * 18 = 90칸 / 실제 필요한 계산: 25칸
알고리즘 대격변
knapsack2 n divs = f [n] divs where
f xs [] = any (== 0) xs
f xs (d:ds) =
let ys = filter (>= 0) (map (x -> x - d) xs)
in f (ys ++ xs) ds
• 약수 리스트에 다시 1 포함
• 약수는 큰 것부터 계산 (knapsack2를 호출할 때 약수 리스트를 뒤집어서 전달)
• 그런데 중복 원소가 있으면?
• 그냥 중복 계산 – 걸러내는 게 더 오래 걸린다
알고리즘 대격변
knapsack2 n divs = f [n] divs where
f xs [] = any (== 0) xs
f xs (d:ds) =
let ys = filter (>= 0) (map (x -> x - d) xs)
in if (any (== 0) ys) then True else f (ys ++ xs) ds
• 역시 조기 탈출 구현
알고리즘 대격변
knapsack2 n divs = f [n] divs (sum divs) where
f xs [] _ = any (== 0) xs
f xs (d:ds) lim =
let ys = filter (y -> 0 <= y && y <= lim) (map (x -> x - d) xs)
in if (any (== 0) ys) then True else f (ys ++ xs) ds (lim-d)
• 분기 한정(Branch and Bound)
• 𝑠1, 𝑠2, … , 𝑠 𝑘 의 부분합으로 M을 만들어야 하는데 sum 𝑠1, 𝑠2, … , 𝑠 𝑘 < M 이면 애초에 만들 수가 없다
SUPER FAST
main = do
forM_ [2..500000] $ i -> do
when (weird i) (print i)
• 2에서 50만까지 전수 검사해도 6.68초
• 메모리 사용량은 14MB
통과
그런데
• 철저한 평가(Strict Evaluation)
• 표현식이 너무 길어지기 전에 계산을 강제로 수행
• 원시 타입(Unboxed type)
• 정수 표현에 C와 같은 바이트 사용
• 테이블의 두 행만 메모리에 유지
• 필요없는 행은 바로 가비지 컬렉션이 되도록
다 안씀
결론
그냥 알고리즘을 잘 짜자

More Related Content

PPTX
하스켈로 알고리즘 문제 풀기
PPTX
하스켈로 알고리즘 문제 풀기 2
PPTX
하스켈 성능 튜닝
PDF
Example
PPTX
하스켈 프로그래밍 입문
PPTX
R 프로그래밍-향상된 데이타 조작
PPTX
R 프로그래밍 기본 문법
PDF
알고리즘과 자료구조
하스켈로 알고리즘 문제 풀기
하스켈로 알고리즘 문제 풀기 2
하스켈 성능 튜닝
Example
하스켈 프로그래밍 입문
R 프로그래밍-향상된 데이타 조작
R 프로그래밍 기본 문법
알고리즘과 자료구조

What's hot (20)

PDF
알고리즘 스터디(정렬) Seungdols
PPTX
하스켈 프로그래밍 입문 2
PPTX
종이접기(fold) 프로그래밍
PDF
함수형 프로그래밍? 그래서 어떻게
PDF
Tensorflow regression 텐서플로우 회귀
PDF
쏙 알고스터디 01
PPTX
Sicp 2.2 계층 구조 데이터와 닫힘 성질
PPTX
세미나
PDF
하스켈 모나드
PDF
2021 2학기 정기 세미나 4주차
PDF
Coursera Machine Learning으로 기계학습 배우기 : week2
PPTX
해커에게 전해들은 머신러닝 #3
PDF
확통 회귀분석
PDF
R 스터디 네번째
PDF
차원축소 훑어보기 (PCA, SVD, NMF)
PPTX
Python 스터디
PPTX
R 프로그램의 이해와 활용 v1.1
PDF
[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이
PDF
고등학생 R&E Python summary for test
PDF
Python Programming: Function
알고리즘 스터디(정렬) Seungdols
하스켈 프로그래밍 입문 2
종이접기(fold) 프로그래밍
함수형 프로그래밍? 그래서 어떻게
Tensorflow regression 텐서플로우 회귀
쏙 알고스터디 01
Sicp 2.2 계층 구조 데이터와 닫힘 성질
세미나
하스켈 모나드
2021 2학기 정기 세미나 4주차
Coursera Machine Learning으로 기계학습 배우기 : week2
해커에게 전해들은 머신러닝 #3
확통 회귀분석
R 스터디 네번째
차원축소 훑어보기 (PCA, SVD, NMF)
Python 스터디
R 프로그램의 이해와 활용 v1.1
[D2 CAMPUS] 숭실대 SCCC 프로그래밍 경시대회 문제 풀이
고등학생 R&E Python summary for test
Python Programming: Function
Ad

Similar to 하스켈 성능 튜닝 2 (20)

PDF
[연세대 모르고리즘] 프로그래밍 경진대회 문제 풀이
PDF
Backtracking [ICPC Sinchon]
PDF
2018 Ajou Programming Contest solutions
PDF
한양대학교 ALOHA - 봄내전대회_알고리즘반
PDF
Haskell study 10
PPTX
01. dp hard
PDF
프로젝트 보고서
PPTX
Dynamic Programming : Step by Step
PPTX
DP 중급 2
PDF
[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이
DOCX
자료구조 Project1
PDF
2019 홍익대학교 프로그래밍 경진대회 풀이 슬라이드 (Open Contest용)
PDF
2012 Ds 01
PPTX
2019 ppc answers
PDF
자료구조01
PDF
자료구조01
PDF
자료구조01
PDF
HI-ARC PS 102 Brute Force
PPT
2007 Icpc3
DOCX
이산치수학 Project6
[연세대 모르고리즘] 프로그래밍 경진대회 문제 풀이
Backtracking [ICPC Sinchon]
2018 Ajou Programming Contest solutions
한양대학교 ALOHA - 봄내전대회_알고리즘반
Haskell study 10
01. dp hard
프로젝트 보고서
Dynamic Programming : Step by Step
DP 중급 2
[D2 CAMPUS] 2016 한양대학교 프로그래밍 경시대회 문제풀이
자료구조 Project1
2019 홍익대학교 프로그래밍 경진대회 풀이 슬라이드 (Open Contest용)
2012 Ds 01
2019 ppc answers
자료구조01
자료구조01
자료구조01
HI-ARC PS 102 Brute Force
2007 Icpc3
이산치수학 Project6
Ad

하스켈 성능 튜닝 2

  • 2. 목 차 1. 상황 요약 2. 성능 개선 3. 새로운 방법 4. 결론
  • 3. 예시로 들었던 문제 온라인 저지 사이트 ALGOSPOT 문제 ID: WEIRD (https://www.algospot.com/judge/problem/read/WEIRD) In mathematics, weird numbers are natural numbers that are abundant but not semiperfect. In other words, a natural number N is a weird number if and only if: • Sum of its proper divisors (i.e. less than N ) is greater than the number. • No subset of its divisors sum to N. For example, the set of proper divisors of 12 is { 1, 2, 3, 4, 6 } . The sum of these numbers exceed 12, however, 12 is not a weird number since 1 + 2 + 3 + 6 = 12. However, 70 is a weird number since its proper divisors are {1, 2, 5, 7, 10, 14, 35} and no subset sums to 70 . Write a program to determine if the given numbers are weird or not.
  • 5. 이전에 썼던 알고리즘 • S(N)을 알면 abundant 검사는 단순하다 • semiperfect 검사는 0-1 knapsack 문제 • 약수들은 정수이므로 다이나믹 프로그래밍으로 해결 가능 • 𝑆 𝑁 = 𝑠1, 𝑠2, … , 𝑠 𝑘 (𝑠1 ≤ 𝑠2 ≤ … ) • 𝑇 𝑖, 𝑗 = 𝑆 𝑁 의 𝑠1, 𝑠2, … , 𝑠𝑖 까지 써서 만들 수 있는, j 이하의 최대 합 • 𝑇 𝑖, 𝑗 = 0 𝑖 = 0 𝑇 𝑖 − 1, 𝑗 𝑠𝑖 > 𝑗 max 𝑇 𝑖 − 1, 𝑗 , 𝑇 𝑖 − 1, 𝑗 − 𝑠𝑖 + 𝑠𝑖 𝑜𝑡ℎ𝑒𝑟𝑤𝑖𝑠𝑒 • 𝑇 𝑘, 𝑁 = 𝑁 이면 N은 semiperfect
  • 6. 불리언 문제로 변환 • S(N)을 알면 abundant 검사는 단순하다 • semiperfect 검사는 0-1 knapsack 문제 • 약수들은 정수이므로 다이나믹 프로그래밍으로 해결 가능 • 𝑆 𝑁 = 𝑠1, 𝑠2, … , 𝑠 𝑘 (𝑠1 ≤ 𝑠2 ≤ … ) • 𝑇 𝑖, 𝑗 = 𝑆 𝑁 의 𝑠1, 𝑠2, … , 𝑠𝑖 까지 써서 만들 수 있는 최대 합이 j인가? • 𝑇 𝑖, 𝑗 = 𝑇𝑟𝑢𝑒 𝑖 = 1, 𝑗 = 1 𝐹𝑎𝑙𝑠𝑒 𝑖 = 1, 𝑗 ≠ 1 𝑇 𝑖 − 1, 𝑗 𝑠𝑖 > 𝑗 𝑇 𝑖 − 1, 𝑗 𝐴𝑁𝐷 𝑇 𝑖 − 1, 𝑗 − 𝑠𝑖 𝑜𝑡ℎ𝑒𝑟𝑤𝑖𝑠𝑒 • 𝑇 𝑘, 𝑁 = 𝑇𝑟𝑢𝑒 이면 N은 semiperfect
  • 7. 이전 0-1 KNAPSACK 코드 Knapsack :: Int -> Uarray Int Int -> Int -> UArray Int Int knapsack n divs len = a 0 (runSTUArray $ newArray (0,n) (0::Int)) where a prevRow prevA | prevRow == len = prevA | True = currA `seq` a (prevRow+1) currA where currA = runSTUArray $ newListArray (0,n) [w `seq` (v `seq` v) | w <- [0..n], let ith = divs ! prevRow, let v = maximum [at w, ith + at (w-ith)]] at w = if w < 0 then -n else prevA ! w 필요없는 배열이 바로 가비지 컬렉션되지만, 매 호출마다 새 배열을 생성한다
  • 8. 수정된 0-1 KNAPSACK 코드 knapsack :: Int -> [Int] -> Bool knapsack n divs = let ary = runSTUArray $ do ary <- newArray (1,n) False writeArray ary 1 True forM_ divs i -> do forM_ [n,n-1..i+1] $ j -> do x <- readArray ary j y <- readArray ary (j-i) writeArray ary j (x || y) writeArray ary i True return ary in ary ! n • 약수들의 배열에서 1은 제외 (base case로 직접 처리) • 단 하나의 배열 ary 사용 • C 코드처럼 보인다 • IO 모나드 안도 아닌데 ary가 변경 가능? • knapsack은 순수 함수가 맞다 • Rank-2 type 덕에 가능 • 자세한 설명은 다음 기회에…
  • 9. 수정된 0-1 KNAPSACK 코드 knapsack :: Int -> [Int] -> Bool knapsack n divs = let ary = runSTUArray $ do ary <- newArray (1,n) False writeArray ary 1 True forM_ divs i -> do forM_ [n,n-1..i+1] $ j -> do x <- readArray ary j y <- readArray ary (j-i) writeArray ary j (x || y) writeArray ary i True 만약 (ary ! n = True) 이면 여기서 종료 return ary in ary ! n • forM, forM_은 무조건 모든 액션을 수행한다 • 일부 약수만으로 N을 완성할 수 있으면 더 계산할 필요가 없다 • C의 break 같은 구문은 없나? • MonadPlus의 guard가 그런 역할 • 하지만 forM은 Monad에 대해 정의됨 • forM_ :: Monad m => [a] -> (a -> m b) -> m () • class (Monad m) => MonadPlus m • guard 적용 불가
  • 10. 수정된 0-1 KNAPSACK 코드 for :: Monad m => [a] -> (b -> Bool) -> (a -> m b) -> m [b] for [] _ _ = return [] for (x:xs) test f = f x >>= y -> if test y then for xs test f >>= ys -> return (y:ys) else return [] knapsack :: Int -> [Int] -> Bool knapsack n divs = let ary = runSTUArray $ do ary <- newArray (1,n) False writeArray ary 1 True for divs not i -> do form_ [n,n-1..i+1] $ j -> do x <- readArray ary j y <- readArray ary (j-i) writeArray ary j (x || y) writeArray ary i True fin <- readArray ary n return False return ary in ary ! n • 중간에 멈출 수 있는 for를 직접 구현 • 액션을 실행할 때마다 모나드 내부의 값을 추출한다 • 추출한 값에 test 함수 적용한 결과가 False면 정지 • 루프 조기 탈출을 구현
  • 11. 성능을 측정해보자 • 또다시 N = 500000에 대해 테스트 • 메모리 사용 18MB -> 1MB • 실행 8.94초 -> 0.19초
  • 12. 이건 통과를 안 할 수가 없다 응 아냐
  • 13. 모든 수단을 활용했다 • 철저한 평가(Strict Evaluation) • 초고속 계산을 위한 불리언 원시 타입 • 불필요한 값 생성 방지 • 탐색 공간 줄이기
  • 14. 계산 테이블 재고 N = 12 1 2 3 4 5 6 7 8 9 10 11 12 s1 = 1 1 1 1 1 1 1 1 1 1 1 1 1 s2 = 2 1 2 3 3 3 3 3 3 3 3 3 3 s3 = 3 1 2 3 4 5 6 6 6 6 6 6 6 s4 = 4 1 2 3 4 5 6 7 8 9 10 10 10 s5 = 6 1 2 3 4 5 6 7 8 9 10 11 12 • N = 12인 경우 총 계산: 6 * 12 = 72칸 / 실제 필요한 계산: 23칸 • N = 18인 경우 총 계산: 5 * 18 = 90칸 / 실제 필요한 계산: 25칸
  • 15. 알고리즘 대격변 knapsack2 n divs = f [n] divs where f xs [] = any (== 0) xs f xs (d:ds) = let ys = filter (>= 0) (map (x -> x - d) xs) in f (ys ++ xs) ds • 약수 리스트에 다시 1 포함 • 약수는 큰 것부터 계산 (knapsack2를 호출할 때 약수 리스트를 뒤집어서 전달) • 그런데 중복 원소가 있으면? • 그냥 중복 계산 – 걸러내는 게 더 오래 걸린다
  • 16. 알고리즘 대격변 knapsack2 n divs = f [n] divs where f xs [] = any (== 0) xs f xs (d:ds) = let ys = filter (>= 0) (map (x -> x - d) xs) in if (any (== 0) ys) then True else f (ys ++ xs) ds • 역시 조기 탈출 구현
  • 17. 알고리즘 대격변 knapsack2 n divs = f [n] divs (sum divs) where f xs [] _ = any (== 0) xs f xs (d:ds) lim = let ys = filter (y -> 0 <= y && y <= lim) (map (x -> x - d) xs) in if (any (== 0) ys) then True else f (ys ++ xs) ds (lim-d) • 분기 한정(Branch and Bound) • 𝑠1, 𝑠2, … , 𝑠 𝑘 의 부분합으로 M을 만들어야 하는데 sum 𝑠1, 𝑠2, … , 𝑠 𝑘 < M 이면 애초에 만들 수가 없다
  • 18. SUPER FAST main = do forM_ [2..500000] $ i -> do when (weird i) (print i) • 2에서 50만까지 전수 검사해도 6.68초 • 메모리 사용량은 14MB
  • 20. 그런데 • 철저한 평가(Strict Evaluation) • 표현식이 너무 길어지기 전에 계산을 강제로 수행 • 원시 타입(Unboxed type) • 정수 표현에 C와 같은 바이트 사용 • 테이블의 두 행만 메모리에 유지 • 필요없는 행은 바로 가비지 컬렉션이 되도록 다 안씀