VB.NETでサクッとメモリ使用量を計測する

リアルタイムでCLR上のアプリのメモリ使用量なんかを確認したい!ってお話です。。。


パフォーマンスチューニングは処理速度以外にメモリ使用量なんかも対象にしますよね?

プロジェクトの後半で、長期間に渡ってテストしているとOutOfMemoryExceptionなんかで悩まされたりするはずです。

チューニングは、やはり計測が基本です。

。。ということで、今回は.NETでメモリ使用量などを手軽に計測する方法をlogります。

ポイント

  • アプリのメモリ使用量の計測にMy.Application.Info.WorkingSetを使います。
  • OSの空き容量の計測にDiagnostics.PerformanceCounterを使います。
  • アプリのタイトルバーにメモリ使用量などを表示します。
  • アドオン的に使います。

MemoryMonitorの実装&使用例

Option Explicit On
Option Strict On

Public Class MemoryMonitor
    Private _performanceCounter As Diagnostics.PerformanceCounter
    Private _timer As Threading.Timer
    Private _defaultWindowTitle As String = ""
    Public Sub StartMonitor(ByVal f As Form)
        _performanceCounter = New Diagnostics.PerformanceCounter
        _performanceCounter.CategoryName = "Memory"
        _performanceCounter.CounterName = "Available KBytes"
        _performanceCounter.NextValue()
        _defaultWindowTitle = f.Text
        _timer = New Threading.Timer(
            New Threading.TimerCallback(
                Sub()
                    f.Invoke(
                        Sub()
                            f.Text = _defaultWindowTitle &
                                String.Format("  [メモリ情報] 使用: {0,6:#,##0.0}MB",
                                            (My.Application.Info.WorkingSet / 1024 / 1024)) &
                                String.Format("  空き: {0,6:#,##0.0}MB",
                                            (_performanceCounter.NextValue() / 1024))
                        End Sub)
                End Sub), f, 1000, 1000)
    End Sub
    Public Sub StopMonitor()
        Try
            If Not _performanceCounter Is Nothing Then
                _performanceCounter.Dispose()
                _performanceCounter = Nothing
            End If
            If Not _timer Is Nothing Then
                _timer.Dispose()
                _timer = Nothing
            End If
        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try
    End Sub
End Class
Partial Public Class Form1
    Private mm As New MemoryMonitor
    Private Sub Activated_(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Me.MdiParent Is Nothing Then mm.StartMonitor(Me)
    End Sub
    Private Sub FormClosed_(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.FormClosing
        If Me.MdiParent Is Nothing Then mm.StopMonitor()
    End Sub
End Class

表示したいForm(今回はForm1)のLoad、FormClosingイベントをHandleするだけです。

VSのプロジェクトに追加するだけのシンプルさです。

New Threading.Timerの第3引数で開始時間(ms)と第4引数で更新間隔(ms)を指定します。


必要に応じて、TimerCallbackのラムダでログ出力したりすると資料作りに便利ですね!


おまけ

デバッガで、タイマースレッドにステップインしちゃうとかなりウザいので、DebuggerStepThrough属性付きのパターンも挙げときます。
※ラムダはDebuggerStepThrough属性つけられないので、Delegate Subになってます。

Option Explicit On
Option Strict On

<Diagnostics.DebuggerStepThrough()>
Public Class MemoryMonitor
    Private _performanceCounter As Diagnostics.PerformanceCounter
    Private _timer As Threading.Timer
    Private _defaultWindowTitle As String = ""
    Public Sub StartMonitor(ByVal f As Form)
        _performanceCounter = New Diagnostics.PerformanceCounter
        _performanceCounter.CategoryName = "Memory"
        _performanceCounter.CounterName = "Available KBytes"
        _performanceCounter.NextValue()
        _defaultWindowTitle = f.Text
        Dim tc As Threading.TimerCallback = AddressOf TimerCallback
        _timer = New Threading.Timer(tc, f, 1000, 1000)
    End Sub
    Public Sub StopMonitor()
        Try
            If Not _performanceCounter Is Nothing Then
                _performanceCounter.Dispose()
                _performanceCounter = Nothing
            End If
            If Not _timer Is Nothing Then
                _timer.Dispose()
                _timer = Nothing
            End If
        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try
    End Sub
    Delegate Sub UpdateMemoryUsageDelegate(ByVal f As Form)
    Private Sub UpdateMemoryUsage(ByVal f As Form)
        f.Text = _defaultWindowTitle &
            String.Format("  [メモリ情報] 使用: {0,6:#,##0.0}MB",
                        (My.Application.Info.WorkingSet / 1024 / 1024)) &
            String.Format("  空き: {0,6:#,##0.0}MB",
                        (_performanceCounter.NextValue() / 1024))
    End Sub
    Private Sub TimerCallback(ByVal o As Object)
        Dim f As Form = DirectCast(o, Form)
        Try
            f.Invoke(New UpdateMemoryUsageDelegate(AddressOf UpdateMemoryUsage), New Object() {f})
        Catch ex As Exception
            Debug.Print(ex.Message)
        End Try
    End Sub
End Class
Partial Public Class Form1
    Private mm As New MemoryMonitor
    <Diagnostics.DebuggerStepThrough()>
    Private Sub Activated_(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Me.MdiParent Is Nothing Then mm.StartMonitor(Me)
    End Sub
    <Diagnostics.DebuggerStepThrough()>
    Private Sub FormClosed_(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.FormClosing
        If Me.MdiParent Is Nothing Then mm.StopMonitor()
    End Sub
End Class


Work! Enjoy it!