• [技术干货] 0x03 LiteOS_Lab仓库组件详解--Driver (下)
    设备开启函数我们在使用某个设备之前,如果该设备具有open和close的能力,就要先去打开该设备,用完之后关闭该设备。/******************************************************************************* function     :open the device with the specified name parameters   : instruction  :if first open the device,then will call the init function if exsited *******************************************************************************/ /* 传入需要开启的设备名称,以及一个flag,该flag的值取决于我们定义设备的open函数指针时定义的 */ los_dev_t  los_dev_open  (const char *name,unsigned int flag) {     bool_t opret = true;           //operate result     struct driv_cb *driv = NULL;   //the driver attached     struct dev_cb  *dev = NULL;    //the device opened     if ( NULL == name )     {         goto EXIT_PARAERR;     }     dev = osal_malloc(sizeof(struct dev_cb));    //为设备结构体指针分配内存,用于存储需要操作设备的信息     if (NULL == dev)     {         goto EXIT_MEMERR;     }     (void) memset(dev,0,sizeof(struct dev_cb));     opret = osal_mutex_lock(s_los_driv_module.lock);    //加锁,防止多个进程同时访问     if(false == opret)     {         goto EXIT_MUTEXERR;     }     driv = __driv_match(name);                        //通过设备名称寻找设备对应的结构体     if(NULL == driv)     {         goto EXIT_DRIVERR;     }     //WE DON'T CARE TOO MUCH ABOUT THE RD AND WR FLAG,MAY BE TODO IT IN THE NEXT VERSION HERE     if((O_EXCL & (unsigned int )driv->flagmask) && (NULL != driv->devlst))     {         goto EXIT_EXCLERR;     }     if((0 == (driv->drivstatus & cn_driv_status_initialized)) && \    //如果设备未初始化但是有初始化函数         (NULL != driv->op->init))     {         opret = driv->op->init(driv->pri);                    //在这里调用其初始化函数         if(false == opret)         {             driv->errno = en_dev_err_init;             goto EXIT_INITERR;         }         driv->drivstatus |= cn_driv_status_initialized;     }     if(NULL != driv->op->open)                            //如果该设备没有open能力或者说是open函数     {         opret = driv->op->open(driv->pri,flag);         if(false == opret)         {             driv->errno = en_dev_err_open;             goto EXIT_OPENERR;         }     }     //reach here means all the initialize is ok     //add the dev to the list of the drive and  attach the driv to the device     driv->opencounter++;        //开启的设备总数加一     dev->nxt = driv->devlst;    //将这个设备的结构体指针挂载到开启的设备链表中     driv->devlst = dev;     dev->driv = driv;     dev->openflag =  flag;     (void) osal_mutex_unlock(s_los_driv_module.lock);     return dev; EXIT_OPENERR: EXIT_INITERR: EXIT_EXCLERR: EXIT_DRIVERR:     (void) osal_mutex_unlock(s_los_driv_module.lock); EXIT_MUTEXERR:     osal_free(dev);     dev = NULL; EXIT_MEMERR: EXIT_PARAERR:     return dev;    //返回一个操作句柄,我们读写数据就是通过这个操作句柄来操作 }设备关闭函数在使用完设备之后,我们可以调用设备关闭函数。/******************************************************************************* function     :close the device of opened parameters   :handle returned by open instruction  : *******************************************************************************/ bool_t  los_dev_close  (los_dev_t dev)    //传入操作句柄 {     bool_t ret = false;     struct dev_cb *devcb = NULL;     struct dev_cb *tmp = NULL;     struct driv_cb *driv = NULL;     if ( NULL == dev )     {         goto EXIT_PARAERR;     }     devcb = dev;     if(false == osal_mutex_lock(s_los_driv_module.lock))    //加锁,防止多进程同时访问出错     {         goto EXIT_MUTEXERR;     }     //deattach the dev from the driv     driv = devcb->driv;    //将设备结构体地址赋值给driv     if(NULL == driv)     {         goto EXIT_DRIVERR;     }     if(devcb == driv->devlst)    //如果需要关闭的设备是链表中第一个     {         driv->devlst = devcb->nxt;     }     else                        //如果需要关闭的设备不是链表中第一个     {         tmp = driv->devlst;         while(NULL != tmp)         {             if(tmp->nxt == devcb)             {                 tmp->nxt = devcb->nxt;                 break;             }             tmp = tmp->nxt;         }         if(NULL == tmp)         {             goto EXIT_DETACHERR;         }     }     if(NULL != driv->op->close)    //调用设备的关闭函数     {         driv->op->close(driv->pri);     }     if((NULL == driv->devlst) && (NULL != driv->op->deinit))     {         driv->op->deinit(driv->pri);        //调用设备的去除初始化函数         driv->drivstatus &= (~cn_driv_status_initialized);    //将设备状态置为未初始化     }     osal_free(dev);        //释放设备结构体所占用内存     driv->opencounter--;    //开启的设备总数减一     (void) osal_mutex_unlock(s_los_driv_module.lock);    //解锁     ret = true; EXIT_DETACHERR: EXIT_DRIVERR:     (void) osal_mutex_unlock(s_los_driv_module.lock); EXIT_MUTEXERR: EXIT_PARAERR:     return ret; }向设备中读取数据/******************************************************************************* function     :use this function to read data from the device parameters   :dev,returned by the los_dev_open function               offet:from where to read,only used for storage device               buf:used to storage the data               len:the length of the buf               timeout:the waittime if no data current instruction  :how many data has been read to the buf *******************************************************************************/ //传入操作句柄、需要读取的数据在设备中的偏移量,读到指针对应的某个内存空间,读取的长度,读取超时时间 ssize_t   los_dev_read  (los_dev_t dev,size_t offset, void *buf,size_t len,uint32_t timeout) {     ssize_t ret = 0;     struct dev_cb  *devcb;     struct driv_cb *drivcb;     if((NULL != dev)&&(NULL != buf)&&(0 != len))     {         devcb = dev;         if((0 == ((uint32_t)devcb->openflag & O_WRONLY)))    //检查设备属性,是否可读         {             drivcb = devcb->driv;             if((NULL != drivcb->op)&&(NULL != drivcb->op->read))             {                 ret = drivcb->op->read( drivcb->pri,offset,buf,len,timeout);    //调用设备的读函数,传入参数                 if(ret > 0)                 {                     drivcb->total_read += ret;                 }             }         }     }     return ret;     //返回读出数据的个数 }向设备中写入数据/******************************************************************************* function     :use this function to write data to the device parameters   :dev,returned by the los_dev_open function               offset: from where to write,only used for storage device               buf:the data to be written               len:the length of the buf               timeout:the waittime if no data current instruction  :how many data has been written to the device *******************************************************************************/ //传入操作句柄,需要写入的位置,需要被写入数据的地址,写入长度,超时时间 ssize_t los_dev_write (los_dev_t dev,size_t offset,const void *buf,size_t len, uint32_t timeout) {     ssize_t ret = 0;     struct dev_cb  *devcb;     struct driv_cb *drivcb;     if((NULL != dev) && (NULL != buf) && (len != 0))     {         devcb = dev;         if((((uint32_t)devcb->openflag) & O_WRONLY) || (((uint32_t)devcb->openflag) & O_RDWR))    //查看设备的属性是否支持写入         {             drivcb = devcb->driv;             if((NULL != drivcb->op)&&(NULL != drivcb->op->write))             {                 ret = drivcb->op->write( drivcb->pri,offset,buf,len,timeout);    //调用设备的写入函数,并将参数传入                 if(ret > 0)                 {                     drivcb->total_write += ret;                 }             }         }     }     return ret;    //返回写入数据的个数 }
  • [大赛专区] 赛道一 综合排名第六 算法小白思路分享
    关于赛道一思路分享大家好,我是thinq_dl,目前大三,大学期间从电气相关专业转至本校计算机学院软件工程专业。本次赛道一的比赛综合排名第六(91.403),同其他获奖选手不同,我对一些经典算法没有太多的了解,这也是我第一次参加算法相关的比赛。以下是我以一个算法小白的身份对本次赛题的两种理解:初始算法思路:初始算法思路比较简单,分为以下三种情况:在仓库的情况Ø 如果派送员在仓库,直接选择去距离仓库最近的一个点;在目标捐赠点/目标小区的情况Ø 直接去最近一个小区;Ø 先去n个捐赠点,再去某个小区;Ø 先回仓库,在去某个小区;注:算法最终从以上三中路径中选择局部范围内步数最优的决策。中途遇到意外出现的小区和捐赠点的情况Ø 重新进行扫描此算法最终成绩:95.5左右最终算法思路:全局搜索由于本次赛题中给出的待捐赠小区只有5个,数量较少,在满足时间复杂度的前提下,穷举当前全局最优策略是很容易想到的。个人猜测比赛获奖的前8名选手或多或少都采取了暴力搜索的方案,只是细节部分可能存在差异。前期对赛题的优化根据比赛规则,地图中有仓库、捐赠小区、待捐赠小区这三种场景,且仓库的库存为无限,此时我们可以在此对赛题设定进行初步的优化:将仓库看成一个库存固定且足够的捐赠小区,在本次赛题中我将仓库设置成了一个库存初始值为2000的捐赠小区。这样做的好处是当前的地图场景就由三种(仓库、捐赠小区、待捐赠小区)变为了两种(捐赠小区、待捐赠小区),在后期的操作中能够对算法的简化起到一定作用。算法核心:决策下一步去哪里如开始的介绍一样,由于地图中至多只有5个小区,此外就是一些捐赠点(根据上面对赛题的改造,已经没有了仓库的概念)。因此我采取了穷举的方案寻找当前最优的派送路径。Ø 当前点-->某小区-->...>某小区(结束)Ø 当前点-->捐赠点-->某小区-->...>某小区(结束)Ø 当前点-->捐赠点-->捐赠点-->某小区-->...>某小区(结束)Ø 当前点-->经过n个捐赠点-->某小区-->...>某小区(结束)穷举过程中应该注意的点由于穷举所带来的时间复杂度是非常高,在实际生活中我们很难通过穷举去解决一些复杂问题,即便在本次赛题的12*12的地图中,穷举策略也不能写得过于复杂,不然限定时间内一定无法跑完1000幅地图。其他考虑的问题Ø 如果穷举出来当前的最优路径有多个,应当如何抉择?遇到这样的问题我的做法是看移动的第一步的步数,谁最少就选谁,如果还是一样,保持开始的路径不变。---这种处理思路显然不是最好的解决方案,后期测试过程中对综合成绩也只有微弱的提升。Ø 每次出现随机捐赠小区,就对当前地图进行新的全局扫描。算法的缺陷Ø 时间复杂度过高,难以用于实际,如果想利用此算法,必须对搜索深度进行一定限制,并由此找到次优解。Ø 算法每次寻求的都是当前的最优解,对于当前最优解有多个的处理方式不够合理。Ø 算法寻求的当前最优解不代表他就是地图的最优解,没有对之后将要出现的捐赠点进行合理的预判。因此算法搜索出来的路径很多时候应该是次优解。 以上即是我对本次赛题的一个方法分享,之前看了比赛中大佬们的算法分享视频,收获颇多,同时也看到了自己在算法这一块知识储备的欠缺,总之感谢华为云提供的这样一个平台让我有机会和众多大佬交流学习。最后祝华为公司越办越好! 
  • [技术干货] 关于赛道一思路分享
    关于赛道一思路分享大家好,我是thinq_dl,目前大三,大学期间从电气相关专业转至本校计算机学院软件工程专业。本次赛道一的比赛综合排名第六(91.403),同其他获奖选手不同,我对一些经典算法没有太多的了解,这也是我第一次参加算法相关的比赛。以下是我以一个算法小白的身份对本次赛题的两种理解:初始算法思路:初始算法思路比较简单,分为以下三种情况:在仓库的情况Ø 如果派送员在仓库,直接选择去距离仓库最近的一个点;在目标捐赠点/目标小区的情况Ø 直接去最近一个小区;Ø 先去n个捐赠点,再去某个小区;Ø 先回仓库,在去某个小区;注:算法最终从以上三中路径中选择局部范围内步数最优的决策。中途遇到意外出现的小区和捐赠点的情况Ø 重新进行扫描此算法最终成绩:95.5左右最终算法思路:全局搜索由于本次赛题中给出的待捐赠小区只有5个,数量较少,在满足时间复杂度的前提下,穷举当前全局最优策略是很容易想到的。个人猜测比赛获奖的前8名选手或多或少都采取了暴力搜索的方案,只是细节部分可能存在差异。前期对赛题的优化根据比赛规则,地图中有仓库、捐赠小区、待捐赠小区这三种场景,且仓库的库存为无限,此时我们可以在此对赛题设定进行初步的优化:将仓库看成一个库存固定且足够的捐赠小区,在本次赛题中我将仓库设置成了一个库存初始值为2000的捐赠小区。这样做的好处是当前的地图场景就由三种(仓库、捐赠小区、待捐赠小区)变为了两种(捐赠小区、待捐赠小区),在后期的操作中能够对算法的简化起到一定作用。算法核心:决策下一步去哪里如开始的介绍一样,由于地图中至多只有5个小区,此外就是一些捐赠点(根据上面对赛题的改造,已经没有了仓库的概念)。因此我采取了穷举的方案寻找当前最优的派送路径。Ø 当前点-->某小区-->...>某小区(结束)Ø 当前点-->捐赠点-->某小区-->...>某小区(结束)Ø 当前点-->捐赠点-->捐赠点-->某小区-->...>某小区(结束)Ø 当前点-->经过n个捐赠点-->某小区-->...>某小区(结束)穷举过程中应该注意的点由于穷举所带来的时间复杂度是非常高,在实际生活中我们很难通过穷举去解决一些复杂问题,即便在本次赛题的12*12的地图中,穷举策略也不能写得过于复杂,不然限定时间内一定无法跑完1000幅地图。其他考虑的问题Ø 如果穷举出来当前的最优路径有多个,应当如何抉择?遇到这样的问题我的做法是看移动的第一步的步数,谁最少就选谁,如果还是一样,保持开始的路径不变。---这种处理思路显然不是最好的解决方案,后期测试过程中对综合成绩也只有微弱的提升。Ø 每次出现随机捐赠小区,就对当前地图进行新的全局扫描。算法的缺陷Ø 时间复杂度过高,难以用于实际,如果想利用此算法,必须对搜索深度进行一定限制,并由此找到次优解。Ø 算法每次寻求的都是当前的最优解,对于当前最优解有多个的处理方式不够合理。Ø 算法寻求的当前最优解不代表他就是地图的最优解,没有对之后将要出现的捐赠点进行合理的预判。因此算法搜索出来的路径很多时候应该是次优解。 以上即是我对本次赛题的一个方法分享,之前看了比赛中大佬们的算法分享视频,收获颇多,同时也看到了自己在算法这一块知识储备的欠缺,总之感谢华为云提供的这样一个平台让我有机会和众多大佬交流学习。最后祝华为公司越办越好! 
  • [大赛专区] 口罩大作战(赛道一)陪跑经历和算法思路代码(C++)分享
      因为一直在电脑旁上网课并且一直拿柜子当椅子坐导致腰肌劳损,所以直到现在才来发分享帖,下面开始正题。  本来早就在学校官方网站上就看到了该比赛,因为课程大作业的原因所以一直推到4月28日才正式开始报名编写代码~  一开始看到题目我首先想到的是TSP(旅行商)问题,就是多次全局搜索取次优解,想到之前编写过用蚁群算法编写过TSP代码(想着这下可以直接搬过来用了),但看到测试用例说要1000幅地图在5分钟内跑完,我开始犹豫了,想起之前跑蚁群的时候一幅地图就要搜索几千次才能得出一个次优解,想着这1000幅地图有点够呛,但自始至终都没注意一幅地图的大小限定(12*12),我脑内一直想的是有在任意大小的地图内N个口罩需求区,1个随机仓库,N个随机口罩捐赠区的配送问题(这也是我一直的毛病看到一个问题就想实现一个通用性比较强的解法,结果最后是试用在用例上效果并不好~~~~)于是我放弃了蚁群搜索算法和Dijkstra这些耗时的搜索算法,使用构造函数的方法(然后陷入了大坑无法自拔~~)第一天(4月28日)——完成了算法思路构建和数据结构编写(代码第一版)1、首先是既然是构造函数法肯定是要有权重值,题目中说基于距离最短,所以将距离因素权重设为0.8,而口罩数量的因素权重设为0.2,然后是每个建筑的位置和口罩数,而且在配送的过程中要时刻删除相关的坐标,所以使用链表数据结构来存储位置信息和口罩数信息,然后是建筑物信息,题目的建筑物可以分为两类,一类是仓库类(捐献小区也可看作是仓库)这类结构体链表中的口罩数是正数;一类是待配送类,这类结构体链表中的口罩数是负数,定义最大口罩数为10000;#define MAX 10000 struct coordinate { int x;         //坐标位置       int y; int cost; struct coordinate *next = NULL; }; class building { public: int sum;                    //目前建筑数量 struct coordinate *last; struct coordinate *first; building() { sum = 0; first =  NULL; last =  NULL; } };2、接着是开始main函数编写while循环监控是否有命令输入,若命令是S则是仓库命令,调用add函数向warehouse建筑类内添加仓库;若命令是R则是小区命令,若num>0则是捐赠小区,向warehouse建筑类内添加,若num<0则是需求小区,向resident建筑类内添加,并且记录总共的需求数need_sum;如果命令为G,则是移动命令,开始执行action函数,若需求小区数sum为0,结束;int main() { char op; int x, y; int num; //int count1 = 0; int Delivery_x, Delivery_y, Delivery = 100; int need_sum = 0; //int total = 0; struct coordinate *pre =  NULL; building warehouse = building(); building resident = building(); while (cin >> op) { if (op == 'S') { cin >> x >> y; add(&warehouse, x, y); if (warehouse.first->next == warehouse.last) { Delivery_x = warehouse.last->x; Delivery_y = warehouse.last->y; } } else if (op == 'R') { cin >> x >> y >> num; if (num > 0) { pre = add(&warehouse, x, y, num); if (x == Delivery_x && y == Delivery_y) { change(&warehouse, &resident, &Delivery_x, &Delivery_y, &Delivery, &need_sum, 2, warehouse.last, pre); } } else { need_sum += abs(num); add(&resident, x, y, num); } } if (op == 'G') { action(&warehouse, &resident, &Delivery_x, &Delivery_y, &Delivery, &need_sum); if (resident.sum == 0) { break; } } } }3、main函数涉及相关函数(1)添加建筑函数add这个就没什么多说的了,建立新的位置信息类结构体,在连到链表上面;(不要在意其中的cout,这是运行测试模块能不能正常运行用的)struct coordinate * establish(int x, int y, int cost = MAX) { struct coordinate *p = new struct coordinate; p->x = x; p->y = y; p->cost = cost; return p; } void action(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need, int flat, struct coordinate *n, struct coordinate *n_pre); struct coordinate* add(class building *build, int x, int y, int num = MAX) { struct coordinate* pre =  NULL; if (build->first ==  NULL) { build->first = new struct coordinate; build->last = establish(x, y, num); (*build).first->next = build->last; //cout<<(*build).first->next->x<<(*build).first->next->y<<(*build).first->next->cost<<endl; //cout<<(*build).last->x<<(*build).last->y<<(*build).last->cost<<endl; } else { pre = build->last; (*build).last->next = establish(x, y, num); build->last = (*build).last->next; //cout<<(*build).first->next->x<<(*build).first->next->y<<(*build).first->next->cost<<endl; //cout<<(*build).last->x<<(*build).last->y<<(*build).last->cost<<endl; } build->sum++; return pre; }(2)移动函数action1、如果当前配送员口罩数大于等于需要口罩数的平均数的一半(为什么是大于等于需要口罩数的平均数的一半,因为假设极端情况所有的需求小区都是200,那么派送员最大是100,要满足配送需求就是大于等于此时需要口罩数的平均数的一半即100),为什么还有(*sum)!=0,这是后期发现的bug之后再说,我们现在按时间顺序来说,满足条件后说明可以配送      当此时发现配送员发现持有口罩数大于等于需求区口罩数时(*sum >= abs(p->cost)),计算该需求区的权重意愿值权重函数构造思路------前半部分:float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta距离越短越好后半部分:float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha  单位距离配送越多越好     当此时发现配送员发现持有口罩数小于需求区口罩数时(*sum < abs(p->cost))时,使用另一个构造函数计算该需求区的权重意愿值权重函数构造思路------前半部分:同上  后半部分:a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha  因为此时所持口罩数大于均值,优先配送需求大的小区,减小需求大的小区从仓库到该区域的来回步数;2、如果当前配送员口罩数大于所有的需求,若有则判断权重意愿值3、否则如果有当前配送员口罩数大于需求小区的,则使用权重函数判断此时权重float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost);  单位距离配送越多越好4、若有配送目标则a!=-1,则移动到该点,移动玩之后判断是否移动到了其他需求小区的位置或捐献小区的位置,若有进入change函数改变对应的口罩数;5、否则跳出前往获取口罩Supplementary(wa, re, d_x, d_y, sum, need);void action(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need) { float alpha = 0.2, beta = 0.8, a = -1; int best_dis = MAX; int b; struct coordinate *p = re->first->next; struct coordinate *p1 = re->first->next; struct coordinate *pre = re->first; struct coordinate *best= NULL; struct coordinate *best_pre = NULL; while (p !=  NULL) { if (*sum >= (*need) / (re->sum * 2)&&(*sum)!=0) {                 if (*sum >= abs(p->cost)) { a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else { b=abs(p->cost) - (*sum);                 b=(b==0)?1:b; a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } } else if(*sum >= (*need)){ a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost)*alpha; //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else if (*sum >= abs(p->cost)) { a = float(abs(*d_x - p->x) + abs(*d_y - p->y)) / abs(p->cost); //cout<<"a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(re->sum*2)<<endl; if (a < best_dis) { best_dis = a; best_pre = pre; best = p; } } else{             break; } pre = p; p = p->next; } //cout<<"目前位置"<<*d_x<<','<<*d_y<<"要去的位置:"<<best->x<<','<<best->y<<endl; if (a != -1) { if (*d_x > best->x) { cout << 'N' << endl; (*d_x)--; } else if (*d_x < best->x) { cout << 'S' << endl; (*d_x)++; } else if (*d_y < best->y) { cout << 'E' << endl; (*d_y)++; } else if (*d_y > best->y) { cout << 'W' << endl; (*d_y)--; } if (*d_x == best->x&&*d_y == best->y) change(wa, re, d_x, d_y, sum, need, 1, best, best_pre); else { int flat = 0; struct coordinate *w = (*wa).first->next; struct coordinate *w_pre = (*wa).first; while (w !=  NULL) { if (abs(*d_x - w->x) == 0 && abs(*d_y - w->y) == 0) { flat = 2; break; } w_pre = w; w = w->next; } struct coordinate *r = (*re).first->next; struct coordinate *r_pre = (*re).first; while (r !=  NULL) { if (abs(*d_x - r->x) == 0 && abs(*d_y - r->y) == 0) { flat = 1; break; } r_pre = r; r = r->next; } if (flat == 2) change(wa, re, d_x, d_y, sum, need, flat, w, w_pre); else change(wa, re, d_x, d_y, sum, need, flat, r, r_pre); } } else { Supplementary(wa, re, d_x, d_y, sum, need); } }(3)获取口罩Supplementary函数1、判断若此时口罩数+捐献小区数大于均值则判断该小区权重意愿值构造函数思路:float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha     距离至上,后半部分表示获取口罩的损失率越小越好2、得到位置之后移动,同移动函数action后半部分,先移动后判断是否移动到其他区域;void Supplementary(class building *wa, class building *re, int *d_x, int *d_y, int *sum, int *need) { float a = -1; int best_dis = MAX; int b; struct coordinate *p = (*wa).first->next; struct coordinate *pre = wa->first; struct coordinate *best= (*wa).first->next; struct coordinate *best_pre =wa->first; while (p !=  NULL) { if ((*sum + p->cost) > (*need) / (re->sum * 2)) { b = ((100 - (*sum)) > p->cost) ? (p->cost-(*sum)) : (100-(*sum)); b =(b==0)?1:b; a = float(abs(*d_x - p->x) + abs(*d_y - p->y))*beta + float(abs(*d_x - p->x) + abs(*d_y - p->y)) /b*alpha; //cout<<"获取口罩a="<<a<<"SUM="<<*sum<<"AVG"<<*need<<"RE"<<(wa->sum*2)<<endl; if (a < best_dis) { best_dis = a; best = p; best_pre = pre; } } pre = p; p = p->next; } }(4)口罩数变动函数change这个没什么多说的,flat是区分送口罩还是取口罩,送口罩:足够就送完删除该小区,不够就持有清0  取口罩:足够就取满,不够就取完删除该捐献小区;void change(class building *re, int *d_x, int *d_y, int *sum, int *need, int flat, struct coordinate *best, struct coordinate *best_pre) { if (flat == 1) { if (*d_x == best->x&&*d_y == best->y) { if ((-best->cost) > *sum) { best->cost = best->cost + *sum; (*need) -= *sum; *sum = 0; } else { (*sum) = best->cost + (*sum); (*need) += best->cost; best->cost = 0; //cout<<best->cost<<"剩余"<<(*sum)<<endl; best_pre->next = best->next; re->sum--; if (re->last == best) re->last = best_pre; delete best; if (re->sum == 0) return; } } } if (flat == 2) { if (*d_x == best->x&&*d_y == best->y) { if ((best->cost + (*sum) > 100)) { best->cost = best->cost - (100 - *sum); *sum = 100; } else { (*sum) = best->cost + (*sum); best->cost = 0; best_pre->next = best->next; //cout<<"这里"<<best->next->x<<best->next->y<<best->next->cost<<endl; warehouse.sum--; if (warehouse.last == best) warehouse.last = best_pre; delete best; } } } }然后后面几天就是debug的时间了第二天(4月29日)——debug半天才知道测试不支持C++11 用null替换nullptr,删除了万能开头头文件;第三天(4月30日)1、通过测试一堆数据发现有的时候G命令没有及时移动命令,发现action函数中*sum >= (*need) / (re->sum * 2)这个判断条件若need==1,re->sum==1,sum==0时会卡住,所以加上sum!=0的条件2、添加了捐献小区落脸上的条件判断,第一次提交成功也是最成功的一次,之后不管如何修改都平均步数会更低;第四天(5月1日)——通过分析几天的每日PK例子发现代码在一个区域配送时,有时会跑到其他区域配送增加重复的步数,猜想分区域配送可能会好一点,分为右上配送,左上配送,左下配送,右下配送,但捐赠小区不分区照常获取最优口罩,让最远的待配送区域留到最后配送,进行逆时针配送,防止出现重复步数;例如:分区后的main函数,代码中涉及到的移动判断位置都要加入分区域判断(篇幅情况不过多赘述原代码)building resident_onright = building(); building resident_belowright=building(); building resident_onleft=building(); building resident_belowleft=building(); building warehouse = building(); int need_sum_onright = 0,need_sum_onleft=0,need_sum_belowleft=0,need_sum_belowright=0;if (op == 'R') { cin >> x >> y >> num; if (num > 0) { pre = add(&warehouse, x, y, num); if (x == Delivery_x && y == Delivery_y) {                     if(x<=wa_x&&y>wa_y) change(&resident_onright, &Delivery_x, &Delivery_y, &Delivery, &need_sum_onright, 2, warehouse.last, pre);                 else if(x<wa_x&&y<=wa_y)                     change(&resident_onleft, &Delivery_x, &Delivery_y, &Delivery, &need_sum_onleft, 2, warehouse.last, pre);                 else if(x>=wa_x&&y<wa_y)                     change(&resident_belowleft, &Delivery_x, &Delivery_y, &Delivery, &need_sum_belowleft, 2, warehouse.last, pre);                 else if(x>wa_x&&y>=wa_y)                     change(&resident_belowright, &Delivery_x, &Delivery_y, &Delivery, &need_sum_belowright, 2, warehouse.last, pre); } } else { if(x<=wa_x&&y>wa_y){                 need_sum_onright += abs(num);                 if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=1;                 } add(&resident_onright, x, y, num); }else if(x<wa_x&&y<=wa_y){     need_sum_onleft += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=2;                 } add(&resident_onleft, x, y, num); }else if(x>=wa_x&&y<wa_y){     need_sum_belowleft += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=3;                 } add(&resident_belowleft, x, y, num); }else if(x>wa_x&&y>=wa_y){     need_sum_belowright += abs(num);     if(abs(x-wa_x)+abs(y-wa_y)>further_dis){                 further_dis=abs(x-wa_x)+abs(y-wa_y);                 position=4;                 } add(&resident_belowright, x, y, num); } } }最后debug提交发现并没有什么用~~~直接崩溃第五天(5月2日)没办法生病了,划水度过~~~~~~~~~眼睁睁的看着排名一直下跌从20到37~~~~~~~~~~~~~~~最后附上垃圾代码,以上!
  • [大赛专区] 赛道一思路分享
    大家好,我是hw32983598,分享一下算法设计思路算法设计思路1:贪心策略如何找下一个目标        只看距离最近的需求小区,然后看如何前往这个需求小区,从下列3种路径中选择一个:                ➀直接去需求小区                ➁先回仓库取货再去需求小区                ➂先去捐赠小区取货再去需求小区        对路径的要求:如果需求小区剩余需求不超过100,则须一次配送完                                        如果需求小区剩余需求大于100,则配送后须让剩余需求小于100                                        即carry >= -a[x][y] % 100        找到满足上述条件的最短路,作为接下来要走的路径。何时找下一个目标        到达一个小区/仓库,或出现新捐献小区,则重新选择新的目标。平均步数:95~96步算法设计思路2:暴力搜索(DFS)+减枝策略        载货量为100     :只去需求小区        载货量为0          :只去仓库和捐献小区        载货量为1~99 :三者都可以去递归结束条件:任务完成、递归深度大于40(时间不够)、当前路径长度大于当前最短路长度(一定不是最优解)何时需要搜索        如果出现新捐献小区,则可能存在更优路径,需要重新搜索路径        如果发生“误撞”,也就是遇到了不该遇到的小区,原路经失效,也需要重新搜索路径时间复杂度问题        暴力搜索的时间复杂度过高,需要加以控制。        可以设置计数器,记录递归函数被调用的次数。        如果计数器大于50000000,则加大减枝力度(每次只考虑最近的捐赠小区);        如果计数器大于250000000,则放弃暴力搜索的方法,转而使用贪心策略。        最终运行时间约为4分10秒平均步数:90~91步算法设计思路2的优化路径长度相同的两条路径,可以随意选择吗?        不可以,因为以后出现新的捐献小区后,两条路经不一定都是最好的如何选择?        可以在搜索之前给需求小区排序,比如可以按距离仓库的距离排序,同时可以按照与地图边缘的距离排序“误撞”问题怎么办?        同时看先走x后走y的路径和先走y后走x的路径,从两者中选择更好的避免不必要的重新搜索        可以将重新搜索延迟到下次输入G的时候        发生“误撞”时,如果送货员的载货量没变,则不需要重新搜索因时间不够而改用贪心算法时,如果新的贪心路径比原先的暴力搜索的路径长,则仍使用原先的暴力搜索的路径最终平均步数:89.866有时候优化并不能得偿所愿,可能反而增加平均步数,需要使用git之类的代码管理工具由于有提交次数的限制,需要自己写测试工具:        生成随机地图、每走一步都输出当前坐标和载货量、计算平均步数
  • [技术干货] 0x01 LiteOS_Lab仓库组件详解--AT(上)
    摘要:本节以解析UART.c文件中的代码为主,为了后面讲解AT框架及Driver框架做铺垫。关于串口的背景知识:https://bbs.huaweicloud.com/forum/thread-53612-1-1.html由于该文件中设计到了ring_buffer也就是环形数据结构的操作,你可以先阅读该篇文章,理解环形数据结构:https://bbs.huaweicloud.com/forum/thread-55254-1-1.html本节的代码摘自该文件:https://github.com/LiteOS/LiteOS_Lab/blob/iot_link/targets/STM32L431_BearPi/uart_at/uart_at.c下图中红框的地方是本节讲解的重点:auti_cb结构体struct atio_cb {     unsigned short        w_next;    //the next position to be write     osal_semp_t           rcvsync;   //if a frame has been written to the ring, then active it     tag_ring_buffer_t     rcvring; /*tag_ring_buffer_t结构体 typedef struct     {     unsigned char   *buf;      ///< which means the buffer     int              buflen;   ///< which means the buffer limit     int              datalen;  ///< which means how many data in the buffer     int              dataoff;  ///< which means the valid data offset in the buffer     }tag_ring_buffer_t;     */     unsigned char         rcvbuf[CONFIG_UARTAT_RCVMAX];     unsigned char         rcvringmem[CN_RCVMEM_LEN];     //for the debug here     unsigned int          rframeover; //how many times the frame has been over the max length     unsigned int          rframedrop; //how many frame has been droped for memmory     unsigned int          sndlen;     //how many bytes has been sent     unsigned int          rcvlen;     //how many bytes has been received    用于在中断中存储接收到了多少字节的数据     unsigned int          sndframe;   //how many frame has been sent     unsigned int          rcvframe;   //how many frame has been received     unsigned int          rcvringrst; //how many times the receive ring has been reset }; static struct atio_cb   g_atio_cb;初始化串口/******************************************************************************* function     :use this function to initialize the uart parameters   : instruction  : *******************************************************************************/ bool_t uart_at_init(void *pri) {     //initialize the at controller     (void) memset(&g_atio_cb,0,sizeof(g_atio_cb));    //为g_atio_cb结构体分配内存     if(false == osal_semp_create(&g_atio_cb.rcvsync,CN_RCVMEM_LEN,0))    //创建一个信号量     {         printf("%s:semp create error\n\r",__FUNCTION__);            //如果创建失败直接退出该函数并打印报错信息         goto EXIT_SEMP;     }     ring_buffer_init(&g_atio_cb.rcvring,g_atio_cb.rcvringmem,CN_RCVMEM_LEN,0,0);      /* ring_buffer_init(&g_atio_cb.rcvring,g_atio_cb.rcvringmem,CN_RCVMEM_LEN,0,0);函数的定义 int ring_buffer_init(tag_ring_buffer_t *ring,unsigned char *buf, int buflen,int offset,int datalen)     {         int ret = -1;         if((NULL == ring))         {         return ret;         }         ring->buf     = buf;    //将rcvringmem的地址与tag_ring_buffer_t类型的rcvring结构体中的buf指针进行绑定     ring->buflen  = buflen; //设置长度为用户定义的CN_RCVMEM_LEN        ring->datalen = datalen;//设置为0         ring->dataoff = offset; //设置为0       ret = 0;         return ret;     }     */ /* 初始化串口硬件相关,波特率、数据位、停止位以及校验位 */     uart_at.Instance = s_pUSART;     uart_at.Init.BaudRate = CONFIG_UARTAT_BAUDRATE;     uart_at.Init.WordLength = UART_WORDLENGTH_8B;     uart_at.Init.StopBits = UART_STOPBITS_1;     uart_at.Init.Parity = UART_PARITY_NONE;     uart_at.Init.HwFlowCtl = UART_HWCONTROL_NONE;     uart_at.Init.Mode = UART_MODE_TX_RX;     uart_at.Init.OverSampling = UART_OVERSAMPLING_16;     if(HAL_UART_Init(&uart_at) != HAL_OK)     {         _Error_Handler(__FILE__, __LINE__);     }     __HAL_UART_CLEAR_FLAG(&uart_at,UART_FLAG_TC);    //清除标志位     LOS_HwiCreate(s_uwIRQn, 3, 0, atio_irq, 0);  //通过LiteOS接管中断的方式创建中断,发生相应中断跳转到atio_irq函数处理     __HAL_UART_ENABLE_IT(&uart_at, UART_IT_IDLE);   //开启空闲中断:可以用该中断判断是否收到一帧数据(注意这里是一帧,不是一字节)     __HAL_UART_ENABLE_IT(&uart_at, UART_IT_RXNE);   //开启接收中断     return true; EXIT_SEMP:     return false; } void uart_at_deinit(void *pri) {     __HAL_UART_DISABLE(&uart_at);     __HAL_UART_DISABLE_IT(&uart_at, UART_IT_IDLE);     __HAL_UART_DISABLE_IT(&uart_at, UART_IT_RXNE); }串口中断处理函数/******************************************************************************* function     :use this function to deal the uart interrupt parameters   : instruction  :we cached the data in the temp buffer,when the idle interrupt reached,               then we write the data and the length to the ring if the ring has enough               space *******************************************************************************/ /* 当产生串口中断就跳转到该函数进行处理 */ static void atio_irq(void) {     unsigned char  value;     unsigned short ringspace;     if(__HAL_UART_GET_FLAG(&uart_at, UART_FLAG_RXNE) != RESET)    //如果是接收中断,代表收到了一字节数据     {        value = (uint8_t)(uart_at.Instance->RDR & 0x00FF);    //将数据从寄存器中读到value变量中        g_atio_cb.rcvlen++;                                        //结构体中的接收长度+1        if(g_atio_cb.w_next < CONFIG_UARTAT_RCVMAX)            //w_next如果小于最大接收长度则正常,反之说明溢出        {            g_atio_cb.rcvbuf[g_atio_cb.w_next] = value;            g_atio_cb.w_next++;                            //w_next变量用于记录下一次收到一个字节的数据该写到rcvbuf数组中的哪个位置        }        else                                                    //溢出数据统计        {             g_atio_cb.rframeover++;                                }     }     else if (__HAL_UART_GET_FLAG(&uart_at,UART_FLAG_IDLE) != RESET)    //如果是空闲中断,代表一帧数据全部收到     {         __HAL_UART_CLEAR_IDLEFLAG(&uart_at);         ringspace = CN_RCVMEM_LEN - ring_buffer_datalen(&g_atio_cb.rcvring);    //计算环形结构体中的空闲空间大小         if(ringspace < g_atio_cb.w_next)  //not enough mem    //如果这帧数据的大小大于环形结构体中的空闲空间大小,丢弃数据         {             g_atio_cb.rframedrop++;         }         else                        //正常         {             //write data to the ring buffer:len+data format             ringspace = g_atio_cb.w_next;            //将存储这帧的数组“大小”(这是一个下标)写到ringspace变量中                          /* 先把这帧数据的大小写到结构体中,再把数据写到结构体中,最终格式|len|data| */             ring_buffer_write(&g_atio_cb.rcvring,(unsigned char *)&ringspace,sizeof(ringspace));             ring_buffer_write(&g_atio_cb.rcvring,g_atio_cb.rcvbuf,ringspace);                          (void) osal_semp_post(g_atio_cb.rcvsync);             g_atio_cb.rcvframe++;    //接收到的帧数+1         }         g_atio_cb.w_next=0; //write from the head    清空接收缓存区大小     }     else ///< clear the flags     {         __HAL_UART_CLEAR_PEFLAG(&uart_at);         __HAL_UART_CLEAR_FEFLAG(&uart_at);         __HAL_UART_CLEAR_NEFLAG(&uart_at);         __HAL_UART_CLEAR_OREFLAG(&uart_at);     } }串口发送函数/******************************************************************************* function     :use this function to send a frame to the uart parameters   : instruction  : *******************************************************************************/ static ssize_t uart_at_send(const char  *buf, size_t len,uint32_t timeout) {     HAL_UART_Transmit(&uart_at,(unsigned char *)buf,len,timeout);    //直接调用HAL库函数即可发送数据     g_atio_cb.sndlen += len;     g_atio_cb.sndframe ++;     return len; }串口接收函数/******************************************************************************* function     :use this function to read a frame from the uart parameters   : instruction  : *******************************************************************************/ /* 该函数本质上是去接收结构体(g_atio_cb.rcvsync)中读取数据 */ static ssize_t uart_at_receive(void *buf,size_t len,uint32_t timeout) {     unsigned short cpylen;     unsigned short framelen;     unsigned short readlen;     int32_t ret = 0;     unsigned int lock;     if(osal_semp_pend(g_atio_cb.rcvsync,timeout))     {         lock = LOS_IntLock();         readlen = sizeof(framelen);         cpylen = ring_buffer_read(&g_atio_cb.rcvring,(unsigned char *)&framelen,readlen);    //从g_atio_cb.rcvring结构体中读取“长度“数据         if(cpylen != readlen)    //说明结构体中的数据长度小于需要读取的数据长度,结构体中的数据有问题         {             ring_buffer_reset(&g_atio_cb.rcvring);  //bad ring format here 清空该结构体中的数据             g_atio_cb.rcvringrst++;         }         else         {             if(framelen > len)    //清空该结构体中的数据             {                 ring_buffer_reset(&g_atio_cb.rcvring);  //bad ring format here                 g_atio_cb.rcvringrst++;             }             else             {                 readlen = framelen;                 cpylen = ring_buffer_read(&g_atio_cb.rcvring,(unsigned char *)buf,readlen);//从g_atio_cb.rcvring结构体中读取“数据“数据                 if(cpylen != framelen)                 {                     ring_buffer_reset(&g_atio_cb.rcvring);  //bad ring format here                     g_atio_cb.rcvringrst++;                 }                 else                 {                     ret = cpylen;    //读取成功返回实际读到的数据大小                 }             }         }         LOS_IntRestore(lock);     }     return ret; }用于先driver层注册的函数/*  通过driver层屏蔽uatr层的底层差异,让driver层之上的层觉得无论什么单片机都是一样的方法操作,使用init、deinit 、read、write即可  *  */ //make it as the at device here static ssize_t  __at_read  (void *pri,size_t offset,void *buf,size_t len, uint32_t timeout) {     return uart_at_receive(buf,len, timeout); } static ssize_t  __at_write (void *pri, size_t offset,const void *buf,size_t len,uint32_t timeout) {     return uart_at_send(buf, len, timeout); } static const los_driv_op_t s_at_op = {         .init = uart_at_init,         .deinit = uart_at_deinit,         .read = __at_read,         .write = __at_write, }; OSDRIV_EXPORT(uart_at_driv,CONFIG_UARTAT_DEVNAME,(los_driv_op_t *)&s_at_op,NULL,O_RDWR);
  • [大赛专区] 赛道一解题思路分享
    程序设计思路看到这个问题之后,首先想到的是找最近的小区进行配送,但是每次最近的小区并不一定是配送最优的情况,并且在送完口罩之后还需要确定回仓库还是去捐赠小区取口罩,因此简单的策略是找不到最优解的,因此还需要对题目进行分析,建立适合计算机求解的模型。首先确定模型的状态。对于每次配送来说,地图中的状态变化有1.配送员的位置和携带口罩数量2.配送点的个数,位置和口罩数量3.捐赠点的个数,位置和口罩数量4.仓库的位置在确定了程序的状态之后要对程序的状态转移进行规划,程序的状态转移有以下几种形式:1.配送员走一步,即接收到G指令每次配送员行走一步,方向是东南西北四个方向。在行走之后,如果遇到配送的小区,将携带的口罩给配送的小区,遇到捐赠小区或者回到了仓库,配送员获得口罩。如果所有小需求都是0,结束这次配送。2.出现捐赠点,即接收到R指令在出现捐赠的小区之后,整个地图状态发生改变,对整个地图的状态进行更新。在对题目进行建模之后,确定这是一个从一个初始状态到终止状态寻找最短路径的一个模型,利用广度优先搜索来寻找整个模型的一个状态转移最小路径。程序的实现程序的输入输出部分,程序输入一行字符串,在读入字符串之后,对字符串进行分析,如果接受到G指令,进行输出,输出根据下一步前进方向,输出EWSN这4个方向。状态结构体的建立class status { public:   int sen_x[10], sen_y[10];   int don_x[140], don_y[140];   int sen_num[10], don_num[140];   int snum, dnum;   int pos_x, pos_y;   int carr; };这个结构体记录了这个地图的状态,仓库位置,配送点的位置,捐赠点的位置,配送点的个数,捐赠点的个数,配送点的需求量和捐赠点的捐赠量,还有配送员的位置和配送员的捐赠量。在这里为了写程序方便,认为仓库是0号捐赠点,class node { public:   set<node>::iterator father;   int carry, currX, currY;   short gmap[12][12];   int need;   int step;   int road_flag;   node();   node(set<node>::iterator parent);   node(set<node>::iterator parent, status sta);   ~node();   int print_map();   int update_state();   int nodecpy(set<node>::iterator right);   bool finish();   bool operator < (const node& left)const;   };Node结构体是存储搜索的状态的,由于捐赠点的数量和位置未定,使用和状态结构体一样的结构不方便查找,因此这里使用整个地图作为一个状态,加上配送员的位置,携带量,一共147个状态变量。这147个状态变量构成一个搜索节点。广度优先搜索的步骤是:首先选择一个初始的状态,然后从这个初始的状态进行扩展,扩展到周围的状态,将这个状态标记为已访问,然后从扩展的节点中再选择一个节点,将这个节点能够到达的状态,如果这个状态没有访问过,将这个状态加入搜索队列中,直到找到所有配送点状态都为0的状态。整个搜索的状态达到了147维,每个节点状态变量取值在0-200,0-100或者0-11之间,对每个状态开辟一个内存空间来存储是不现实的,已搜索的节点和准备搜索的节点只能放到一个数组中,由于要寻找最短路径,在准备访问的节点需要找到行走步数最少的点,在访问过的节点数组中需要查询是否已经访问过这个状态,因此每次查询和寻找都需要遍历一遍整个数组,这样是没办法在5分钟内查询完100幅图的。因此需要使用更加高效的数据结构。在C++中提供了set类型的数据结构,set底层是利用红黑树实现的,能够在的时间内进行查询和插入操作,相比数组的时间复杂度,运算速度提高了很多首先建立对两个set重载操作符,确定各自的比较顺序,这几定义openlist为将要访问的状态,closelist为访问过的状态。这里在node结构体定义的比较函数是closelist使用的,在结构体nodecmp中定义的是openlist中的比较函数,closelist中的比较函数比较所有状态变量。Openlist中的函数比较步数加上所有状态。road是zuihoset<node, nodecmp> openlist; set<node> closelist; list<node> road;下面是具体的寻找路径的算法顺序1.把起点加入 openlist 。2.重复如下过程:a)取出openlist中第一个元素即步数最小的状态,把它作为当前要处理的节点。b)把这个节点移到 closelist 。c)对当节点的状态出发,前往所有配送小区和捐赠小区(包括仓库) i.如果需要配送的小区和捐赠小区的需要或者捐赠的口罩数量为0,忽略该小区。 ii.如果携带口罩数量为0,忽略配送的小区 iii.如果携带口罩数量为100,忽略捐赠的小区(包括仓库) iv.如果从捐赠口罩的小区出发,不能回到仓库。d)模拟配送过程,更新抵达目的地之后的状态。e)如果抵达之后的状态在closelist中,忽略它,否则进行下面操作 i.如果抵达之后的状态不在closelist中,把它加入openlist。 ii.否则,忽略当前状态。f)停止,当 i.Openlist取出的节点已经达到了终止状态,即所有小区需要的口罩数量为0 ii.查找终点失败,即openlist是空的(这种情况实际中不存在) iii.当访问过的状态数量超过一定程度,即closelist中节点数量超过10万个3.保存路径。从终点开始,每个状态沿着父节点移动直至起点,这就是你的路径。这是一次寻找路径的算法啊,当第一次接收到行走的指令时,出现新的捐赠点的时候或者路径已经走完还没有到达结束状态的时候,重新寻找路径int search_road(struct status state) { node* head = NULL; set<node>::iterator res; node t1; openlist.clear(); closelist.clear(); road.clear(); head = new node(closelist.end(), state); head->step = 0; res = closelist.end(); openlist.insert(*head); while (openlist.size() != 0) { set<node, nodecmp>::iterator it; it = openlist.begin(); if (it->need == 0 || closelist.size() > listsize) { res = it; break; } pair <set<node>::iterator, bool> tmp; set<node>::iterator father; tmp = closelist.insert((*it)); father = tmp.first; int px = father->currX; int py = father->currY; for (int i = 0; i < state.snum; i++) { int dx = state.sen_x[i]; int dy = state.sen_y[i]; if (father->gmap[dx][dy] == 0 || father->carry == 0) { continue; } t1.nodecpy(father); t1.father = father; find_road(&t1, dx, dy); set<node>::iterator t2 = closelist.find(t1); if (t2 == closelist.end()) { openlist.insert(t1); } else { if (t2->step > t1.step) { closelist.erase(t2); openlist.insert(t1); closelist.insert(t1); } } } int max_dis = dist(father->currX, father->currY, state.don_x[0], state.don_y[0]); for (int i = 0; i < state.dnum; i++) { int dx = state.don_x[i]; int dy = state.don_y[i]; if (father->gmap[dx][dy] == 0 || father->carry == 100) { continue; } if (i == 0 && dmap[father->currX][father->currY] == 1) { continue; } int dis = dist(father->currX, father->currY, dx, dy); if (dis > max_dis && dmap[father->currX][father->currY] == 0) { continue; } t1.nodecpy(father); t1.father = father; find_road(&t1, dx, dy); set<node>::iterator t2 = closelist.find(t1); if (t2 == closelist.end()) { openlist.insert(t1); } else { if (t2->step > t1.step) { closelist.erase(t2); openlist.insert(t1); closelist.insert(t1); } } } openlist.erase(it); } while (res != closelist.end()) { road.push_back(*res); res = res->father; } road.pop_back(); delete(head); return 0; }程序的优化程序完成之后还需要对程序进行一些优化,主要的优化方面1.       Openlist排序的方式Openlist中的函数最开始是严格按照每个节点从开始到这里的步数排序的,这样可以保证在达到终止状态的时候不是是当前状态下最优的,但是有时候如果有心的捐赠点出现,可能之前找到的最优路径就不一定适合最新的状态,因此在选择状态的时候没必要严格按照步数来寻找,因此首先优化的就是openlist的比较函数,最近的比较函数利用到达当前状态的步数和当前所有配送点的需求的合来作为新的优先级函数。如果在步数和需求相通的情况下,优先返回配送员携带量比较大的,如果携带量相通,返回整个地图相比比较大的(这个是在比赛最后试出来的,效果非常好,也不明白什么道理,如果有人知道可以解释一下)。在更新比较函数之后,运行速度大大加快,closelist中的搜索节点个数放宽到了30万class nodecmp { public: bool operator()(const node& left, const node& right) const { int para = 12; if ((left.step * (left.step * para - left.need)) != (right.step * (right.step * para - right.need))) { return (left.step * (left.step * para - left.need)) < (right.step * (right.step * para - right.need)); } else if (left.carry != right.carry) { return left.carry > right.carry; } else if (dmap[left.currX][left.currY] != dmap[right.currX][right.currY]) { return dmap[left.currX][left.currY] > dmap[right.currX][right.currY]; } else if (left.currX != right.currX) { return left.currX < right.currX; } else if (left.currY != right.currY) { return left.currY < right.currY; } else { return memcmp(left.gmap, right.gmap, sizeof(left.gmap)) > 0; } } };1.       行走方向的选择我默认的行走方式是先沿着x方向行走,再沿着y方向行走,在行走过程中有可能遇到其他小区,这样会对到达之后的状态产生影响,在程序更改了行走方式,先沿着y方向走再沿着x走之后,程序的结果出现了变化,可以利用这个行走方式对程序进行再次的优化,在搜索的过程中,进行不同走法的选择,比较达到之后谁的状态更好,选择状态更好的走法。最终使用的是先走一步x方向,再走一步y方向int find_road(node* sta, int dx, int dy) { int ins; int t; int px = sta->currX; int py = sta->currY; if (px == dx) { if (dy > py) { for (int i = py; i < dy; i++) { sta->currY++; sta->step++; sta->update_state(); } } else { for (int i = py; i > dy; i--) { sta->currY --; sta->step++; sta->update_state(); } } } else if (py == dy) { if (dx > px) { for (int i = px; i < dx; i++) { sta->currX ++; sta->step++; sta->update_state(); } } else { for (int i = px; i > dx; i--) { sta->currX --; sta->step++; sta->update_state(); } } } else if (dx > px && dy > py) { int Dx = dx - px; int Dy = dy - py; if (Dx >= Dy) { t = Dx - Dy; for (int i = 0; i < t; i++) { sta->currX++; sta->step++; sta->update_state(); } for (int i = 0; i < Dy; i++) { sta->currX++; sta->step++; sta->update_state(); sta->currY++; sta->step++; sta->update_state(); } } else { t = Dy - Dx; for (int i = 0; i < t; i++) { sta->currY++; sta->step++; sta->update_state(); } for (int i = 0; i < Dx; i++) { sta->currX++; sta->step++; sta->update_state(); sta->currY++; sta->step++; sta->update_state(); } } } else if (dx > px && dy < py) { int Dx = dx - px; int Dy = py - dy; if (Dx >= Dy) { t = Dx - Dy; for (int i = 0; i < t; i++) { sta->currX++; sta->step++; sta->update_state(); } for (int i = 0; i < Dy; i++) { sta->currX++; sta->step++; sta->update_state(); sta->currY--; sta->step++; sta->update_state(); } } else { t = Dy - Dx; for (int i = 0; i < t; i++) { sta->currY--; sta->step++; sta->update_state(); } for (int i = 0; i < Dx; i++) { sta->currX++; sta->step++; sta->update_state(); sta->currY--; sta->step++; sta->update_state(); } } } else if (dx < px && dy > py) { int Dx = px - dx; int Dy = dy - py; if (Dx >= Dy) { t = Dx - Dy; for (int i = 0; i < t; i++) { sta->currX--; sta->step++; sta->update_state(); } for (int i = 0; i < Dy; i++) { sta->currX--; sta->step++; sta->update_state(); sta->currY++; sta->step++; sta->update_state(); } } else { t = Dy -  Dx; for (int i = 0; i < t; i++) { sta->currY++; sta->step++; sta->update_state(); } for (int i = 0; i < Dx; i++) { sta->currX--; sta->step++; sta->update_state(); sta->currY++; sta->step++; sta->update_state(); } } } else if (dx < px && dy < py) { int Dx = px - dx; int Dy = py - dy; if (Dx >= Dy) { t = Dx - Dy; for (int i = 0; i < t; i++) { sta->currX--; sta->step++; sta->update_state(); } for (int i = 0; i < Dy; i++) { sta->currX--; sta->step++; sta->update_state(); sta->currY--; sta->step++; sta->update_state(); } } else { t = Dy - Dx; for (int i = 0; i < t; i++) { sta->currY--; sta->step++; sta->update_state(); } ins = Dy / Dx; for (int i = 0; i < Dx; i++) { sta->currX--; sta->step++; sta->update_state(); sta->currY--; sta->step++; sta->update_state(); } } } return 0; }
  • [技术干货] OpenTSDB数据模型
    OpenTSDB数据模型                                                -------本文转载自华为内部论坛-作者:zhongchaoqiangOpenTSDB通过对HBase表数据(包括HBase RowKey、HBase Qualifier和HBase Value)的特殊设计,使得存储时序数据时可以更加高效和节省空间。在OpenTSDB的数据表中,存在两种类型的数据:指标(metrics)数据和注释(Annotation)数据。这两种类型的数据有不同的结构设计。【说明:如非特殊说明,文中字节的顺序是从高位到低位进行描述的,即从左到右进行描述。】1       指标(Metrics)数据1.1   HBase RowKey设计指标数据的HBase RowKey中包含主要组成部分为:盐值(Salt)、指标名、时间戳、标签名、标签值等部分。为了统一各个值的长度以及节省空间,对指标名、标签名和标签值都进行了统一编码。所以,在HBase RowKey中实际写入的指标ID、标签名ID和标签值ID。         HBase RowKey的数据模型如下图所示:说明:l  SALT:建议开启SALT功能,可以有效提高性能。SALT数据的长度根据设置的只觉得。如果SALT的值值少于256,那么只用一个字节表示即可;如果需要设置更大的SALT值,也会相应地占用更多的空间。l  Metric ID:指标名经过编码后,每个Metric ID的长度为三个字节。l  Timestamp:这里的时间戳是以小时为单位的。也就是真实时间戳(单位是秒)/3600的值。l  Tag Name ID & Tag Value ID:标签名和标签值经过编码后,每个Tag Name ID和Tag Value ID的长度都为三个字节。Tag Name ID和Tag Value ID必须成对出现,最少必须存在1对,最多存在8对。1.2   HBase Qualifier设计HBase Qualifier用于保存一个或多个DataPoint中的时间戳、数据类型、数据长度等信息。         由于时间戳中的小时级别的信息已经保存在HBase RowKey中了,所以HBase Qualifier只需要保存一个小时中具体某秒或某毫秒的信息即可,这样可以减少数据占用的空间。一个小时中的某一秒(少于3600)最多需要2个字节即可表示,而某一毫秒(少于3600000)最多需要4个字节才可以表示。为了节省空间,OpenTSDB没有使用统一的长度,而是对特定的类型采用特性的编码方法。         HBase Qualifer的数据模型主要分为如下三个情况:秒、毫秒、秒和毫秒混合。1.2.1  秒类型当OpenTSDB接收到一个新的DataPoint的时候,如果请求中的时间戳是秒,那么就会插入一个如下模型的数据。【注:判断请求中的时间戳为秒或毫秒的方法是根据时间戳的大小来判读的。如果时间戳的值的超过无符号整数的最大值(即4个字节的长度),那么该时间戳是毫秒,否则为秒。】说明:l  Value长度:Value的实际长度是HBase Qualifier的最后3个bit的值加1,即(qualifier & 0x07) + 1。表示该时间戳对应的值的字节数。所以,值的字节数的范围是1到8个字节。l  Value类型:Value的类型由HBase Qualifier的倒数第4个bit表示,即(qualifier & 0x08)。如果值为1,表示Value的类型为float;如果值为0,表示Value的类型为long。l  时间戳:时间戳的值由HBase Qualifier的第1到第12个bit表示,即(qualifier & 0xFFF0) >>>4。由于秒级的时间戳最大值不会大于3600,所以qualifer的第1个bit肯定不会是1。1.2.2  毫秒类型当OpenTSDB接收到一个新的DataPoint的时候,如果请求中的时间戳是毫秒,那么就会插入一个如下模型的数据。说明:l  Value长度:与秒类型相同。l  Value类型:与秒类型相同。l  时间戳: 时间戳的值由HBase Qualifier的第5到第26个bit表示,即(qualifier & 0x0FFFFFC0) >>>6。l  标志位:标志位由HBase Qualifier的前4个bit表示。当该HBase Qualifier表示毫秒级数据时,必须全为1,即(qualifier[0] & 0xF0) == 0xF0。l  第27到28个bit未使用。 1.2.3  混合类型当同一小时的数据发生合并后,就会形成混合类型的HBase Qualifier。合并的方法很简单,就是按照时间戳顺序进行排序后,从小到大依次拼接秒类型和毫秒类型的HBase Qualifier即可。说明:l  秒类型和毫秒类型的数量没有限制,并且可以任意组合。l  不存在相同时间戳的数据,包括秒和毫秒的表示方式。l  遍历混合类型中的所有DataPoint的方法是:Ø  从左到右,先判断前4个bit是否为0xF。Ø  如果是,则当前DataPoint是毫秒型的,读取4个字节形成一个毫秒型的DataPoint;Ø  如果否,则当前DataPoint是秒型的,读取2个字节形成一个秒型的DataPoint;Ø  以此迭代即可遍历所有的DataPoint。1.3   HBase Value设计HBase Value部分用于保存一个或多个DataPoint的具体某个时间戳对应的值。         由于在HBase Qualifier中已经保存了DataPoint Value的类型和DataPoint Value的长度,所以无论是秒级还是毫秒级的值,都可以用相同的表示方法,而混合类型就是多个DataPoint Value的拼接。         HBase Value按照长度可以分为如下几种类型:1.3.1  一字节         当DataPoint Value为long型,且大于等于-128(Byte.MIN_VALU),且少于或等于127(Byte.MAX_VALUE)的时候,使用1个字节存储。1.3.2  二字节当DataPoint Value为long型,且大于等于-32768(Short.MIN_VALU),且少于或等于32767(Short.MAX_VALUE)的时候,使用2个字节存储。1.3.3  四字节当DataPoint Value为long型,且大于等于0x80000000(Integer.MIN_VALU),且少于或等于0x7FFFFFFF(Integer.MAX_VALUE)的时候,使用4个字节存储。1.3.4  八字节         当DataPoint Value为long型,且不是上面三种类型的时候,使用8个字节存储。         当DataPoint Value为float型的时候,使用8个字节表示。1.3.5  多字节         按照时间戳的顺序,把多个Value拼接起来的数据模型如下:说明:l  每个格子表示一个DataPoint Value的值,这个DataPoint Value可能是1或2或4或8个字节。l  DataPoint Value的顺序与HBase Qualifier中时间戳的顺序一一对应。l  混合标志:最后1个字节表示是否存在秒级类型和毫秒级类型混合的情况。如果为0x01,表示是混合情况;否则不是。2       注释(Annotation)数据         注释数据主要用于描述某一个时间点发生的时间,注释数据的值是字符串类型,而不是指标数据中的数字类型。注意:n  注释数据只支持秒级时间戳的数据。n  注释数据不会合并。 2.1   HBase RowKey设计         HBase RowKey的数据模型如下图:说明:l  SALT/ Timestamp/Metric ID/ Tag Name ID /Tag Value ID的意义与指标数据的HBase RowKey设计中的意义相同。l  把[Metric ID/ Tag Name ID /Tag Value ID]部分统称为TSUID。实际上,读写注释数据的时候,需要指定的是TSUID,而不是像指标数据中那样分开指定的。2.2   HBase Qualifier设计         由于注释数据只支持秒级类型的数据,同时注释类型的数据不支持合并,所以HBase Qualifier的设计相对指标数据简单一些。         HBase Qualifier的模型如下:说明:l  与指标数据的HBase Qualifier相比,注释数据的HBase Qualifer的长度是3个字节。l  标志位:使用第1个字节表示,而且值必须为0x01。即(qualifier & 0xFF0000)>>>16 == 0x01。l  时间戳:使用第2到第3个字节表示。即时间戳的值为(qualifier & 0x00FFFF)。 2.3   HBase Value设计注释数据中的Value保存的是字符串类型的数据。整个HBase Value部分就是注释数据的值。3       APPEND模式         当OpenTSDB启动APPEND模式后,每个插入的新DataPoint,都会以HBase的append的方式写入。注意:n  由于使用了HBase的append的接口,每次插入一个新数据,都需要对同一小时的数据都执行一次读取和插入的操作;另外多线程对同一小时的数据进行更新的时候,是不能并发的。这样就大大限制了数据写入的速度了,一般情况下不建议使用这种模式。n  APEEND的数据其实就是合并过的数据了,所以不会参与OpenTSDB的Compaction流程。3.1   HBase RowKey设计APPEND模式的HBase RowKey设计与HBase RowKey设计是相同的。3.2   HBase Qualifier设计         APPEND模式下,由于同1小时的数据中不存在多个HBase Qualifier,所以只需要使用一个固定的Qualifier即可。说明:l  APPEND模式的HBase Qualifier使用3个字节表示。l  标志位: 由第1个字节表示,而且值必须为0x05。即(qualifier & 0xFF0000)>>>16 == 0x05。l  固定部分:由第2到第3个字节表示,这部分的值固定为0x0000。l  所以,APPEND模式的HBase Qualifier固定为0x050000。3.3   HBase Value设计APPEND模式下,HBase Value部分既要保存时间戳,数值类型和数值长度,也要保存对应的数值。         Value的数据结构如下:说明:l  每个Qualifier的格式与HBase Qualifier设计中的格式相同。l  每个Value的格式与HBase Qualifier设计中的格式相同。l  遍历Value中的所有DataPoint的方法是:Ø  从左到右,先判断前4个bit是否为0xF。Ø  如果是,则当前DataPoint是毫秒型的读取4个字节形成一个毫秒型的HBase Qualifier,从HBase Qualifier中获得Value的长度,然后再读取对应长度的字节数;Ø  如果否,则当前DataPoint是秒型的,读取2个字节形成一个秒型的HBase Qualifier,从HBase Qualifier中获得Value的长度,然后再读取对应长度的字节数;Ø  依此迭代即可遍历所有的DataPoint。 本文转载自华为内部论坛-作者:zhongchaoqiang
  • [技术干货] Taurus2.0垃圾回收Compactor优化方案
    1.   简介TaurusDB是一种基于MySQL的计算与存储分离架构的云原生数据库,一个集群中包含多个存储几点,每个存储节点包含多块磁盘,每块磁盘对应一个或者多个slicestore的内存逻辑结构来管理. 在taurus的slicestore中将数据划为多个slice进行管理,每个slice的大小是10G,Taurus架构图如下:TauruDB的存储层支持append-only写和随机读,最小数据存储逻辑单元为plog,每个slice中包含多个plog,默认每个plog的大小为64M。slice中的plog主要用来存放page。Plog中存放中不同版本的page,有些老page已经过期,需要删除;有些page是新page,需要被保留下来。Compator主要用来清理plog中过期的page,把一个plog上所有没有过期的page搬移到一个新plog,老的plog删除掉。Compactor的任务需要频繁访问内存中索引结构和读写plog中的page页,这两部分都属于整个系统中关键资源,锁竞争的压力比较大,会直接影响性能,所以compactor的优化方案主要围绕减少内存访问和磁盘IO,需要考虑以下几个点:A、 选取的清理的plog集最优问题,每次回收需要搬运有效page,搬运的有效page数越少,磁盘IO就越小。如何每次调度都选取到垃圾量最大的一批plog;B、 垃圾量是否分布均匀,如何让垃圾集中到一起,回收垃圾集中的plog,提高回收效率;C、 回收周期是固定的,怎么样保证在每个周期内,都能取到最优plog集;2.   关键点1-全局调度全局调度的方案增大plog垃圾量的排序范围,从slice的范围增大到slicestore的范围。因为考虑到后面需要针对单个磁盘进行“加速回收”,所以不扩大到一个存储节点的全局范围。全局调度方案按照slicestore来选取回收plog,先遍历所有的slicestore,然后在slicestore内部进行垃圾量排序,选取最多的若干个plog进行回收;优点:有效避免一个slicestore中由于垃圾分布不均匀引起的plog的无效搬运,减少对plog的读写产生的IO;缺点:排序范围增大后,排序算法会增加CPU的消耗;3.   关键点2-排序算法优化原始方案中在slice内部对plog按照垃圾量排序采用C++标准库排序(std::sort),该算法内部基于快速排序、插入排序和堆排序实现。原始方案中每个slice中最多存有160个plog,小数据量的排序或许效率影响不大,但是一个slicestore中存储成百上千的slice,排序算法的效率问题就值得关注。Taurus设计了一种topN算法,能够提升该场景下的效率。假设需要在n个元素中选取m个最大的元素,两种算法的时间复杂度和空间复杂度:C++标准库排序时间复杂度为O(nlogn),空间复杂度为O(nlogn);topN算法排序时间复杂度为O(nlogm),空间复杂度为O(1);Compactor应用场景中,n和m相差几个数量级,topN算法在时间和空间上都更具优势。         优点:减少时间复杂度和空间复杂度;4.   关键点3-调度数优化公共线程池分配给compactor的线程数是固定的,每个周期调度器生成一次任务。原始方案中compactor的每次生成的任务数由slice个数决定,会导致任务队列中的任务数过多或者过少。过多的话会失去时效性,也就是说plog的垃圾量会随着时间改变,如果在队列个等待执行的时间太长,可能就不是当前最高垃圾量的plog,同时一次挑选的plog个数太多,会增加算法的时间和空间复杂度;过少的话,compactor线程没有跑满,会导致垃圾回收速度降低。调度数优化也是基于全局调度优化,调度策略只需要保证在一个调度周期内,任务队列中任务数刚好满足compactor线程执行。假设有8个slicestore,分配了24个执行线程,每个线程每个调度周期完成一个plog的回收,则每个调度周期每个slicestore只需要生成3个任务。调度数优化即记录公共线程池中正在执行和准备执行的任务数,跟据记录决定本轮调度生成多少个回收任务,从而保证执行线程刚好够用,且不多不少。优点:保证垃圾回收速度,最优回收的plog集,有效的减少page的搬运量。5.   关键点4-冷热分区优化在数据库系统中,数据的更新并不是相同频率,一些数据页更新或者写入会更加频繁(例如系统页),这部分页面被称作热页,另外一些更新不频繁的称作冷页。如果把冷页和热页混合放到一起,就会导致更高的写放大。举个简单的例子,假设有100个热页和10个冷页,冷页基本不更新但是每次垃圾回收,都需要重复把冷页搬运一次。如果把冷页单独写入一个plog,那么在垃圾回收阶段,就可以减少重复搬运这部分冷页,达到减少IO放大的效果。         优点:提高垃圾回收的效率和速度。         缺点:需要额外的内存记录热度信息。6.   关键点5-磁盘逃生优化为了后台线程比如备份、快照、垃圾回收、页面回放等线程有足够的磁盘空间运行,在磁盘容量使用到达一定阈值,会置Full标志位,同时停止前台IO。在极端情况下,磁盘使用到阈值前台IO会出现断崖式下跌为零:为了避免在磁盘容量达到一定阈值之后前台IO完全停止,在磁盘使用率未达到阈值时,就应该有相应的处理机制。比如说在磁盘使用率未达阈值时,增加如下处理:A、 降低前台IO,减少磁盘的压力;B、 加速垃圾回收,也就是TaurusDB中的compactor机制;A点不涉及compactor的功能,所以本文先不涉及,下面主要介绍加速机制。6.1 修改写IO优先级Compactor作为后台线程,考虑到整个系统的效率,正常运行时plog写IO优先级默认为low。在加速回收阶段,plog的IO需要修改为high。6.2 提高执行速度前文可知,公共线程池为compactor分配固定数目执行线程,而且运行过程中只支持扩容不支持恢复。如果压缩其他后台任务的执行线程,对整个系统的影响太大,量也不易控制。所以不考虑。考虑到过程中不能扩容,那就初始化就扩容,通过控制调度任务数控制任务执行速度。例如,假设compactor的运行线程在正常场景下为4个,加速状态下需要增加到8个。可以在公共线程池中先分配8个线程,在正常场景下,控制任务队列为4个,另外4个线程处于wait状态,只会占用文件句柄,并不影响CPU。而在加速状态下,控制任务队列中的任务数为8,就可以实现上述的加速逻辑。6.3 总结提升写IO的优先级能够加速page的搬运,提升垃圾回收速度。通过控制调度任务数控制任务执行速度却很难控制很精准,上下会有一定的小波动。7.   总结以上1-4个方案能有效的减少page的搬运数,达到减少IO的目的。方案5能够处理磁盘容量紧急情况。目前本地测试,在500G的小数据集情况下,IO放大能减少到原来的1/6。系统资源占用减少到原来的1/3。
  • [问题求助] 模型配置文件中apis字段为空数组
    使用自己的模型做导入操作时,在修改配置文件时并没有自己的model_algorithm(具体做的是声音分类), 在按照模型包导入时显示“模型配置文件中apis字段为空数组时,将无法创建批量服务“,下面是我自己的模型转换,推理代码和配置文件
  • [技术干货] Python 内存管理学习
    Python中的所有东西都是对象。这意味着动态内存分配是Python内存管理的基础。当不再需要对象时,Python内存管理器将自动从它们中回收内存。Python是使用C编程语言实现的高级编程语言。Python内存管理器管理Python的内存分配。有一个私有heap,其中包含所有Python对象和数据结构。Python内存管理器按需管理Python堆。Python内存管理器具有特定于对象的分配器,可为int,string等特定对象分别分配内存。在此之下,原始内存分配器与操作系统的内存管理器进行交互,以确保私有堆上有空间。python 中的内存管理涉及包含所有 python 对象和数据结构的私有堆。 python 内存管理器在内部保证这个私有堆的管理。 python 内存管理器有不同的组件,这些组件处理各种动态存储管理方面,比如共享,分割,预分配或者缓存。Python内存管理器管理称为“块”的内存块。相同大小的块的集合构成了“池”。池是在Arenas上创建的,在堆= 64池上分配了256kB的内存块。如果对象被销毁,则内存管理器将用相同大小的新对象填充此空间。方法和变量在堆栈存储器中创建。每当创建方法和变量时,都会创建一个堆栈框架。只要返回方法,这些框架就会自动销毁。在堆内存中创建对象和实例变量。一旦返回变量和函数,将对垃圾对象进行垃圾回收。请务必注意,Python内存管理器不一定会将内存释放回操作系统,而是将内存返回给python解释器。Python有一个小的对象分配器,用于分配内存以供进一步使用。在长时间运行的进程中,您可能有未使用内存的增量保留。Python 的内存管理主要有三种机制:引用计数机制,垃圾回收机制和内存池机制。引用计数机制对象的内存赋值语句是语言最常见的功能了。但即使是最简单的赋值语句,也可以很有内涵。Python的赋值语句就很值得研究。a = 1整数1为一个对象。而a是一个引用。利用赋值语句,引用a指向对象1。为了探索对象在内存的存储,我们可以求助于Python的内置函数id()。它用于返回对象的身份(identity)。其实,这里所谓的身份,就是该对象的内存地址。a = 1 print(id(a))print(hex(id(a)))它们返回的是:19145624800x721de7b0分别为内存地址的十进制和十六进制表示。在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。当我们创建多个等于1的引用时,实际上是让所有这些引用指向同一个对象。a = 1b = 1 print(id(a))print(id(b))19145624801914562480可见a和b实际上是指向同一个对象的两个引用。由于Python缓存了整数和短字符串,因此每个对象只存有一份。比如,所有整数1的引用都指向同一对象。即使使用赋值语句,也只是创造了新的引用,而不是对象本身。长的字符串和其它对象可以有多个相同的对象,可以使用赋值语句创建出新的对象。在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。我们可以使用sys包中的getrefcount(),来查看某个对象的引用计数。需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。from sys import getrefcount a = [1, 2, 3]print(getrefcount(a)) b = aprint(getrefcount(b))由于上述原因,两个getrefcount将返回2和3,而不是期望的1和2。对象引用Python的一个容器对象(container),比如表、词典等,可以包含多个对象。实际上,容器对象中包含的并不是元素对象本身,是指向各个元素对象的引用。容器对象的引用可能构成很复杂的拓扑结构。我们可以用objgraph包来绘制其引用关系,比如:x = [1, 2, 3]y = [x, dict(key1=x)]z = [y, (x, y)] import objgraphobjgraph.show_refs([z], filename='show_refs.png')两个对象可能相互引用,从而构成所谓的引用环(reference cycle)。a = []b = [a]a.append(b)即使是一个对象,只需要自己引用自己,也能构成引用环。a = []a.append(a)print(getrefcount(a))垃圾回收机制当Python中的对象越来越多,它们将占据越来越大的内存。不过不用太担心Python的体形,它会乖巧的在适当的时候启动垃圾回收(garbage collection),将没用的对象清除。从基本原理上,当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就可以被垃圾回收。比如下面的表:a = [1, 2, 3]del adel a后,已经没有任何引用指向之前建立的[1, 2, 3]这个表。用户不可能通过任何方式接触或者动用这个对象。当垃圾回收启动时,Python扫描到这个引用计数为0的对象,就将它所占据的内存清空。垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。如果内存中的对象不多,就没有必要总启动垃圾回收。所以,Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。我们可以通过gc模块的get_threshold()方法,查看该阈值:import gc print(gc.get_threshold())python3中返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。可以通过gc中的set_threshold()方法重新设置。循环引用的回收策略上文已经举例说明循环引用计数的问题,出现循环引用后,利用上述引用计数机制无法对循环引用中的对象进行释放空间,这就是循环引用问题。 解决循环引用并释放内存(标记清除):收集所有容器对象(循环引用只针对于容器对象,其他对象不会产生循环引用),使用双向链表(可以看作一个集合)对这些对象进行引用;针对每一个容器对象,使用变量gc_refs来记录当前对应的应用个数;对于每个容器对象,找到其正在引用的其他容器对象,并将这个被引用的容器对象引用计数减去1;检查所有容器对象的引用计数,若为0,则证明该容器对象是由于循环引用存活下来的,并对其进行销毁。如何提升查找循环引用过程的性能?由一可知,循环引用查找和销毁过程非常繁琐,要分别处理每一个容器对象,所以python考虑一种改善性能的做法,即分代回收。 首先是一个假设–如果一个对象被检测了10次还没有被销毁,就减少对其的检测频率;基于这个假设,提出一套机制,即分代回收机制。 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。内存分配(内存池机制)Python频繁地创建和销毁一些小的对象,为了减少小对象(小于512字节)的开销,Python引入了内存池机制, 用于管理对小块内存的申请和释放,较大的对象被路由到标准C分配器。 我们可以在obmalloc.c源码中可以看到关于小内存请求的描述分配的内存空间大于 SMALL_REQUEST_THRESHOLD 将直接使用layer 1的内存分配接口进行分配。 pymalloc 把“内存池”主要抽象成用3种数据结构表示:block、pool、arenablock块指的是一个确定大小的内存块,每个块保留一个固定大小的Python对象,块的大小可以从8到512字节,并且是8的倍数(即8字节对齐)。block由pool管理,所有512字节以下的内存申请会根据申请字节的大小分配不一样的block,一个pool通常为4K(系统页大小),一个pool管理着一堆固定大小的内存块(block) obmalloc.c源代码注释:* Request in bytes                           Size of allocated block      Size class idx * ---------------------------------------------------------------- *        1-8                    8                       0 *        9-16                   16                       1 *       17-24                   24                       2 *       25-32                   32                       3 *       33-40                   40                       4 *       41-48                   48                       5 *       49-56                   56                       6 *       57-64                   64                       7 *       65-72                   72                       8 *        ...                   ...                     ... *      497-504                 504                      62 *      505-512                 512                      63Poolpool 由若干个大小相等的 block 组成,相同大小的块集合称为池。通常池的大小等于存储器页面的大小,即4Kb。 将池限制为固定大小的块有助于碎片化管理,如果对象被破坏,则内存管理器可以使用相同大小的新对象填充此空间。/* Pool for small blocks. */struct pool_header {    union { block *_padding;            uint count; } ref;          /* number of allocated blocks    */    block *freeblock;                   /* pool's free list head         */    struct pool_header *nextpool;       /* next pool of this size class  */    struct pool_header *prevpool;       /* previous pool       ""        */    uint arenaindex;                    /* index into arenas of base adr */    uint szidx;                         /* block size class index        */    uint nextoffset;                    /* bytes to virgin block         */    uint maxnextoffset;                 /* largest valid nextoffset      */};每个池都使用双向链表(nextpool和prevpool字段)将相同大小的块的池链接在一起。通过这种方式,即使跨不同的池,算法也可以轻松找到给定块大小的可用空间。 szidx字段保存size类索引,而ref.count保留已使用块的数量。 arenaindex存储创建Pool的arena的索引。每个Pool有三种状态: - used - 具有可用于存储数据的块,部分使用,既不空也不满 - full - 所有池的块目前都已分配并包含数据,当该 pool 上的一个 block 被回收时,重新将该 pool 链接在适当的 usedpools 链表中。 - empty - 没有存储数据,可以在需要时分配我们已经知道,所有相同块大小的池都链接在一起。为了有效地管理池,Python使用了一个名为usedpools的列表存储跟踪可用池的指针。在分配内存时,Pymalloc先判断是否存在要申请的大小的Pool,如果存在的话,直接从Pool中获取一个Free Block返回给应用程序,这个过程是非常迅速的。如果分配完这个Block后此Pool变为一个空Pool,则将这个Pool从链表中移除。ArenaArenas是最大的内存块,并在内存中的页面边界上对齐,页面边界是OS使用的固定长度连续内存块的边缘。在Arena内的池是一个虚拟内存页(4K),在堆上分配的256kB内存块,为64个池提供内存。arena 结构定义如下obmalloc.c:struct arena_object {    uintptr_t address;    block* pool_address;    uint nfreepools;    uint ntotalpools;    struct pool_header* freepools;    struct arena_object* nextarena;    struct arena_object* prevarena;};所有arena都使用双链表(nextarena和prevarena字段)链接,跟上面的pool类似。 ntotalpools 总共分配的 pool 数量 nfreepools 可分配的 pool 数量 freepools 字段指向可用池的链接列表Arenas的实现没有什么复杂的。可以将其视为容器列表,在需要时自动为Pool分配新内存。Python的小对象管理器不一定会将内存释放回操作系统,保留了一些已分配的内存块,以备将来进一步使用。 arena 完全释放当且仅当其中的所有池都为空时,如果长时间运行的Python进程随着时间的推移需要更多内存,但并不一定意味着您有内存泄漏。内存优化内存分析工具资源(resource)resource 模块用来查看项目当前得的固有的)内存消耗(固有内存是项目实际使用的RAM),注意resource库只在linux系统下有效import resourceresource.getrusage(resource.RUSAGE_SELF).ru_maxrss443对象(objgraph)objgraph 是一个实用模块,可以展示当前内存中存在的对象,上文中已经展示过其用法,不再过多说明。Heapyheapy 是一个实用的,用于调试内存消耗/泄漏的工具。通常,我将objgraph和heapy搭配使用:用 heapy 查看分配对象随时间增长的差异,heapy能够显示对象持有的最大内存等;用Objgraph找backref链,尝试获取它们不能被释放的原因。Heapy的典型用法是在不同地方的代码中调用一个函数,试图为内存使用量提供大量收集线索,找到可能会引发的问题:from guppyimport hpy  def dump_heap(h, i):    """    @param h: Theheap (from hp = hpy(), h = hp.heap())    @param i: Identifierstr    """     print "Dumpingstatsat: {0}".format(i)     print 'Memoryusage: {0}(MB)'.format(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss/1024)     print "Mostcommontypes:"    objgraph.show_most_common_types()     print "heapis:"    print "{0}".format(h)     by_refs = h.byrcs    print "byreferences: {0}".format(by_refs)    print "Morestatsfor topelement.."    print "Byclodo (class or dict owner): {0}".format(by_refs[0].byclodo)    print "Bysize: {0}".format(by_refs[0].bysize)    print "Byid: {0}".format(by_refs[0].byid)内存用量诊断工具(memory_profiler)memory_profile包最好有psutil包的支持,二者皆可以通过pip安装。pip install psutil memory_profilermemory_profiler的对性能的影响很大,所以最好将测试局限在一个较小的问题上,或者在代码成长的早期进行分析。使用的方法分两步:第一步是在源代码中用‘@profile’对需要分析的函数进行标记,第二步是调用memory_profiler模块进行分析。python -m memory_profiler test_memory.py在处理内存分配时,情况并不像CPU占有率那么直截了当,通常一个进程将内存超额分配给本地内存池并在空闲时使用会更有效率,因为一次内存分配的操作非常昂贵;另外,垃圾收集不会立即执行,所以对象被销毁后依然会存在垃圾收集池中一段时间;这些技术的后或是很难真正了解一个Python程序内部的内存使用和释放情况,因为当从进程外部观察时,某一段代码可能不会分配固定数量的内存,观察多行代码的内存占有趋势可能比只观察一行代码更有参考价值。垃圾回收优化手动垃圾回收对Python的垃圾回收进行调优的一个最简单的手段便是关闭自动回收,根据情况手动触发。调高垃圾回收阈值相比完全手动的垃圾回收,一个更温和的方法是调高垃圾回收的阈值。 调高阈值的方法能在一定程度上避免内存溢出的问题(但不能完全避免),同时可能减少可观的垃圾回收开销。根据具体项目 的不同,甚至是程序输入的不,合适的阈值也不同。因此需要反复测试找到一个合适的阈值,这也算调高阈值这种手段 的一个缺点。避免循环引用一个可能更好的方法是使用良好的编程习惯尽可能的避免循环引用。两种常见的手段包括: 手动解循环引用和使用弱引用。查找循环引用, Python的gc模块提供了gc.set_debug接口来设置一些辅助的调试信息。如果我们调用gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_OBJECTS)则每当Python进行垃圾回收时都会将 其发现的无法到达需要回收的对象的信息打印出来。例如:gc.collect()gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_OBJECTS)test2()gc.collect() gc: collectable <T 0x7fb6cacf6090>gc: collectable <T 0x7fb6cace0fd0>gc: collectable <dict 0x7fb6cac974b0>gc: collectable <dict 0x7fb6cac97280>Slots默认情况下,自定义的对象都使用dict来存储属性(通过obj.dict__查看),而python中的dict大小一般比实际存储的元素个数要大(以此降低hash冲突概率),因此会浪费一定的空间。在新式类中使用__slots,就是告诉Python虚拟机,这种类型的对象只会用到这些属性,因此虚拟机预留足够的空间就行了,如果声明了__slots__,那么对象就不会再有__dict__属性。class Student(object):    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称使用slots到底能带来多少内存优化呢,取决于类自身有多少属性、属性的类型,以及同时存在多少个类的实例。# -*- coding: utf-8 -*-import sysimport tracemalloc NUM_OF_ATTR =  3 #3 # 10 # 30 #90NUM_OF_INSTANCE = 10 # 10 # 100 class Slots(object):    __slots__ = ['attr%s'%i for i in range(NUM_OF_ATTR)]    def __init__(self):        value_lst = (1.0, True, [], {}, ())        for i in range(NUM_OF_ATTR):            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)])  class NoSlots(object):    def __init__(self):        value_lst = (1.0, True, [], {}, ())        for i in range(NUM_OF_ATTR):            setattr(self, 'attr%s'%i, value_lst[i % len(value_lst)]) if __name__ == '__main__':    clz = Slots if len(sys.argv) > 1 else NoSlots    tracemalloc.start()    objs = [clz() for i in range(NUM_OF_INSTANCE)]    print(tracemalloc.get_traced_memory()[0])上面的代码,主要是在每个实例的属性数目、并发存在的实例数目两个维度进行测试,并没有测试不同的属性类型。结果如下表:实例数\属性数31030901028.7%25.7%43.9%64.2%10042.7%21.6%36.5%56.7%百分比为内存优化百分比,计算公式为(b - a) / b, 其中b为没有使用__slots__时分配的内存, a为使用了__slots__时分配的内存。注意事项·         基类和子类都必须__slots__,即使基类或者子类没有属性·         >>> class Base(object):··         ...     pass·         ...·         >>> class Derived(Base):·         ...     __slots__ = ('a', )·         ...·         >>> d.__slots__·         ('a',)·         >>> getattr(d, '__dict__', 'No Dict')·         {}·         子类会继承基类的__slots__·         >>> class Base(object):··         ...     __slots__ = ('a',)·         ...·         >>> class Derived(Base):·         ...     __slots__ = ('b', )·         ...·         >>> d = Derived()·         >>> d.__slots__·         ('b',)·         >>> getattr(d, '__dict__', 'No Dict')·         'No Dict'·         >>> d.a = 1·         >>> d.c = 0·         Traceback (most recent call last):·           File "<stdin>", line 1, in <module>·         AttributeError: 'Derived' object has no attribute 'c'栈溢出在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。尾递归解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。例如斐波那契数列,在最开始的时候所有的斐波那契代码都是使用递归的方式来写的,递归有很多的缺点,执行效率低下,浪费资源,还有可能会造成栈溢出,而递归的程序的优点也是很明显的,就是结构层次很清晰,易于理解。可以使用循环的方式来取代递归,当然也可以使用尾递归的方式来实现。#encoding:utf-8 import time def Fib_recursion(num):    '''    直接使用递归法求解斐波那契数量的第num个数字    '''    if num<2:        return num    return Fib_recursion(num-1)+Fib_recursion(num-2)  def Fib_tail_recursion(num,res,temp):    '''    使用尾递归法求解斐波那契数量的第num个数字    '''    if num==0:        return res    else:        return Fib_tail_recursion(num-1, temp, res+temp)  def Fib_circle(num):    '''    直接使用循环来求解    '''    a=0    b=1    for i in range(1,num):        c=a+b        a=b        b=c    return c  if __name__ == '__main__':    num_list=[5,10,20,30,40,50]    for num in num_list:        start_time=time.time()        print Fib_recursion(num)        end_time=time.time()        print Fib_tail_recursion(num,0,1)        end_time2=time.time()        print Fib_circle(num)        end_time3=time.time()        print '正在求解的斐波那契数字下标为%s' %num        print '直接递归耗时为 :', end_time-start_time        print '尾递归调用耗时为:', end_time2-end_time        print '直接使用循环耗时为:', end_time3-end_time2deepcopycopy模块的deepcopy可以很好的完成对象的深拷贝,一个深层拷贝会构造一个新的复合对象,然后递归地将原始对象中所找到的对象的副本 插入。当对象节点内容过于庞大或者存在循环应用的话,会导致栈内存溢出或者递归循环导致进程崩溃;实际中,由于深层复制会复制所有内容,因此可能会过多复制(例如本应该在副本之间共享的数据)。 
  • [应用开发] 如何创建InputTensor和OutputTensor
    模型输入类型是type: float32[1,448,448,1],APP中已经把t输入图片调整成width(448)*height(448)的一维数组,有数组的头指针和长度。参考hisi_demo,不需要使用CreateInputTensor,直接将DVPP的输出指向inputtensor,这里是否也不需要创建input tensor?是否也是参照demo中的方式,CreateOutputTensor时,input_data_vec中push尽iput_data_img,调用iput_data_img的SetBuffer接口,把图片的内存地址和长度作为参数传入就可以了?谢谢!
  • [问题求助] PC与真机的数据差异问题
    问题描述:后端返回的数据为json格式,但是在PC端显示为图一格式,真机为图二格式所以我加了一句JSON.parse(),这样之后在PC端变为数组对象格式,方便对数据进行调用,这样在PC端调用没有任何问题,但是真机调试却什么也没有,也不报错,也打印不出来;如果去掉那句话,直接对返回数据进行调用,PC调试则会报错,报的都是我使用的split()、slice()这些未定义,和数组未定义,就是不能直接对该数据操作。
  • [技术干货] LiteOS双向链表使用详解
    官方开放文档中关于双向链表的介绍比较简单,并没有告诉大家到底怎么在自己的数据结构中使用链表。其实也比较简单,时间紧的朋友可以直接看第二部分。一、双向链表简介双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。从双向链表的任意一个节点开始,都可以很方便的访问它的前驱和后继节点。如下图所示:LiteOS双向链接跟linux的内核链接原理一样。LiteOS定义了双向链表基本数据结构,并提供了相关的函数和宏定义来操作链表,用户可以添加、删除节点,遍历节点等。LiteOS双向链表数据结构定义如下,包含了两个指针,分别指向前驱和后继节点。这是个最基本的数据结构,仅包含有双向链表。typedef struct LOS_DL_LIST {     struct LOS_DL_LIST *pstPrev;    /**< Current node's pointer to the previous node*/     struct LOS_DL_LIST *pstNext;    /**< Current node's pointer to the next node*/ } LOS_DL_LIST;LiteOS双向链表操作函数:VOID LOS_ListInit(LOS_DL_LIST *pstList) #define LOS_DL_LIST_FIRST(pstObject) ((pstObject)->pstNext) VOID LOS_ListAdd(LOS_DL_LIST *pstList, LOS_DL_LIST *pstNode) VOID LOS_ListTailInsert(LOS_DL_LIST *pstList, LOS_DL_LIST *pstNode) VOID LOS_ListDelete(LOS_DL_LIST *pstNode) BOOL LOS_ListEmpty(LOS_DL_LIST *pstNode) //return (BOOL)(pstNode->pstNext == pstNode); //取得链表所在结构体地址,即根据链表节点的地址获取用户结构体地址. //item: 链表节点地址.用户结构体中定义的链表成员的地址 //type: 用户结构体名称 //member:链表在用户结构体中的成员名称 #define LOS_DL_LIST_ENTRY(item, type, member) \     ((type *)((char *)item - LOS_OFF_SET_OF(type, member))) \ //遍历用户结构体 for循环 //item:用户结构体指针。需用户在外定义好。 //list: 链表首地址 //type: 用户结构体名称 //member: 链表在用户结构体中的成员名称 #define LOS_DL_LIST_FOR_EACH_ENTRY(item, list, type, member) \     for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member); \         &item->member != (list); \         item = LOS_DL_LIST_ENTRY(item->member.pstNext, type, member))    //遍历用户结构体,可删除节点  for循环 //item:用户结构体指针。需用户在外定义好。 //next: 用户结构体指针,指向item的下一个节点. 需用户在外定义该变量。 //list: 链表首地址 //type: 用户结构体名称 //member: 链表在用户结构体中的成员名称         #define LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, next, list, type, member)            \     for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member), \         next = LOS_DL_LIST_ENTRY(item->member.pstNext, type, member); \         &item->member != (list); \         item = next, next = LOS_DL_LIST_ENTRY(item->member.pstNext, type, member))     //遍历双向链表,不可有删除节点.仅遍历链表节点(不包括用户结构体内容) //item: 链表指针, 需用户定义好该变量 //list: 链表头 #define LOS_DL_LIST_FOR_EACH(item, list)   \     for ((item) = (list)->pstNext; \         (item) != (list); \         (item) = (item)->pstNext)         //安全遍历双向链表,可删除节点.仅遍历链表节点(不包括用户结构体内容) //item: 链表指针, 需用户定义好该变量 //next: 链表指针, 指向item下一个节点.需用户定义好该变量 //list: 链表头 #define LOS_DL_LIST_FOR_EACH_SAFE(item, next, list) \     for (item = (list)->pstNext, next = item->pstNext; item != (list); \         item = next, next = item->pstNext)    // 定义一个双向链表节点并初始化,可用于初始化一个链表头.变量名为list #define LOS_DL_LIST_HEAD(list) \             LOS_DL_LIST list = { &(list), &(list) }二、链表的使用LiteOS 内核定义的双向链表跟linux内核链表思想一致,它仅仅包含了两个指针类型的变量。用户使用双向链表时,要将双向链表加入自己的数据结构中。即定义一个结构体,成员包括用户需要用到的数据类型,最后,在结构体中加入内核双向链表数据类型,使之成为结构体的成员。如代码所示,定义用户结构体,包含双向链表成员:typedef struct {     UINT16          member1;  //用户数据              UINT16          member2;  //用户数据据                         LOS_DL_LIST     mList;    //双向链表    } User_Data_t;基本原理是:利用双向链表,将用户结构体中定义好的链表成员变量都链接起来;链表节点在用户结构体中的偏移地址是固定的,根据链表节点的地址,减去偏移地址,就能得到用户结构体变量的起始地址了。LiteOS中根据链表地址获取用户节点地址的宏定义是://取得链表所在结构体地址,即根据链表节点的地址获取用户结构体地址. //item: 链表节点地址.用户结构体中定义的链表成员的地址 //type: 用户结构体名称 //member:链表在用户结构体中的成员名称 #define LOS_DL_LIST_ENTRY(item, type, member) \     ((type *)((char *)item - LOS_OFF_SET_OF(type, member))) \如下图所示,利用链表将用户节点中的链表成员链接起来示例代码如下:User_Data_t  data1,data2,data3; //定义用户变量 LOS_DL_LIST  gHeader;   //定义链表头 data1.member1 = 1;data2.member1 = 2;data3.member1 = 3;//初始化用户数据 LOS_ListInit(&gHeader);  //初始化链表 LOS_ListTailInsert(&gHeader,&data1.mList);//将data1添加到链表尾部 LOS_ListTailInsert(&gHeader,&data2.mList);//将data2添加到链表尾部,即data1之后 LOS_ListTailInsert(&gHeader,&data3.mList);//将data3添加到链表尾部,即data2之后 //从链表中取数据  LOS_DL_LIST *node; User_Data_t  *pData; node = LOS_DL_LIST_FIRST(&gHeader); //获取链表第一个节点.node指向data1.mList //根据node取得data1的地址 pData = LOS_DL_LIST_ENTRY(node,User_Data_t,mList); printf("data1.member1 is %d!\r\n",pData->member1); //遍历链表 LOS_DL_LIST_FOR_EACH_ENTRY(pData,&gHeader,User_Data_t,mList) {     printf("data member1 is %d!\r\n",pData->member1); }
  • [问题求助] 【趁热打“帖”】+数据类型无法成功转化
    在数据中包含了字符串类型和数字类型代码如下:data_all=data_all.astype(float)data_all = np.log(data_all+0.001)data_all.hist(figsize=(20,15), color = 'c')运行结果提示: