f# 和递归中的内存使用
f# and memmory usage in recursive
最近我想学习更多关于内存分析的知识,所以我有一个简单的应用程序可以从 UDP 接收消息。现在我使用标准 Visual Studio 2019 内存分析器 https://memprofiler.com/.
从外观上看,我在这个应用程序中的内存使用量很小但不断增加,但我不知道我是否在做某事(内存分析的术语对我来说很新,所以我大部分时间都这样做不要打结我在看什么)。
我的问题和分析器的问题数据向我展示了这一点
快照拍摄间隔大约10分钟(但程序运行一个小时,我每10分钟拍摄一次快照,所有进程都稳定只是这部分在增加)
因此,如果我理解正确,问题出在过程 $UdpListener.listenerWatcher@76-5 中,它是代码的一部分,如下所示:
member this.Start (udpReceiver : UdpClient) (onMessageReceived : byte array -> IBus -> unit) =
async {
try
let! receiveResult = udpReceiver.ReceiveAsync() |> Async.AwaitTask
let receiveBytes = receiveResult.Buffer
this.Logger.Debug
(sprintf "Received info %A from remote end poin Ip:%s Port:%i" receiveBytes (receiveResult.RemoteEndPoint.Address.ToString()) receiveResult.RemoteEndPoint.Port)
onMessageReceived receiveBytes bus
with
| :? ObjectDisposedException -> return ()
| :? SocketException -> return ()
| ex ->
match ex.InnerException :? ObjectDisposedException with
| true ->
this.Logger.Warn "Receiver is disposed or socked is closed, probably receiver restart"
return ()
| _ ->
this.Logger.Fatal(sprintf "Fatal error while starting listening, exception %A" ex)
raise (ex)
}
member this.Watcher (ip: IPAddress, port : int32) (onMessageReceived : byte array -> IBus -> unit) =
let rec listenerWatcher (udpReceiver : UdpClient) =
async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with | ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex)
return ()
}
(ip, port) |> this.CreateUdpReceiver |> listenerWatcher
UdpReceiver 在 watcher 启动时创建一次,它看起来像:
member private this.CreateUdpReceiver (ip: IPAddress, port : int32) : UdpClient =
try
let endpoint = IPEndPoint (ip, port)
let receivingClient = new UdpClient()
receivingClient.Client.Bind endpoint
receivingClient.Client.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true)
this.Logger.Debug (sprintf "Listener binded to Ip Adress:%s Port:%i" (endpoint.Address.ToString()) endpoint.Port)
UdpMediator.Instance.InvokeAddUdpReceiver receivingClient
receivingClient
with | ex ->
this.Logger.Fatal(sprintf "Fatal error while stoping udp clinet")
raise (ex)
而 InvokeAddUpdReceiver 只是将创建的接收器添加到外部,因此我们可以在不需要或需要刷新时处理他。
所以基本上我无法确定这次泄漏的来源,对我来说它看起来不错,但我可能对某些事情视而不见。
我在您的代码中看到的一个潜在问题来源是在 listenerWatcher
函数中实现递归调用的方式。你有:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
这里的问题是使用 return!
的递归调用在 try .. with
块内。这可以防止异步运行时将其视为尾递归调用。它必须为异常处理分配一个处理程序,因此它为每个递归调用添加一个分配的对象。
您应该能够通过将异常处理移到递归循环之外来解决此问题:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver) }
let startListenerWatcher udpReceiver = async {
try
return! listenerWatcher udpReceiver
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
或者,假设您不需要在递归调用中保留任何状态,您可以只使用 while
循环而不是递归:
let listenerWatcher (udpReceiver : UdpClient) = async {
try
while true do
do! this.Start udpReceiver onMessageReceived
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
最近我想学习更多关于内存分析的知识,所以我有一个简单的应用程序可以从 UDP 接收消息。现在我使用标准 Visual Studio 2019 内存分析器 https://memprofiler.com/.
从外观上看,我在这个应用程序中的内存使用量很小但不断增加,但我不知道我是否在做某事(内存分析的术语对我来说很新,所以我大部分时间都这样做不要打结我在看什么)。
我的问题和分析器的问题数据向我展示了这一点
快照拍摄间隔大约10分钟(但程序运行一个小时,我每10分钟拍摄一次快照,所有进程都稳定只是这部分在增加)
因此,如果我理解正确,问题出在过程 $UdpListener.listenerWatcher@76-5 中,它是代码的一部分,如下所示:
member this.Start (udpReceiver : UdpClient) (onMessageReceived : byte array -> IBus -> unit) =
async {
try
let! receiveResult = udpReceiver.ReceiveAsync() |> Async.AwaitTask
let receiveBytes = receiveResult.Buffer
this.Logger.Debug
(sprintf "Received info %A from remote end poin Ip:%s Port:%i" receiveBytes (receiveResult.RemoteEndPoint.Address.ToString()) receiveResult.RemoteEndPoint.Port)
onMessageReceived receiveBytes bus
with
| :? ObjectDisposedException -> return ()
| :? SocketException -> return ()
| ex ->
match ex.InnerException :? ObjectDisposedException with
| true ->
this.Logger.Warn "Receiver is disposed or socked is closed, probably receiver restart"
return ()
| _ ->
this.Logger.Fatal(sprintf "Fatal error while starting listening, exception %A" ex)
raise (ex)
}
member this.Watcher (ip: IPAddress, port : int32) (onMessageReceived : byte array -> IBus -> unit) =
let rec listenerWatcher (udpReceiver : UdpClient) =
async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with | ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex)
return ()
}
(ip, port) |> this.CreateUdpReceiver |> listenerWatcher
UdpReceiver 在 watcher 启动时创建一次,它看起来像:
member private this.CreateUdpReceiver (ip: IPAddress, port : int32) : UdpClient =
try
let endpoint = IPEndPoint (ip, port)
let receivingClient = new UdpClient()
receivingClient.Client.Bind endpoint
receivingClient.Client.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true)
this.Logger.Debug (sprintf "Listener binded to Ip Adress:%s Port:%i" (endpoint.Address.ToString()) endpoint.Port)
UdpMediator.Instance.InvokeAddUdpReceiver receivingClient
receivingClient
with | ex ->
this.Logger.Fatal(sprintf "Fatal error while stoping udp clinet")
raise (ex)
而 InvokeAddUpdReceiver 只是将创建的接收器添加到外部,因此我们可以在不需要或需要刷新时处理他。
所以基本上我无法确定这次泄漏的来源,对我来说它看起来不错,但我可能对某些事情视而不见。
我在您的代码中看到的一个潜在问题来源是在 listenerWatcher
函数中实现递归调用的方式。你有:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
try
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver)
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
这里的问题是使用 return!
的递归调用在 try .. with
块内。这可以防止异步运行时将其视为尾递归调用。它必须为异常处理分配一个处理程序,因此它为每个递归调用添加一个分配的对象。
您应该能够通过将异常处理移到递归循环之外来解决此问题:
let rec listenerWatcher (udpReceiver : UdpClient) = async {
do! this.Start udpReceiver onMessageReceived
return! listenerWatcher (udpReceiver) }
let startListenerWatcher udpReceiver = async {
try
return! listenerWatcher udpReceiver
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }
或者,假设您不需要在递归调用中保留任何状态,您可以只使用 while
循环而不是递归:
let listenerWatcher (udpReceiver : UdpClient) = async {
try
while true do
do! this.Start udpReceiver onMessageReceived
with ex ->
this.Logger.Fatal (sprintf "Udp listener watcher has beed stoped %A" ex) }