【问题标题】:High Speed Circular Buffer高速环形缓冲器
【发布时间】:2012-06-08 05:14:42
【问题描述】:

有人要求我提高用于高速数据采集的应用程序的内存效率。在多次运行 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

【问题讨论】:

  • 您应该发布当前缓冲区实现的代码。内存使用量增加可能是由于代码中的错误。

标签: vb.net memory-leaks dispose circular-buffer


【解决方案1】:

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

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

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

Should we use "workstation" garbage collection or "server" garbage collection?

【讨论】:

  • 感谢 Eric 提供的信息。我发布了一些反映循环缓冲区主要方面的代码。您提到您的应用程序消耗了 1.4 GB 的 RAM,您采取了哪些措施来释放这些资源?
  • 我没有采取任何措施释放资源。有问题的进程会定期“唤醒”以处理大量传入数据。处理完数据后,GC 决定它有时间清理并这样做(实际上,如果批处理中有足够的数据,它会调用 GC 几次,但分配的内存仍然徘徊在该范围内)。它运行在具有 16GB 内存且没有实时要求的批处理机器上,因此我没有采取步骤进行调整。如果我需要,服务器 GC 模型可能会通过在后台稳定收集来帮助我。
  • 我没有看到代码中有任何明显的缺陷。您是否尝试过运行内存分析器(例如 VS2010 中内置的分析器)来查看哪类对象占用了所有空间,以及大部分对象驻留在哪一代。好的经验法则是 Gen 1 应该是第 0 代的 10%,第 2 代应该是第 1 代的 10%(非常广泛的规则,但作为一个方向很有用)。
  • 嗨,Eric,我确实运行了分析器,并且实例化最多的对象是我的 SamplePoint 对象,其中包含双精度数组。随后双打也是所有数据类型中使用最多的。我没有查看垃圾收集器是如何分配对象的,但我会假设样本点被认为很小(对于一个信号,我们有 10 b
【解决方案2】:

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

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

【讨论】:

  • 在 .net 中传递一个数组只是传递一个对数组的引用;传递数组byrefref 传递给数组引用(允许将其更改为指向不同的数组)。
猜你喜欢
  • 2017-05-02
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 2017-12-07
  • 2018-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多