ncnn源码阅读(三)----数据结构Mat

这篇具有很好参考价值的文章主要介绍了ncnn源码阅读(三)----数据结构Mat。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

数据结构Mat

个人认为一个框架中的比较核心的两个点,一个是数据结构,一个任务调度。两者之间其实是相互独立的,数据结构为调度过程中的数据流动提供了一个载体。在ncnn中的数据结构是Mat,类似于OpenCV中的Mat,但又增加了一些属于它本身的一些特性,在这个部分学习一下ncnn的Mat

成员变量

成员变量有7个

成员变量 含义
int dims; 表示当前数据是几维数据
float* data; Mat中数据的指针
int* refcount; 实现引用计数功能,实现类似智能指针的自动管理内存功能
int w; 第一个维度
int h; 第二个维度
int c; 第三个维度
size_t cstep; 表示在channel维的步长
这几个变量的,具体含义在成员函数中体会的会更加深刻。

成员方法

构造函数

构造函数主要完成对成员变量进行初始化

1、普通构造函数
  • 空构造函数:所有的成员赋值0
  • 一维数据构造:数据为1维,会分配相应的内存空间;
inline Mat::Mat(int _w)
    : dims(0), data(0), refcount(0)
{
    create(_w);
}
inline void Mat::create(int _w)
{
    release();
    dims = 1;
    w = _w;
    h = 1;
    c = 1;

    cstep = w;
	
    if (cstep * c > 0)
    {
        size_t totalsize = cstep * c * sizeof(float);
        data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount));
        refcount = (int*)(((unsigned char*)data) + totalsize);
        *refcount = 1;
    }
}
  • 二维数据构造:数据为2维,会分配相应的内存空间;
inline Mat::Mat(int _w, int _h)
    : dims(0), data(0), refcount(0)
{
    create(_w, _h);
}

inline void Mat::create(int _w, int _h)
{
    release();
    dims = 2;
    w = _w;
    h = _h;
    c = 1;
    cstep = w * h;

    if (cstep * c > 0)
    {
        size_t totalsize = cstep *c * sizeof(float);
        data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount));
        refcount = (int*)(((unsigned char*)data) + totalsize);
        *refcount = 1;
    }
}
  • 三维数据构造:数据为3维,会分配相应的内存空间;
inline Mat::Mat(int _w, int _h, int _c)
    : dims(0), data(0), refcount(0)
{
    create(_w, _h, _c);
}
inline void Mat::create(int _w, int _h, int _c)
{
    release();
    dims = 3;
    w = _w;
    h = _h;
    c = _c;
    cstep = alignSize(w * h * sizeof(float), 16) >> 2;

    if (cstep * c > 0)
    {
        size_t totalsize = cstep * c * sizeof(float);
        data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount));
        refcount = (int*)(((unsigned char*)data) + totalsize);
        *refcount = 1;
    }
}

上面的三个构造函数内部,都会调用到create函数,而在create内部又会调用release函数,这是因为create的调用者不局限在构造函数中,其它的调用在后面再细说。
上面构造中还调用了两个其他的函数:

static inline size_t alignSize(size_t sz, int n)
{
    return (sz + n-1) & -n;
}

static inline void* fastMalloc(size_t size)
{
    unsigned char* udata = (unsigned char*)malloc(size + sizeof(void*) + MALLOC_ALIGN);
    if (!udata)
        return 0;
    unsigned char** adata = alignPtr((unsigned char**)udata + 1, MALLOC_ALIGN);
    adata[-1] = udata;
    return adata;
}
static inline void fastFree(void* ptr)
{
    if (ptr)
    {
        unsigned char* udata = ((unsigned char**)ptr)[-1];
        free(udata);
    }
}

alignSize函数主要用来做对齐,,举例:

原始大小 8对齐 16对齐
3 8 16
7 8 16
9 16 16
17 24 32
25 32 32
后面的fastMalloc和fastFree是对malloc和free的封装,其中也使用的内存对齐相关的内容
针对指针的对齐方法:
template<typename _Tp> static inline _Tp* alignPtr(_Tp* ptr, int n=(int)sizeof(_Tp))
{
    return (_Tp*)(((size_t)ptr + n-1) & -n);
}

fastMalloc方法中,针对要分配的空间会多分配,对齐大小+一个指针的大小
多分配的一个指针大小,将存储由malloc分配出来的原始指针用以内存的释放使用;
多分配的对齐大小将用来做内存对齐填充;
具体的操作在下面这两句代码:

 unsigned char** adata = alignPtr((unsigned char**)udata + 1, MALLOC_ALIGN);
 adata[-1] = udata;

udata为malloc得到的原始指针。先将原始指针偏移一个指针大小,然后再做内存对齐,偏移的一个指针大小用来存储原始指针。
ncnn源码阅读(三)----数据结构Mat,ncnn,数据结构
fastFree时,需要将使用的ptr指针,向后偏移一个指针大小,然后调用系统的free进行释放

2、外部数据指针构造函数

只能接受外部的float*类型的数据指针进行构造。同样的也是分为一维、二维和三维数据的构造

inline Mat::Mat(int _w, float* _data)
    : dims(1), data(_data), refcount(0)
{
    w = _w;
    h = 1;
    c = 1;

    cstep = w;
}

inline Mat::Mat(int _w, int _h, float* _data)
    : dims(2), data(_data), refcount(0)
{
    w = _w;
    h = _h;
    c = 1;

    cstep = w * h;
}

inline Mat::Mat(int _w, int _h, int _c, float* _data)
    : dims(3), data(_data), refcount(0)
{
    w = _w;
    h = _h;
    c = _c;

    cstep = alignSize(w * h * sizeof(float), 16) >> 2;
}
3、拷贝构造函数和opertor =
  • 拷贝构造函数,只是一个浅拷贝,两个Mat公用一块数据内存,引用计数加一
inline Mat::Mat(const Mat& m)
    : dims(m.dims), data(m.data), refcount(m.refcount)
{
    if (refcount)
        NCNN_XADD(refcount, 1);

    w = m.w;
    h = m.h;
    c = m.c;

    cstep = m.cstep;
}
  • operaotr=:也会导致引用计数加1,然后两个Mat也是共用一块数据内存,与构造不同的是,需要将左边Mat的进行release
inline Mat& Mat::operator=(const Mat& m)
{
    if (this == &m)
        return *this;

    if (m.refcount)
        NCNN_XADD(m.refcount, 1);

    release();

    dims = m.dims;
    data = m.data;
    refcount = m.refcount;

    w = m.w;
    h = m.h;
    c = m.c;

    cstep = m.cstep;

    return *this;
}

深拷贝函数

inline Mat Mat::clone() const
{
    if (empty())
        return Mat();

    Mat m;
    if (dims == 1)
        m.create(w);
    else if (dims == 2)
        m.create(w, h);
    else if (dims == 3)
        m.create(w, h, c);

    if (total() > 0)
    {
        memcpy(m.data, data, total() * sizeof(float));
    }

    return m;
}

类型转换

inline Mat::operator float*()
{
    return data;
}

inline Mat::operator const float*() const
{
    return data;
}

引用计数的实现

实现:一段堆空间存储引用计数,当发生拷贝时,引用计数加1,销毁的时候,引用计数减1,如果引用计数为0,则释放资源。
在ncnn中的mat的实现方式:

  • 在申请数据空间,多一段空间用来存储引用计数
 data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount));
 refcount = (int*)(((unsigned char*)data) + totalsize);

当发生拷贝构造、赋值时,需要对引用计数加一
当析构的时候需要对引用计数减一,并判断引用计数,决定是否释放资源

inline void Mat::release()
{
    if (refcount && NCNN_XADD(refcount, -1) == 1)
        fastFree(data);

    dims = 0;
    data = 0;

    w = 0;
    h = 0;
    c = 0;

    cstep = 0;

    refcount = 0;
}

其他数据操作函数

设计方法:在Mat.h中声明包含了上述的函数和一些数据操作函数,但具体的实现,可以在两个cpp中进行分类实现文章来源地址https://www.toymoban.com/news/detail-548886.html

到了这里,关于ncnn源码阅读(三)----数据结构Mat的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构与集合源码

    目录 数据结构概述 概述: 数据结构的研究对象(三个): 逻辑结构: 物理结构(存储结构) 运算(相关算法操作) 常见存储结构: 树的理解  经典二叉树 BST(二叉排序树) 平衡二叉树(AVL) 红黑树(RBT)(复杂,不多讲,了解) List源码解析   ArraysList在JDK7和JDK8中的

    2024年04月13日
    浏览(33)
  • 【数据结构】堆详解!(图解+源码)

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 数据结构解析 🌄 莫道桑榆晚,为霞尚满天! 堆是一种基本而强大的数据结构。本文将深入探讨堆的概念、原理以及实现。 普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存

    2024年02月05日
    浏览(51)
  • 【数据结构】 顺序表详解!(源码+解析)

    🎥 屿小夏 : 个人主页 🔥个人专栏 : 数据结构解析 🌄 莫道桑榆晚,为霞尚满天! ​ 什么是数据结构?我们为什么要学数据结构?数据结构中的顺序表长什么样子?它是怎么运用? ​ 本期我们将对这些一一讲解,彻底明白数据结构的重要性,以及顺序表是一种什么的数据

    2024年02月06日
    浏览(44)
  • 【数据结构】栈算法(算法原理+源码)

    博主介绍:✌全网粉丝喜爱+、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦! 🍅附上相关C语言版源码讲解🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找

    2024年01月23日
    浏览(61)
  • 数据结构之队列的实现(附源码)

    目录 一、队列的概念及结构 二、队列的实现  拓展:循环队列 三、初学的队列以及栈和队列结合的练习题 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾 出队

    2024年02月09日
    浏览(29)
  • Redis源码篇 - inset数据结构

    Redis底层原理篇

    2024年02月16日
    浏览(55)
  • 数据结构——七大排序[源码+动图+性能测试]

    本章代码gitee仓库:排序 我们日常打扑克牌,摸牌,让后将牌按顺序插入好,这其实就是插入排序的过程,打小插入排序的思想就植入我们的脑海 第一张牌不用管,直接拿在手里,之后的牌按照大小再一个一个插入即可 直接插入排序特性: 越接近有序,效率越高(不用那么多

    2024年02月09日
    浏览(48)
  • 数据结构入门指南:单链表(附源码)

    目录 前言 尾删 头删 查找 位置前插入  位置后插入  位置删除  位置后删除  链表销毁 总结         前边关于链表的基础如果已经理解透彻,那么接下来就是对链表各功能的实现,同时也希望大家能把这部分内容熟练于心,这部分内容对有关链表部分的刷题很有帮助。废话

    2024年02月14日
    浏览(53)
  • 数据结构之栈的实现(附源码)

    目录 一、栈的概念及结构 ​二、栈的实现 三、初学栈的练习题 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插

    2024年02月07日
    浏览(46)
  • 【数据结构与算法】堆的实现(附源码)

      目录 一.堆的概念及结构 二.接口实现 A.初始化  Heapinit   销毁 Heapdestroy B.插入 Heappush 向上调整  AdjustUp 1.Heappush 2.AdjustUp C.删除 Heappop  向下调整  AdjustDown D.堆的判空  Heapempty  堆顶数据  Heaptop  堆的大小  Heapsize 三.源码 Heap.h Heap.c test.c 1.概念      如果有一个关键码的

    2024年02月01日
    浏览(84)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包