2

我被要求提高用于高速数据采集的应用程序的内存效率。在多次运行 VS 内存分析器并扫描项目以查找明显问题后,我得出以下结论:尽管使用固定大小的循环缓冲区来存储获取的样本点,但 RAM 使用量相对于轮询周期会增加。例如:在 2 微秒内轮询一个信号的数据可以使用比以 50 微秒运行时多 5 倍的内存(私有字节)......即使缓冲区大小相同。

循环缓冲区是一组 SamplePoint 对象。这些对象中的每一个都包含一组 Shorts(16 位)用于每个信号的相应数据和一个日期对象(8 个字节)用于时间戳。为提高效率,循环缓冲区每次开始轮询时仅重新调整一次,并填充空样本点,然后“分配”。

此外,似乎当我们停止并运行应用程序时,它每次都会占用更多内存,就好像 Redim 没有释放先前的数组一样。

我的问题归结为以下几点:

实现包含具有数组的托管对象的循环缓冲区的最节省内存的方法是什么?此外,具有不同轮询速度的固定大小数组如何以及为什么会增加内存使用量?垃圾收集器没有时间吗?当子或函数退出时,局部变量是否会立即被处理掉?

这些是我想在继续之前排除的一些疑虑和担忧。感谢您抽出宝贵时间。另外,我可以发布代码,但这将毫无意义,因为它有很多并且分散了。

编辑:这是我编写的一些精简代码,反映了循环缓冲区的初始化、填充和重置。看到任何错误?

    ''' <summary>
''' Initialize internal list circular buffer.
''' </summary>
''' <param name="sizeOfBuffer"></param>
''' <remarks>
''' This is done for efficiency to avoid creating new samples points 
''' and redimensioning samplepoint data arrays for every read. Instead
''' the buffer is created and each samplepoint re-used. 
''' </remarks>
Friend Sub InitializeCircularBuffer(ByVal sizeOfBuffer As Integer, ByVal smpleDataSize As Integer, ByVal name As String)

    Dim mutexName As String = CreateMutexName(name)
    'First check for already existing mutex, otherwise create a new one
    Try
        _Mutex = Mutex.OpenExisting(mutexName)
    Catch ex As WaitHandleCannotBeOpenedException
        'Intialize mutex for each shared memory with unique names
        _Mutex = New Mutex(False, mutexName)
    Catch ex As UnauthorizedAccessException
        'Intialize mutex for each shared memory with unique names
        _Mutex = New Mutex(False, mutexName)
    End Try

    _Mutex.WaitOne()
    Try
        _SampleDataSize = smpleDataSize

        'Check size is valid, otherwise use the shared memory numSamples as default
        If sizeOfBuffer <= 0 Then
            _CircularBufferSize = _DefaultBufferSize
        Else
            _CircularBufferSize = sizeOfBuffer
        End If

        'Initialize/Reset circular buffer
        If _CircularBuffer Is Nothing Then
            _CircularBuffer = New List(Of SHM_SamplePoint)
        Else
            _CircularBuffer.Clear()
        End If

        'Create empty sample points with redimensioned data arrays in buffer 
        For i = 0 To _CircularBufferSize - 1
            _CircularBuffer.Add(New SHM_SamplePoint(_SampleDataSize))
        Next

        'Set current index to last place in buffer
        'It is incremented to first place when buffer
        'is being populated
        _CurrentIndex = _CircularBufferSize - 1

        _CircularBufferInitialized = True
    Catch ex As Exception
    Finally
        _Mutex.ReleaseMutex()
    End Try
End Sub

''' <summary>
''' Packages raw data and populates circular buffer.
''' </summary>
Friend Sub PopulateCircularBuffer(ByRef rawData() As Double, ByVal rawTimeStamps() As Double, ByVal numSamples As Integer, Optional ByVal startIndex As Integer = 0)
    _Mutex.WaitOne()
    Try
        _NumNewSamples = numSamples
        If _NumNewSamples > 0 Then
            For i As Integer = startIndex To _NumNewSamples - 1
                'Get index of next sample to be overwritten
                _CurrentIndex = (_CurrentIndex + 1) Mod _CircularBufferSize
                'Assign time-stamp
                _CircularBuffer(_CurrentIndex).TimeStamp = Date.FromOADate(rawTimeStamps(i))
                'Assign data
                Array.ConstrainedCopy(rawData, (i * _SampleDataSize), _CircularBuffer(_CurrentIndex).Data, 0, _SampleDataSize)
            Next
        End If
    Catch ex As Exception
    Finally
        _Mutex.ReleaseMutex()
    End Try
End Sub

''' <summary>
''' Empty the circular buffer.
''' </summary>
''' <remarks></remarks>
Friend Sub ResetCircularBuffer()
    For i As Integer = 0 To _CircularBuffer.Count - 1
        _CircularBuffer(i).Data = Nothing
        _CircularBuffer(i).TimeStamp = Nothing
    Next
    _CircularBuffer.Clear()
    _CircularBuffer.TrimExcess()
    'Signal garbage collection
    GC.Collect()
    _CircularBufferSize = 0
    _CircularBufferInitialized = False
End Sub
4

2 回答 2

2

垃圾收集器会在认为合适的时候处理对象。事实上,我正在开发的一个应用程序现在获取内存的速度非常快,以至于 GC 直到进程使用大约 1.4GB 的 RAM 才开始释放 RAM(即使只有大约 100K 正在使用中,其余的有资格收集)。

根据您的描述,我完全不清楚为什么内存利用率应该与采样率成反比(给定一个固定大小的缓冲区)。我同意发布相关代码是明智之举的评论。

如果您当前使用的是工作站之一(反之亦然),您可以尝试使用服务器垃圾收集器来查看结果是否有所不同。

我们应该使用“工作站”垃圾收集还是“服务器”垃圾收集?

于 2012-06-08T05:19:19.660 回答
0

通过将“ByVal rawTimeStamps() As Double”更改为 ByRef,您将在内存占用方面获得小幅改进,因为将为 ByVal 复制数组。

此外,存储原始时间戳并仅调用 Date.FromOADate 来转换从缓冲区中提取的值 - 您正在创建您可能永远不需要的 Date 对象(如果缓冲区环绕)。

于 2012-11-14T20:05:06.063 回答