【问题标题】:I need to access global (unmanaged) memory in c#我需要在 C# 中访问全局(非托管)内存
【发布时间】:2019-06-14 06:29:42
【问题描述】:

我是 C# 的新手,在音频项目上可能需要一些帮助。 我的音频输入缓冲区在填满时调用一个方法。在我将这个缓冲区编组到本地 float[] 的方法中,将它传递给一个函数,在该函数中完成一些音频处理。处理完函数后,我会通过 Marshall.copy 将操纵的 float[] 传回音频输出缓冲区。它可以工作,但是很难足够快地完成音频处理,将结果传递回方法而不以丑陋的故障告终。如果我扩大音频缓冲区,它会变得更好,但我会在信号链中得到无法忍受的高延迟。 一个问题是GC。我的 DSP 例程正在执行一些 FFT,并且这些方法经常需要分配局部变量。我认为这大大减慢了我的进程。
所以我需要一种方法来分配一次(并重新访问)一些非托管内存,在整个运行时保持这个内存 fpr 并从方法中引用它。

我找到了例如:

IntPtr hglobal = Marshal.AllocHGlobal(8192);
Marshal.FreeHGlobal(hglobal);

所以我尝试用静态成员定义一个全局静态类“Globasl”并将该 IntPtr 分配给它。

Globals.mem1 = hglobal;

我现在可以通过任何其他方法访问它 例如

int[] f = new int[2];
            f[0] = 111;
            f[1] = 222;
Marshal.Copy(f, 0, Globals.mem1, 2);

现在我的问题来了: 如果我想用另一种方法从上面的示例中访问这个 int[],我该怎么做?


感谢您的快速帮助。 我好像有点不准确,对不起

我的音频设备驱动程序引发了一个我捕获的缓冲区填充事件(在伪代码中,因为我现在无法访问我的家庭桌面)。看起来像:

void buffer (....)
    {
    byte[] buf = new byte[asiobuffersize];
    marshall.copy(asioinbuffers, 0, buf, asiobufferlenth);
    buf= manipulate(buf);
    marshall.copy(buf, 0, asiooutbuffer, asiobufferlenth);
    }

操作函数正在做一些从字节到浮点数的转换,然后是一些数学 (FFT) 和反向转换到字节,看起来像例如

private byte[] manipulate(byte[] buf, Complex[] filter)
     {
     float bu = convertTofloat(buf); //conversion from byte to audio float here
     Complex[] inbuf = new Complex[bu.Length];
     Complex[] midbuf = new Complex[bu.Length];
     Complex[] mid2buf = new Complex[bu.Length];
     Complex[] outbuf = new Complex[bu.Length];
     for ( n....)
     {
     inbuf[n]= bu[n];    //Copy to Complex
     }

     midbuf=FFT(inbuf);         //Doing FFT transform

     for (n....)
     {
     mid2buf[n]=midbuf[n]*filter[n];    //  Multiply with filter
     } 

     outbuf=iFFT(mid2buf)        //inverse iFFT transform

     byte bu = convertTobyte(float); //conversion from float back to audio byte

     return bu;
     }

在这里,我希望我的速度问题是。所以我认为如果操纵函数可以“获得”一块固定的非托管内存(一旦预先创建)固定所有这些变量(例如复杂)和预先分配的内存,问题就可以解决,所以我不能每次调用函数时创建新的。我首先想到了错误 FFT 或数学故障的原因,但它发生在几秒钟的“尖锐”时间距离内,因此它与削波等音频信号问题无关。我认为问题发生了,当 GC 正在做一些严肃的工作并且正好吃掉我这几毫秒的时间以及时填充输出缓冲区时。 可以接受。

【问题讨论】:

  • 我在这里看不到方法。从其他方法访问某些内容是什么意思?
  • 看不到你的很多代码我建议用using 封装你的处理,所以该对象只在它被使用的时间内有效,而垃圾收集器不需要照顾它
  • 主要的问题是你在复制任何东西。如果没有看到更多的代码,很难给你一个合适的解决方案。简单地在你的静态类上分配一个静态数组应该让它在 appdomain 的生命周期内保持活跃,并且不应该需要“重新分配”。也就是说,在任何高吞吐量环境中批量复制缓冲区都会导致一些性能问题。我们需要查看更多代码。
  • 短期本地 POCO 变量很少引入任何显着的性能影响。事实上,拥有大量短期变量是 GC 优化的场景。我建议不要在这种优化上浪费时间,而是寻找一种方法来修改代码以避免缓冲区复制。

标签: c# memory global


【解决方案1】:

我真的怀疑您遇到的问题是由您的托管缓冲区创建/复制引起的。相反,我认为您的问题是您的数据捕获逻辑与您的 DSP 逻辑相结合。通常,捕获的数据驻留在循环缓冲区中,数据会在一段时间后被重写,因此您应该尽快获取这些数据。 问题是在 DSP 完成之前,您不会获取下一个可用数据块;您已经知道 FFT 操作确实是 CPU 密集型的!如果您有一个处理高峰,您可能无法在捕获驱动程序重写之前检索数据。

解决您的问题的一种方法是尝试尽可能增加捕获缓冲区的大小和/或可用数量。这可以在重写捕获的数据之前为您争取更多时间。另一种可能性,也是我喜欢的一种可能性是将处理阶段与捕获阶段分离,这样,如果在您忙于执行 DSP 计算时有新数据可用,您仍然可以获取它并几乎在它可用时立即对其进行缓冲。在 manipulate 方法中,您对垃圾收集引起的暂停或计算峰值变得更有弹性。

这将涉及创建两个线程:捕获线程和处理线程。您还需要一个“事件”来向处理线程发出新数据可用的信号,以及一个用作动态、可扩展缓冲区的队列。

捕获线程看起来像这样:

// the following are class members
AutoResetEvent _bufQueueEvent = new AutoResetEvent(false);
Queue<byte[]> _bufQueue = new Queue<byte[]>;
bool _keepCaptureThreadAlive;
Thread _captureThread;

void CaptureLoop() {
    while( _keepCaptureThreadAlive ) {

       byte[] asioinbuffers = WaitForBuffer();  
       byte[] buf = new byte[asiobuffers.Length]
       marshall.copy(asioinbuffers, 0, buf, asiobufferlenth);
       lock( _bufQueue ) {
          _bufQueue.Enqueue(buf);
       }
       _bufQueueEvent.Set();   // notify processing thread new data is available
    }
}

void StartCaptureThread() {
   _keepCaptureThreadAlive = true;
   _captureThread = new Thread(new(ThreadStart(CaptureLoop));
   _captureThread.Name = "CaptureThread";
   _captureThread.IsBackground = true;
   _captureThread.Start();
}

void StopCaptureThread() {
   _keepCaptureThreadAlive = false;
   _captureThread.Join();  // wait until the thread exits.
}

处理线程看起来像这样

// the following are class members
bool _keepProcessingThreadAlive;
Thread _processingThread;

void ProcessingLoop() {
   while( _keepProcessingThreadAlive ) {
      _bufQueueEvent.WaitOne();  // thread will sleep until fresh data is available
      if( !_keepProcessingThreadAlive ) {
         break;  // check if thread is being waken for termination.
      }   
      int queueCount;
      lock( _bufQueue ) {
        queueCount = _bufQueue.Count;
      }
      for( int i = 0; i < queueCount; i++ ) {
         byte[] buffIn;
         lock( _bufQueue ) {
            // only lock during dequeue operation, this way the capture thread Will
            // be able to enqueue fresh data even if we are still doing DSP processing
            buffIn = _bufQueue.Dequeue();
         }
         byte[] buffOut = manipulate(buffIn);  // you are safe if this stage takes more time than normal, you will still get the incoming data   
         // additional logic using manipulate() return value
         ...
      }
   }
}

void StartProcessingThread() {
  _keepProcessingThreadAlive = true;
  _processingThread = new Thread(new(ThreadStart(ProcessingLoop));
  _processingThread.Name = "ProcessingThread";
  _processingThread.IsBackground = true;
  _processingThread.Start();
}

void StopProcessingThread() {
   _keepProcessingThreadAlive = false;
   _bufQueueEvent.Set();  // wake up thread in case it is waiting for data
   _processingThread.Join();
}

在我的工作中,我们还执行大量 DSP,这种模式确实帮助我们解决了您遇到的问题。

【讨论】:

    猜你喜欢
    • 2011-01-26
    • 1970-01-01
    • 2013-12-14
    • 2011-02-08
    • 2017-04-24
    • 1970-01-01
    • 2013-12-29
    • 2010-11-24
    • 2011-12-24
    相关资源
    最近更新 更多