Performance 回忆录完成了,现在怎么办?
我试图在Haskell中解决一个难题,并编写了以下代码:Performance 回忆录完成了,现在怎么办?,performance,haskell,memoization,Performance,Haskell,Memoization,我试图在Haskell中解决一个难题,并编写了以下代码: u 0 p = 0.0 u 1 p = 1.0 u n p = 1.0 + minimum [((1.0-q)*(s k p)) + (u (n-k) p) | k <-[1..n], let q = (1.0-p)**(fromIntegral k)] s 1 p = 0.0 s n p = 1.0 + minimum [((1.0-q)*(s (n-k) p)) + q*((s k p) + (u (n-k) p)) | k
u 0 p = 0.0
u 1 p = 1.0
u n p = 1.0 + minimum [((1.0-q)*(s k p)) + (u (n-k) p) | k <-[1..n], let q = (1.0-p)**(fromIntegral k)]
s 1 p = 0.0
s n p = 1.0 + minimum [((1.0-q)*(s (n-k) p)) + q*((s k p) + (u (n-k) p)) | k <-[1..(n-1)], let q = (1.0-(1.0-p)**(fromIntegral k))/(1.0-(1.0-p)**(fromIntegral n))]
u0p=0.0
u 1 p=1.0
u n p=1.0+最小值[((1.0-q)*(skp))+(u(n-k)p)| k我在运行编译后的版本时不会耗尽内存,但Java版本的工作方式与Haskell版本的工作方式之间存在显著差异,我将在这里说明
首先要做的是添加一些重要的类型签名。特别是,您不需要Integer
数组索引,因此我添加了:
memoUa :: Array Int Double
memoSa :: Array Int Double
我使用ghc mod check
找到了这些。我还添加了一个main
,以便您可以从命令行运行它:
import System.Environment
main = do
(arg:_) <- getArgs
let n = read arg
print $ mua n
然后当我们像这样调用程序时:
memo 1000 +RTS -s
我们将获得如下分析输出:
164.31333233347755
98,286,872 bytes allocated in the heap
29,455,360 bytes copied during GC
657,080 bytes maximum residency (29 sample(s))
38,260 bytes maximum slop
3 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 161 colls, 0 par 0.03s 0.03s 0.0002s 0.0011s
Gen 1 29 colls, 0 par 0.03s 0.03s 0.0011s 0.0017s
INIT time 0.00s ( 0.00s elapsed)
MUT time 0.21s ( 0.21s elapsed)
GC time 0.06s ( 0.06s elapsed)
RP time 0.00s ( 0.00s elapsed)
PROF time 0.00s ( 0.00s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 0.27s ( 0.27s elapsed)
%GC time 21.8% (22.3% elapsed)
Alloc rate 468,514,624 bytes per MUT second
Productivity 78.2% of total user, 77.3% of total elapsed
需要注意的重要事项包括:
- 最高居住期
- 总时间
- %GC时间(或生产率)
最大驻留时间是衡量程序需要多少内存的指标。%GC时间花费在垃圾收集和生产效率上的时间比例是补充(100%-%GC时间)
如果您针对各种输入值运行程序,您将看到大约80%的生产率:
n Max Res. Prod. Time Output
2000 779,076 79.4% 1.10s 328.54535361588535
4000 1,023,016 80.7% 4.41s 657.0894961398351
6000 1,299,880 81.3% 9.91s 985.6071032981068
8000 1,539,352 81.5% 17.64s 1314.0968411684714
10000 1,815,600 81.7% 27.57s 1642.5891214360522
这意味着大约20%的运行时间用于垃圾收集。此外,随着n
的增加,内存使用率也在增加
事实证明,通过告诉Haskell计算数组元素的顺序,而不是依赖惰性计算,我们可以显著提高生产率和内存使用率:
import Control.Monad (forM_)
main = do
(arg:_) <- getArgs
let n = read arg
forM_ [1..n] $ \i -> mua i `seq` return ()
print $ mua n
这里有一些有趣的观察结果:生产率提高,内存使用率下降(在输入范围内保持不变)但是运行时间到了。这表明我们强制执行的计算比我们需要的要多。在像Java这样的命令式语言中,你必须给出一个求值顺序,这样你才能确切地知道需要执行哪些计算。看看你的Java代码,看看它正在执行哪些计算,这会很有趣。我不会耗尽内存n我运行编译后的版本,但是Java版本的工作方式与Haskell版本的工作方式有很大区别,我将在这里说明
首先要做的是添加一些重要的类型签名。特别是,您不需要Integer
数组索引,因此我添加了:
memoUa :: Array Int Double
memoSa :: Array Int Double
我使用ghc mod check
找到了这些。我还添加了一个main
,以便您可以从命令行运行它:
import System.Environment
main = do
(arg:_) <- getArgs
let n = read arg
print $ mua n
然后当我们像这样调用程序时:
memo 1000 +RTS -s
我们将获得如下分析输出:
164.31333233347755
98,286,872 bytes allocated in the heap
29,455,360 bytes copied during GC
657,080 bytes maximum residency (29 sample(s))
38,260 bytes maximum slop
3 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 161 colls, 0 par 0.03s 0.03s 0.0002s 0.0011s
Gen 1 29 colls, 0 par 0.03s 0.03s 0.0011s 0.0017s
INIT time 0.00s ( 0.00s elapsed)
MUT time 0.21s ( 0.21s elapsed)
GC time 0.06s ( 0.06s elapsed)
RP time 0.00s ( 0.00s elapsed)
PROF time 0.00s ( 0.00s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 0.27s ( 0.27s elapsed)
%GC time 21.8% (22.3% elapsed)
Alloc rate 468,514,624 bytes per MUT second
Productivity 78.2% of total user, 77.3% of total elapsed
需要注意的重要事项包括:
- 最高居住期
- 总时间
- %GC时间(或生产率)
最大驻留时间是衡量程序需要多少内存的指标。%GC时间花费在垃圾收集和生产效率上的时间比例是补充(100%-%GC时间)
如果您针对各种输入值运行程序,您将看到大约80%的生产率:
n Max Res. Prod. Time Output
2000 779,076 79.4% 1.10s 328.54535361588535
4000 1,023,016 80.7% 4.41s 657.0894961398351
6000 1,299,880 81.3% 9.91s 985.6071032981068
8000 1,539,352 81.5% 17.64s 1314.0968411684714
10000 1,815,600 81.7% 27.57s 1642.5891214360522
这意味着大约20%的运行时间用于垃圾收集。此外,随着n
的增加,内存使用率也在增加
事实证明,通过告诉Haskell计算数组元素的顺序,而不是依赖惰性计算,我们可以显著提高生产率和内存使用率:
import Control.Monad (forM_)
main = do
(arg:_) <- getArgs
let n = read arg
forM_ [1..n] $ \i -> mua i `seq` return ()
print $ mua n
这里有一些有趣的观察结果:生产率提高,内存使用率下降(在输入范围内保持不变)但是运行时间到了。这表明我们强制执行的计算比我们需要的要多。在像Java这样的命令式语言中,您必须给出一个求值顺序,这样您就可以确切地知道需要执行哪些计算。查看Java代码以了解它正在执行哪些计算是很有趣的。示例输入和预期输出会有帮助的。还有输入签名!您是否尝试过使用-O
编译,或者是在GHCi中运行此功能?您是否可以将其分解为几个较小的表达式,例如在where
子句中,或者至少将其拆分为多行?这190个字符行很难读取。通过这种方式,可读性要高得多但是它仍然需要类型签名示例输入和预期输出会有所帮助。类型签名!您是否尝试过使用-O
编译,或者您正在GHCi中运行此功能?您是否可以将其分解为一些较小的表达式,例如在where
子句中,或者至少将其拆分为多行?这些190个字符行很难阅读。这样可读性好得多,但仍然需要类型签名。我添加了java版本。顺便说一句,我不认为可以进行比需要更多的计算。我希望由于“最小”的原因,需要所有东西。我不知道你在哪里设置s[k]
常规k
(即不是1或n
)(数组u[]
)的所有其他s[]
值是否都等于0?我已经添加了java版本。顺便说一句,我看不出有多少计算可以做得比需要的多。我希望由于“最小值”的原因,一切都需要。我看不到你在哪里设置s[k]
对于常规k
(即不是1或n
)(对于数组u[]
)也是如此,s[]
的所有其他值是否都等于0?