DeepStream学习拾遗
Something useful.
Last updated
Something useful.
Last updated
nvstreammux
插件能够接收多个输入源数据,将他们组成一个batch buffer并附加上一个NvDsBatchMeta数据结构输出。
在连接时由于无法预知source的信息,因此nvstreammux
的sink-pad
是使用gst_element_request_pad()
动态生成的因此在link插件进行negotiation时必须要使用gst_element_get_request_pad()
来获取新生成的pad进行连接。
根据DeepStram SDK API Reference中的NvBufSurfaceMemType可以知道在DeepStream中总共有7种memory,分别对应两个dGPU和Jetson两个平台的4种内存:system memory,host memory,device memory,unified memory(default memory为对应平台的device memory)。
其中system memory就是普通的CPU内存,在语言层面由malloc/free管理。
剩下三种都是Nvidia针对CPU和GPU相互进行数据传递设计的内存管理机制:
1)Pinned memory(host memory)
CUDA提供了一系列Memory Management API,在早先的开发中为了完成CPU和GPU之间的数据传递我们需要进行大量的显式内存分配和拷贝工作。由于对于device(GPU)的malloc/free的运行开销非常大,因此更常用的做法是通过cudaMemcpy()
接口完成两者之间数据的交互,尽可能复用已申请的device内存。
同时由于虚拟内存机制的存在,host memory在malloc之后直到被访问才会去触发一个page fault操作来申请真正的物理内存,这是OS决定的。因为CPU和GPU通过PCI-E总线进行连接通信,因此GPU无法控制pageable memory的分配和转移的时机,所以在申请device memory的时候,我们需要先进行一个数据预取的操作——CUDA驱动程序首先分配一个临时的locked-page,将主机数据复制到locked-page,然后将数据从locked-page传输到device memory,如下图所示:
为了节省这次页表拷贝的开销,Nvidia设计了一种Pinned memory,pinned的含义是page-locked或non- pageable,这块内存是单独划分出来,能够被device直接访问的特殊内存,因此相对于pageable memory具有更高的带宽,但是由于占用了虚拟内存,过度使用会影响虚拟内存性能。Pinned Memory比pageable Memory的分配操作更加昂贵,但是他对大数据的传输有很好的表现。
2)Device memory:
Device memory简单理解就是GPU内存,可以通过cudaMalloc()
接口来申请。
频繁的进行memcpy对于追求性能的开发者来说是不可忍受的,因此CUDA还提供了一种zero-copy机制,用于内存减少拷贝的次数,避免device和host之间显式的数据传输。其本质就是将将pinned memory映射到device的地址空间:
flags
为以下几个选项:
cudaHostAllocDefault
:cudaHostAlloc()
和cudaMallocHost()
等价,申请的是pinned memory。
cudaHostAllocPortable
:分配的pinned memory对所有CUDA context都有效,而不是单单执行分配此操作的那个context或者说线程。
cudaHostAllocWriteCombined
:在特殊系统配置情况下使用的,这块pinned memory在PCIE上的传输更快,但是对于host自己来说,却没什么效率。所以该选项一般用来让host去写,然后device读。
cudaHostAllocMapped
:返回一个标准的zero-copy momery。可以用cudaHostGetDevicePointer()
来获取device端的地址,从而直接操作device memory。
使用zero-copy memory来作为device memory的读写很频繁的那部分的补充是很不明智的,pinned这一类适合大数据传输,不适合频繁的操作,究其根本原因还是GPU和CPU之间低的可怜的传输速度,甚至,频繁读写情况下,zero-copy表现比global memory也要差不少。
当使用zero-copy来共享host和device数据时,我们必须同步Memory的获取,否则,device和host同时访问该Memory会导致未定义行为。
3)Ucnified memory
Unified memory是CUDA 6.0引入的新特性,如前文所说,在CUDA 6.0之前,程序员必须在CPU和GPU两端都进行内存分配,并不断地进行手动copy,来保证两端的内存一致。
Unified memory在程序员的视角中,维护了一个统一的内存池,在CPU与GPU中共享。使用了单一指针进行托管内存,由系统来自动地进行内存迁移。
Unified memory简化了代码编写和内存模型,可以在CPU端和GPU端共用一个指针,不用单独各自分配空间。方便管理,减少了代码量,这种代码量的减少在类对象的拷贝上体现的尤为明显。
需要表明的一点是unified memroy依赖于unified virtual addressing(UMA),并且在实现上与zero-copy相似,所有的copy工作都在runtime阶段再处理,对程序员透明。关于UMA并没有进行过多的了解,简单来说就是CPU和GPU使用同一块连续的地址空间。
知道了这几点之后再来看我前几天在映射上遇到的问题,我在Nvidia的developer forum上提了一个issue,moderator其实给我提供了一个思路让我去查看插件的内存类型属性的设置,我最终也根据这点发现了问题所在。但假如我们提前了解了Nvidia平台相关的内存管理模型,那么这个问题的解决思路将显而易见:
根据log可以知道我取出的NvBufSurface的memType是0——NVBUF_MEM_DEFAULT,对于dGPU来说那么就是device memory,根据NvBufSurfaceMap()
的说明可以知道它对于dGPU仅支持NVBUF_MEM_CUDA_UNIFIED,因此可以查看各个DeepStream插件的memory type相关的属性设置情况,最后发现nvcideoconvert的nvbuf-memory-type属性默认值是0[NVBUF_MEM_DEFAULT]而不是3[NVBUF_MEM_UNIFIED],在我修改pipeline中该插件的默认值之后成功map并且完成对frame data的读写操作。
作为一名基于GStreamer框架进行AI算法应用开发的工程师最常遇到的问题之一就是如何将AI算法的输出可视化,DeepStream提供了一个用于OSD的插件nvdsosd,具体使用可以参考我写的使用文档:nvdsosd。
使用nvdsosd进行OSD的限制在于它的输入是RGBA格式,而编解码器通常只能处理YUV(NV12)格式的数据,因此在使用时需要使用nvvideoconvert进行格式转换,这其实会带来一部分性能损耗,因此还可以直接在解码出的NV12图像上OSD,只不过目前并没有什么图形库提供了全面的在NV12上进行OSD的API,我所实现的draw-rectangle-on-YUV库只支持绘制line和rectangle。
想要在解码出的NV12上进行OSD需要解决一下几个问题:
如何map: gst_buffer_map->NvBufSurface
GstBuffer的stride->NvBufSurface->surfacelistp[frame_meta->batch_id].pitch
NvBufSurface: how to extrace data form a NvBufSurface structure
DeepStream提供了现成的API用来访问其封装的NvBufSurface数据:
具体可以参考我的开源仓库reid中的spl中test程序的实现。
除了上述两种方法,还可以直接使用OpenCV在RGBA上进行绘制。
DeepStream get-dsexample plugin: 插件源码位于/opt/nvidia/deepstream/deepstream-6.0/sources/gst-plugins/gst-dsexample