硬件 & 版本依据:ARM A53,ARMv7,ARM GCC 11.1.0
前言
这里针对前面的总结进行对应的分析,主要从三个点出发:
内存管理与锁通过 uftrace 对应用层调用进行追踪,统计对应的耗时与堆栈
分析
内存频率
通过统计视频帧达到的时间和数据大小可以得到相关数据分布:
不难看出大部分数据分布在 3800 - 25000 bytes 之间,而且与数据达到时间并无太大关系,这里将数据大小总体分为三段:,,。这里通过统计计算后得出:
- 占比为 96%,
- 均值为 60921,占比 2%
- 占比为 2%,
总体来看,内存拷贝的大小大部分位于 之间,因此可以考虑将这个区间作为基本的数据测试大小,可以考虑采用并行的方式对数据进行复制
内存管理
Buffer 的空间申请位于分发者内部,当数据到达时申请空间然后通过指针计数的方式传递到对应的 FrameQueue
集合中。这就导致分发者每次有数据到达时必须先申请内存再进行复制,每次进行申请空间属实没这个必要,在空间分配激烈的情况下,反而会导致不好的效果。虽然 Buffer 管理的方式是通过智能指针的引用计数进行管理,但是前面提到了 FrameBufferMemory
是在析构函数中进行释放的,因此当引用计数为 0 时,需要在订阅者内花费额外的开销来进行释放,因此总体的流程如下(以最后一个订阅者为例,同时也是最坏情况):
flowchart LR
subgraph "Dispatcher"
DataArrived(数据达到)
DataAllocated(申请空间)
ChooseSet([选择 FrameQueue 集合])
DataArrived --> DataAllocated --> ChooseSet
end
FrameQueue
subgraph "Module Worker (Last)"
WaitForData(等待数据达到)
HandleData(等待数据达到)
FreeData(数据释放)
WaitForData --> HandleData --> FreeData
end
FrameQueue --> WaitForData
ChooseSet --> FrameQueue
这里通过 uftrace 分别抓取分发者线程的构造对应 Buffer 与释放的时间(分配大小为数据中位数 60921),构造如下:
# DURATION TID FUNCTION 24.000 us [ 895] | malloc(); [ 895] | std::make_shared() { [ 895] | std::allocator::allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::new_allocator(); 2.500 us [ 895] | } /* std::allocator::allocator */ 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.333 us [ 895] | std::forward(); [ 895] | std::allocate_shared() { 0.333 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); [ 895] | std::shared_ptr::shared_ptr() { 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.333 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); [ 895] | std::__shared_ptr::__shared_ptr() { 0.500 us [ 895] | std::forward(); 0.334 us [ 895] | std::forward(); 0.334 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); [ 895] | std::__shared_count::__shared_count() { [ 895] | std::allocator::allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::new_allocator(); 2.500 us [ 895] | } /* std::allocator::allocator */ [ 895] | std::__allocate_guarded() { [ 895] | std::allocator_traits::allocate() { [ 895] | __gnu_cxx::new_allocator::allocate() { 0.500 us [ 895] | __gnu_cxx::new_allocator::_M_max_size(); 1.667 us [ 895] | operator new(); 6.334 us [ 895] | } /* __gnu_cxx::new_allocator::allocate */ 8.333 us [ 895] | } /* std::allocator_traits::allocate */ [ 895] | std::__allocated_ptr::__allocated_ptr() { 0.500 us [ 895] | std::__addressof(); 2.500 us [ 895] | } /* std::__allocated_ptr::__allocated_ptr */ 13.333 us [ 895] | } /* std::__allocate_guarded */ [ 895] | std::__allocated_ptr::get() { 0.500 us [ 895] | std::__to_address(); 2.333 us [ 895] | } /* std::__allocated_ptr::get */ [ 895] | std::allocator::allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::new_allocator(); 2.333 us [ 895] | } /* std::allocator::allocator */ 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | operator new(); [ 895] | std::_Sp_counted_ptr_inplace::_Sp_counted_ptr_inplace() { 0.500 us [ 895] | std::_Sp_counted_base::_Sp_counted_base(); [ 895] | std::allocator::allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::new_allocator(); 2.333 us [ 895] | } /* std::allocator::allocator */ [ 895] | std::_Sp_counted_ptr_inplace::_Impl::_Impl() { [ 895] | std::_Sp_ebo_helper::_Sp_ebo_helper() { [ 895] | std::allocator::allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::new_allocator(); 2.500 us [ 895] | } /* std::allocator::allocator */ 4.000 us [ 895] | } /* std::_Sp_ebo_helper::_Sp_ebo_helper */ 5.667 us [ 895] | } /* std::_Sp_counted_ptr_inplace::_Impl::_Impl */ [ 895] | std::allocator::~allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::~new_allocator(); 2.333 us [ 895] | } /* std::allocator::~allocator */ [ 895] | std::_Sp_counted_ptr_inplace::_M_ptr() { [ 895] | __gnu_cxx::__aligned_buffer::_M_ptr() { 0.500 us [ 895] | __gnu_cxx::__aligned_buffer::_M_addr(); 2.833 us [ 895] | } /* __gnu_cxx::__aligned_buffer::_M_ptr */ 4.334 us [ 895] | } /* std::_Sp_counted_ptr_inplace::_M_ptr */ 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.334 us [ 895] | std::forward(); [ 895] | std::allocator_traits::construct() { 0.333 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.334 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); [ 895] | __gnu_cxx::new_allocator::construct() { 0.500 us [ 895] | std::forward(); 0.500 us [ 895] | std::forward(); 0.333 us [ 895] | std::forward(); 0.334 us [ 895] | std::forward(); 0.333 us [ 895] | std::forward(); 0.500 us [ 895] | operator new(); [ 895] | FrameBufferMemory::FrameBufferMemory() { 0.333 us [ 895] | FrameBuffer::FrameBuffer(); [ 895] | std::mutex::mutex() { 0.500 us [ 895] | std::__mutex_base::__mutex_base(); 2.000 us [ 895] | } /* std::mutex::mutex */ 4.666 us [ 895] | } /* FrameBufferMemory::FrameBufferMemory */ 13.833 us [ 895] | } /* __gnu_cxx::new_allocator::construct */ 22.167 us [ 895] | } /* std::allocator_traits::construct */ 67.667 us [ 895] | } /* std::_Sp_counted_ptr_inplace::_Sp_counted_ptr_inplace */ [ 895] | std::allocator::~allocator() { 0.333 us [ 895] | __gnu_cxx::new_allocator::~new_allocator(); 1.667 us [ 895] | } /* std::allocator::~allocator */ 0.500 us [ 895] | std::__allocated_ptr::operator=(); [ 895] | std::_Sp_counted_ptr_inplace::_M_ptr() { [ 895] | __gnu_cxx::__aligned_buffer::_M_ptr() { 0.500 us [ 895] | __gnu_cxx::__aligned_buffer::_M_addr(); 1.834 us [ 895] | } /* __gnu_cxx::__aligned_buffer::_M_ptr */ 3.000 us [ 895] | } /* std::_Sp_counted_ptr_inplace::_M_ptr */ 0.500 us [ 895] | std::__allocated_ptr::~__allocated_ptr(); [ 895] | std::allocator::~allocator() { 0.333 us [ 895] | __gnu_cxx::new_allocator::~new_allocator(); 1.834 us [ 895] | } /* std::allocator::~allocator */ 115.167 us [ 895] | } /* std::__shared_count::__shared_count */ 0.333 us [ 895] | std::__shared_ptr::_M_enable_shared_from_this_with(); 138.334 us [ 895] | } /* std::__shared_ptr::__shared_ptr */ 147.833 us [ 895] | } /* std::shared_ptr::shared_ptr */ 157.333 us [ 895] | } /* std::allocate_shared */ [ 895] | std::allocator::~allocator() { 0.500 us [ 895] | __gnu_cxx::new_allocator::~new_allocator(); 1.834 us [ 895] | } /* std::allocator::~allocator */ 172.667 us [ 895] | } /* std::make_shared */
|
析构释放如下:
# DURATION TID FUNCTION [ 885] | std::shared_ptr::~shared_ptr() { [ 885] | std::__shared_ptr::~__shared_ptr() { [ 885] | std::__shared_count::~__shared_count() { [ 885] | std::_Sp_counted_base::_M_release() { [ 885] | __gnu_cxx::__exchange_and_add_dispatch() { 0.500 us [ 885] | __gnu_cxx::__is_single_threaded(); 0.333 us [ 885] | __gnu_cxx::__exchange_and_add(); 3.500 us [ 885] | } /* __gnu_cxx::__exchange_and_add_dispatch */ [ 885] | std::_Sp_counted_ptr_inplace::_M_dispose() { [ 885] | std::_Sp_counted_ptr_inplace::_Impl::_M_alloc() { 0.333 us [ 885] | std::_Sp_ebo_helper::_S_get(); 2.167 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_Impl::_M_alloc */ [ 885] | std::_Sp_counted_ptr_inplace::_M_ptr() { [ 885] | __gnu_cxx::__aligned_buffer::_M_ptr() { 0.334 us [ 885] | __gnu_cxx::__aligned_buffer::_M_addr(); 2.167 us [ 885] | } /* __gnu_cxx::__aligned_buffer::_M_ptr */ 3.334 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_M_ptr */ [ 885] | std::allocator_traits::destroy() { [ 885] | __gnu_cxx::new_allocator::destroy() { [ 885] | FrameBufferMemory::~FrameBufferMemory() { 1.667 us [ 885] | free(); 0.500 us [ 885] | FrameBuffer::~FrameBuffer(); 5.500 us [ 885] | } /* FrameBufferMemory::~FrameBufferMemory */ 7.334 us [ 885] | } /* __gnu_cxx::new_allocator::destroy */ 8.500 us [ 885] | } /* std::allocator_traits::destroy */ 17.166 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_M_dispose */ [ 885] | __gnu_cxx::__exchange_and_add_dispatch() { 0.333 us [ 885] | __gnu_cxx::__is_single_threaded(); 0.333 us [ 885] | __gnu_cxx::__exchange_and_add(); 3.167 us [ 885] | } /* __gnu_cxx::__exchange_and_add_dispatch */ [ 885] | std::_Sp_counted_ptr_inplace::_M_destroy() { [ 885] | std::_Sp_counted_ptr_inplace::_Impl::_M_alloc() { 0.334 us [ 885] | std::_Sp_ebo_helper::_S_get(); 1.834 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_Impl::_M_alloc */ [ 885] | std::allocator::allocator() { 0.334 us [ 885] | __gnu_cxx::new_allocator::new_allocator(); 2.000 us [ 885] | } /* std::allocator::allocator */ [ 885] | std::__allocated_ptr::__allocated_ptr() { 0.333 us [ 885] | std::__addressof(); 2.000 us [ 885] | } /* std::__allocated_ptr::__allocated_ptr */ [ 885] | std::_Sp_counted_ptr_inplace::~_Sp_counted_ptr_inplace() { [ 885] | std::_Sp_counted_ptr_inplace::_Impl::~_Impl() { [ 885] | std::_Sp_ebo_helper::~_Sp_ebo_helper() { [ 885] | std::allocator::~allocator() { 0.500 us [ 885] | __gnu_cxx::new_allocator::~new_allocator(); 2.500 us [ 885] | } /* std::allocator::~allocator */ 3.833 us [ 885] | } /* std::_Sp_ebo_helper::~_Sp_ebo_helper */ 5.167 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_Impl::~_Impl */ 0.333 us [ 885] | std::_Sp_counted_base::~_Sp_counted_base(); 8.000 us [ 885] | } /* std::_Sp_counted_ptr_inplace::~_Sp_counted_ptr_inplace */ [ 885] | std::__allocated_ptr::~__allocated_ptr() { [ 885] | std::allocator_traits::deallocate() { [ 885] | __gnu_cxx::new_allocator::deallocate() { 1.333 us [ 885] | operator delete(); 3.334 us [ 885] | } /* __gnu_cxx::new_allocator::deallocate */ 4.500 us [ 885] | } /* std::allocator_traits::deallocate */ 5.833 us [ 885] | } /* std::__allocated_ptr::~__allocated_ptr */ [ 885] | std::allocator::~allocator() { 0.334 us [ 885] | __gnu_cxx::new_allocator::~new_allocator(); 1.833 us [ 885] | } /* std::allocator::~allocator */ 27.333 us [ 885] | } /* std::_Sp_counted_ptr_inplace::_M_destroy */ 55.500 us [ 885] | } /* std::_Sp_counted_base::_M_release */ 57.000 us [ 885] | } /* std::__shared_count::~__shared_count */ 58.000 us [ 885] | } /* std::__shared_ptr::~__shared_ptr */ 59.333 us [ 885] | } /* std::shared_ptr::~shared_ptr */
|
不难看到,malloc
也是耗时点之一,同时智能指针的申请对象的空间也在一定程度上消耗了时间。在最后一个订阅者通过析构释放归还给内存空间,虽然内部调用 free
的时间很短暂,但是完整走完智能指针的析构函数会占很大的时间
锁
这里主要针对 FrameBufferMemory::readData()
锁进行分析,分别记录有无锁下的耗时:
# 有锁 # DURATION TID FUNCTION [ 676] | FrameBufferMemory::readData() { 90.833 us [ 676] | memcpy(); 117.833 us [ 676] | } /* FrameBufferMemory::readData */ | [ 675] | FrameBufferMemory::readData() { 72.333 us [ 675] | memcpy(); 100.333 us [ 675] | } /* FrameBufferMemory::readData */ | [ 674] | FrameBufferMemory::readData() { 76.166 us [ 674] | memcpy(); 94.334 us [ 674] | } /* FrameBufferMemory::readData */ | [ 677] | FrameBufferMemory::readData() { 16.834 us [ 677] | memcpy(); 45.167 us [ 677] | } /* FrameBufferMemory::readData */ | [ 673] | FrameBufferMemory::readData() { 73.833 us [ 673] | memcpy(); 93.166 us [ 673] | } /* FrameBufferMemory::readData */ | [ 672] | FrameBufferMemory::readData() { 71.334 us [ 672] | memcpy(); 90.834 us [ 672] | } /* FrameBufferMemory::readData */ | [ 671] | FrameBufferMemory::readData() { 77.834 us [ 671] | memcpy(); 96.334 us [ 671] | } /* FrameBufferMemory::readData */ | [ 678] | FrameBufferMemory::readData() { 17.500 us [ 678] | memcpy(); 149.000 us [ 678] | } /* FrameBufferMemory::readData */ | [ 670] | FrameBufferMemory::readData() { 88.166 us [ 670] | memcpy(); 108.000 us [ 670] | } /* FrameBufferMemory::readData */ | [ 669] | FrameBufferMemory::readData() { 103.667 us [ 669] | memcpy(); 119.000 us [ 669] | } /* FrameBufferMemory::readData */
|
# 无锁 # DURATION TID FUNCTION [ 693] | FrameBufferMemory::readData() { 93.333 us [ 693] | memcpy(); 106.834 us [ 693] | } /* FrameBufferMemory::readData */ | [ 692] | FrameBufferMemory::readData() { 72.667 us [ 692] | memcpy(); 74.666 us [ 692] | } /* FrameBufferMemory::readData */ | [ 691] | FrameBufferMemory::readData() { 69.834 us [ 691] | memcpy(); 71.666 us [ 691] | } /* FrameBufferMemory::readData */ | [ 694] | FrameBufferMemory::readData() { 17.000 us [ 694] | memcpy(); 18.833 us [ 694] | } /* FrameBufferMemory::readData */ | [ 690] | FrameBufferMemory::readData() { 68.500 us [ 690] | memcpy(); 70.500 us [ 690] | } /* FrameBufferMemory::readData */ | [ 695] | FrameBufferMemory::readData() { 17.167 us [ 695] | memcpy(); 19.167 us [ 695] | } /* FrameBufferMemory::readData */ | [ 689] | FrameBufferMemory::readData() { 72.500 us [ 689] | memcpy(); 74.333 us [ 689] | } /* FrameBufferMemory::readData */ | [ 687] | FrameBufferMemory::readData() { 69.667 us [ 687] | memcpy(); 71.500 us [ 687] | } /* FrameBufferMemory::readData */ | [ 686] | FrameBufferMemory::readData() { 95.500 us [ 686] | memcpy(); 97.500 us [ 686] | } /* FrameBufferMemory::readData */ | [ 685] | FrameBufferMemory::readData() { 72.833 us [ 685] | memcpy(); 74.834 us [ 685] | } /* FrameBufferMemory::readData */
|
计算得出两者的操作耗时均值:
|
memcpy 平均耗时 |
总体平均耗时 |
有锁操作 |
|
|
无锁操作 |
|
|
不难看出,无锁的情况下确实更加优异,总体耗时下降了 40% ,由于锁的竞争减小,使得多线程任务可以同时进行,由 A53 为 4 核,在不考虑内核调度的情况下,那么 3 个订阅者线程和 1 分发者线程得以同时进行
数据分发
分发者的工作线程采用的 std::set
的数组对 FrameQueue
进行托管,虽然工程中数组的长度并没有很多,但是订阅者的数量就不一定会少于数组的长度,因此分发者分配数据到对应的订阅者最坏的情况为 。考虑到 FrameBufferMemory
通过智能指针来实现了引用计数以及 FrameQueue
锁的竞争相对较少,因此分发者最主要的耗时存在于循环遍历找到对应的 FrameQueue
以及等待其将数据存放/覆盖到缓冲队列中
总结
通过上面的大致分析,可以得出一些改进的方向:
- 采用并行操作进行复制:通过 SIMD 指令进行并行复制,进一步减低复制所带来的耗时
- 改进引用计数:不通过智能指针来实现引用计数,从而避免内存的释放出现在订阅者的工作线程中
- 改进内存管理:分发者工作线程应无需关注内存的申请,同时既然是限定长度的缓存,那么可以考虑使用定长的内存池进行管理,从而避免频繁申请释放内存所带来的性能消耗
- 改进架构:原本的设计是由硬件实现的类进行订阅者的注册与分发,导致工作线程需要依次抢夺对应集合中所有队列的锁,这样导致了分发者必须进行等待,增大了分发者线程的负载