一款DMA性能优化记录:异步传输和指定实时信号做async IO
关键词:DMA、sync、async、SIGIO、F_SETSIG。
DMA本身用于减轻CPU负担,进行CPU off-load搬运工作。
在DMA驱动内部实现有同步和异步模式,异步模式使用dma_async_issue_pending(),然后在callback()中发送SIGIO信号,用户空间收到SIGIO进行handler处理视为一个周期完成。
同步模式,采用dma_sync_wait()进行等待,期间并没有释放CPU给其他进程使用。
在一个项目中,发现DMA相关占用率高的问题,后来发现是因为其使用了同步模式。然后,将其改成异步模式,并对其进行详细的分析,记录如下。
那么当初为什么没有使用async IO模式呢?
原来是因为同一进程中其他线程也是用了async IO设备,由于kill_fasync()发送SIGIO信号,在一个进程内无法多个handler。
权宜措施使用了同步DMA,造成占用率高。
然后通过fcntl(fd, F_SETSIG, sig);解决了kill_fasync()发送相同SIGIO信号的冲突问题。
1. 问题发现:DMA同步模式占用率高
抓数据命令:
- perf record -a -e cpu-clock -- sleep 60
- perf report
- perf report -s comm
不同数据量,不同CMA处理方式下的top和perf结果:
Item | 1 cma | per cma | ||
top | perf | top | perf | |
4K 25fps | 15% | 84.43% swapper [kernel.kallsyms] [k] __sched_text_end 11.51% main [kernel.kallsyms] [k] dma_cookie_status 2.81% main [kernel.kallsyms] [k] dma_sync_wait 0.20% main [kernel.kallsyms] [k] uart_write 0.13% swapper [kernel.kallsyms] [k] cache_op_range 0.12% swapper [kernel.kallsyms] [k] __dma_tx_complete 0.06% swapper [kernel.kallsyms] [k] dw_dma_tasklet 0.04% ksoftirqd/0 [kernel.kallsyms] [k] finish_task_switch 0.03% perf [kernel.kallsyms] [k] raw_copy_from_user 0.02% swapper [kernel.kallsyms] [k] __softirqentry_text_start |
61% | 45.93% main [kernel.kallsyms] [k] dcache_wb_line 36.79% swapper [kernel.kallsyms] [k] __sched_text_end 4.86% main [kernel.kallsyms] [k] dma_cookie_status 4.06% main [kernel.kallsyms] [k] free_hot_cold_page 1.28% main [kernel.kallsyms] [k] dma_sync_wait 1.28% main [kernel.kallsyms] [k] skip_ftrace 0.66% main [kernel.kallsyms] [k] _mcount 0.58% main [kernel.kallsyms] [k] unset_migratetype_isolate 0.51% main [kernel.kallsyms] [k] __free_pages 0.41% main [kernel.kallsyms] [k] start_isolate_page_range |
1080p 50fps | 9% | 89.82% swapper [kernel.kallsyms] [k] __sched_text_end 5.63% main [kernel.kallsyms] [k] dma_cookie_status 1.48% main [kernel.kallsyms] [k] dma_sync_wait 0.49% ksoftirqd/0 [kernel.kallsyms] [k] finish_task_switch 0.24% swapper [kernel.kallsyms] [k] dw_dma_tasklet 0.13% swapper [kernel.kallsyms] [k] __dma_tx_complete 0.10% swapper [kernel.kallsyms] [k] tasklet_action 0.10% main [kernel.kallsyms] [k] do_futex 0.06% main [kernel.kallsyms] [k] raw_copy_from_user 0.06% main [kernel.kallsyms] [k] restore_from_user_fp |
44% | 53.49% swapper [kernel.kallsyms] [k] __sched_text_end 30.73% main [kernel.kallsyms] [k] dcache_wb_line 3.65% main [kernel.kallsyms] [k] free_hot_cold_page 3.36% main [kernel.kallsyms] [k] dma_cookie_status 1.03% main [kernel.kallsyms] [k] skip_ftrace 0.85% main [kernel.kallsyms] [k] dma_sync_wait 0.56% main [kernel.kallsyms] [k] _mcount 0.48% main [kernel.kallsyms] [k] unset_migratetype_isolate 0.38% main [kernel.kallsyms] [k] __free_pages 0.33% main [kernel.kallsyms] [k] isolate_migratepages_range |
问题的分析:
1. skip_trace和_mcount两个是因为ftrace引入的负荷,不合理。此时不应该有这些。----需要推动csky修改成Dynamic function/function_graph。
2. dcache_wb_line/free_hot_cold_page是CMA操作引起的。-----------------------------------这里需要修改处理方式,CMA不需要重复申请释放。
3. dma_cookie_status/dma_sync_wait是DMA操作引起的。------------------------------------这里可以通过修改DMA异步信号触发来降低占用率。
分析总结:
1. 从上面的测试结果看,应该尽量避免内存申请释放。csky内存处理效率很低。
2. 同步模式效率非常低,4K单路占用率达到15%,如果4K双路就没法使用了。
所以必须要使用异步模式,这也是使用DMA的初衷。
1.1 不同DMA size对性能影响
多大的传输使用DMA获得的收益最高呢?做了个实验,结果如下,横轴单位是B,纵轴单位是MB/s、
可以看出,DMA size大小越大效率越高,size接近3MB的时候,以及以后吞吐率就比较稳定了。
2. 分析问题:让DMA异步起来
- int axidma_memcpy(dma_addr_t src, dma_addr_t dst, unsigned int len)
- {
- struct dma_async_tx_descriptor *tx = NULL;
- dma_cookie_t cookie;
- unsigned long flags;
- bool sync_wait = false;-------------------------------------------------------------------true表示同步等待模式;false表示异步模式,和callback()配合。
- int err = ;
- flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
- tx = xxxxxx->dma.chan->device->device_prep_dma_memcpy(xxxxxx->dma.chan, dst, src, len, flags);
- if (!tx)
- {
- pr_err("Fail to prepare memcpy.\n");
- return -;
- }
- tx->callback = axidma_callback;
- tx->callback_param = xxxxxx;
- cookie = tx->tx_submit(tx);
- if (dma_submit_error(cookie))
- {
- pr_err("Fail to submit axi dma.\n");
- return -;
- }
- if (!sync_wait) {
- dma_async_issue_pending(dma_dev->dma.chan);------------------------------------------发送DMA传输请求,然后退出。这里不会等待操作结果。
- } else {
- if (dma_sync_wait(dma_dev->dma.chan, cookie) == DMA_COMPLETE) {
- err = ;
- } else {
- err = -EIO;
- }
- }
- return err;
- }
- enum dma_status dma_sync_wait(struct dma_chan *chan, dma_cookie_t cookie)
- {
- enum dma_status status;
- unsigned long dma_sync_wait_timeout = jiffies + msecs_to_jiffies();-------------------超时5000ms,足够大了。
- dma_async_issue_pending(chan);------------------------------------------------------------和之前同样功能,发送DMA传输请求。只是下面会进行等待,并有超时动作。
- do {
- status =dma_async_is_tx_complete(chan, cookie, NULL, NULL);--------------------------pool DMA传输状态。
- if (time_after_eq(jiffies, dma_sync_wait_timeout)) {
- dev_err(chan->device->dev, "%s: timeout!\n", __func__);
- return DMA_ERROR;-----------------------------------------------------------------超时退出。
- }
- if (status != DMA_IN_PROGRESS)
- break;
- cpu_relax();--------------------------------------------------------------------------让出CPU执行。
- } while ();
- return status;
- }
- static inline enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,
- dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)
- {
- struct dma_tx_state state;
- enum dma_status status;
- status = chan->device->device_tx_status(chan, cookie, &state);
- if (last)
- *last = state.last;
- if (used)
- *used = state.used;
- return status;
- }
然后在callback()中发送SIGIO信号:
- static void axidma_callback(void *arg)
- {
- if(dma_dev->async)
- {
- pr_debug("axidma callback: chan=%s.\n", dma_chan_name(dma_dev->dma.chan));
- pr_debug("axidma_callback: magic=0x%08x pid_type=%d\n", dma_dev->async->magic, dma_dev->async->fa_file->f_owner.pid_type);
- kill_fasync(&dma_dev->async, SIGIO, POLL_IN);--------------------------------在传输完成后,异步发送SIGIO信号。
- }
- }
3. 解决问题:DMA异步传输
为了排除其他进程影响,单独构造3个试用例:1. 4K 25fps;2. 1080p 2路 25fps;3. 1080p 4路 25fps。
然后改成DMA异步模式,CPU占用率是否明显下降?
测试程序如下:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <stdio.h>
- #include <poll.h>
- #include <signal.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <fcntl.h>
- #include <time.h>
- #include <pthread.h>
- #include <sys/prctl.h>
- #include <sys/syscall.h>
- #include <sys/ioctl.h>
- #include "IFMS_MemCMA_API.h"
- int fd;
- unsigned int handle_count = ;
- volatile unsigned int dmatest_exit = ;
- volatile sigio_received = ;
- #define LOOP_COUNT 10000
- #define DMA_MEMCPY 0
- #define DMA_FILE "/dev/dpeye1000_axidma"
- #define DPEYE1000_AXIDMA_IOC_MAGIC 'd'
- #define DPEYE1000_AXIDMA_REQUEST_CHNN _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 1, int) //Request channel.
- #define DPEYE1000_AXIDMA_RELEASE_CHNN _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 2, int) //Release channel.
- #define DPEYE1000_AXIDMA_MEMCPY _IOW(DPEYE1000_AXIDMA_IOC_MAGIC, 3, int) //Do 1d memcpy.
- //#define PERFORMANCE_DEBUG
- struct axidma_dma
- {
- struct dma_chan *chan;
- unsigned long src;
- unsigned long dst;
- // for memcpy
- unsigned int len;
- unsigned int n_channels;
- };
- struct thread_para
- {
- unsigned int size;
- unsigned int expries;
- struct cma_block cma_block1;
- struct cma_block cma_block2;
- };
- static struct axidma_dma axidma_dma;
- struct thread_para t_para;
- int dmacopy_tid = ;
- void trigger_dma_copy(void)
- {
- int ret;
- #ifdef PERFORMANCE_DEBUG
- struct timespec time_0, time_1;
- unsigned long duration;
- float throughput = 0.0;
- clock_gettime(CLOCK_REALTIME, &time_0);
- #endif
- axidma_dma.src = (unsigned long)t_para.cma_block1.addr_p;
- axidma_dma.dst = (unsigned long)t_para.cma_block2.addr_p;
- axidma_dma.len = t_para.size;
- ret = ioctl(fd, DPEYE1000_AXIDMA_MEMCPY, &axidma_dma);
- if(ret < )
- {
- printf("fail to ioctl DPEYE1000_AXIDMA_MEMCPY!!!\n");
- }
- #ifdef PERFORMANCE_DEBUG
- clock_gettime(CLOCK_REALTIME, &time_1);
- duration = (time_1.tv_sec-time_0.tv_sec)* + (time_1.tv_nsec-time_0.tv_nsec);
- throughput = (float)t_para.size/*/duration;
- printf("%ld.%ld %d %d, %ld, %f\n", time_1.tv_sec, time_1.tv_nsec, handle_count, t_para.size, duration, throughput);
- #endif
- }
- void sigint_handler(int sig)
- {
- if(sig == SIGINT)
- {
- printf("%s SIGINT\n", __func__);
- dmatest_exit = ;
- }
- }
- void sigio_handler(int sig)
- {
- if(sig == SIGIO)
- {
- sigio_received = ;
- }
- }
- static void pthread_func(void *arg)
- {
- int ret;
- int Oflags;
- struct f_owner_ex owner_ex;
- struct timespec time1, time2;
- struct sigaction sa, sa2;
- sigset_t set, oldset;
- long long duration;
- unsigned int mode = DMA_MEMCPY;
- ret = IFMS_MemCMAAlloc(t_para.size, &t_para.cma_block1);
- if(ret<)
- {
- printf("CMA alloc failed.\n");
- return -;
- }
- ret=IFMS_MemCMAAlloc(t_para.size, &t_para.cma_block2);
- if(ret<)
- {
- printf("CMA alloc failed.\n");
- return -;
- }
- //===========================================================================================
- //Set thread name.
- prctl(PR_SET_NAME,"sigio");
- dmacopy_tid = syscall(SYS_gettid);
- printf("sigio thread tid=%ld %ld.\n", syscall(SYS_gettid), getpid());
- //Set SIGIO actiong.
- memset(&sa, , sizeof(sa));
- sa.sa_handler = sigio_handler;
- sa.sa_flags |= SA_RESTART;
- sigaction(SIGIO, &sa, NULL);
- //Set proc mask.
- sigemptyset(&set);
- sigprocmask(SIG_SETMASK, &set, NULL);
- sigaddset(&set, SIGIO);
- fd = open(DMA_FILE, O_RDWR);
- if (fd < )
- {
- printf("Can't open %s!\n", DMA_FILE);
- }
- //If set F_SETOWN_EX, SIGIO will send to this thread only.
- owner_ex.pid = syscall(SYS_gettid);
- owner_ex.type = F_OWNER_TID;
- fcntl(fd, F_SETOWN_EX, &owner_ex);
- Oflags = fcntl(fd, F_GETFL);
- fcntl(fd, F_SETFL, Oflags | FASYNC);
- clock_gettime(CLOCK_REALTIME, &time1);
- ret = ioctl(fd, DPEYE1000_AXIDMA_REQUEST_CHNN, &mode);
- if(ret < )
- {
- printf("fail to ioctl DPEYE1000_AXIDMA_REQUEST_CHNN!!!\n");
- }
- while(!dmatest_exit)
- {
- if(t_para.expries > )
- usleep(t_para.expries);
- trigger_dma_copy(); //Send DMA copy request. while(!sigio_received)
- while(!sigio_received)
- {
- pthread_sigmask(SIG_BLOCK, &set, &oldset);
- //sigprocmask(SIG_BLOCK, &set, &oldset);
- sigsuspend(&oldset); //Will pause here, and will be waked up by SIGIO.
- pthread_sigmask(SIG_UNBLOCK, &set, NULL);
- //sigprocmask(SIG_UNBLOCK, &set, NULL);
- handle_count++;
- }
- sigio_received = ;
- }
- clock_gettime(CLOCK_REALTIME, &time2);
- duration = (long long)(time2.tv_sec-time1.tv_sec)* + (time2.tv_nsec-time1.tv_nsec);
- sleep();
- printf("End time %lld.%09lld count=%d fps=%lld.\n", duration/, duration%, handle_count, (long long)handle_count*/duration);
- ioctl(fd, DPEYE1000_AXIDMA_RELEASE_CHNN, NULL);
- if(ret < )
- {
- printf("fail to ioctl DPEYE1000_AXIDMA_RELEASE_CHNN!!!\n");
- }
- close(fd);
- //===========================================================================================
- ret=IFMS_MemCMAFree(t_para.cma_block1);
- if(ret<)
- {
- printf("CMA free failed.\n");
- return -;
- }
- ret=IFMS_MemCMAFree(t_para.cma_block2);
- if(ret<)
- {
- printf("CMA free failed.\n");
- return -;
- }
- pthread_exit();
- }
- void main(int argc, char **argv)
- {
- pthread_t tidp;
- sigset_t set;
- unsigned int size = , expries = ;
- struct sigaction sa;
- if(argc != )
- {
- printf("Usage: %s size(B) expries(us).\n", argv[]);
- return -;
- }
- size = atoi(argv[]);
- if(!size)
- {
- printf("Please input right size.\n");
- return -;
- }
- expries = atoi(argv[]);
- if(!expries)
- {
- printf("Please input right expries time.\n");
- return -;
- }
- t_para.size = size;
- t_para.expries = expries;
- memset(&sa, , sizeof(sa));
- sa.sa_handler = sigint_handler;
- sa.sa_flags |= SA_RESTART;
- sigaction(SIGINT, &sa, NULL);
- sigemptyset(&set);
- sigaddset(&set, SIGIO);
- sigprocmask(SIG_BLOCK, &set, NULL);
- if(pthread_create(&tidp, NULL, pthread_func, NULL) == -)
- {
- printf("Create pthread error.\n");
- return;
- }
- if(pthread_join(tidp, NULL))
- {
- printf("Join pthread error.\n");
- return;
- }
- printf("Main exit.\n");
- return;
- }
3.1 DMA异步和同步在不同场景下对比测试
dma_thread测试不同分辨率、不同帧率下的性能对比:
Case | 同步DMA | 异步DMA | ||
top -d 5 |
perf record -a -e cpu-clock -- sleep 60 |
top -d 5 | perf record -a -e cpu-clock -- sleep 60 | |
1080p 50fps dma_thread 3110400 20000 |
7.5% 单次耗时:1.6ms |
91.12% swapper [kernel.kallsyms] [k] __sched_text_end 91.22% swapper 8.04% main |
dma_sigio 3110400 20000 0.3% 单次耗时:135us |
98.57% swapper |
1080p 100fps dma_thread 3110400 10000 |
14.1% 单次耗时:1.6ms |
84.27% swapper [kernel.kallsyms] [k] __sched_text_end 84.30% swapper |
dma_sigio 3110400 10000 0.5% 单次耗时:135us |
98.48% swapper |
4k 25fps dma_thread 13271040 40000 |
14.5% 单次耗时:6.7ms |
84.71% swapper [kernel.kallsyms] [k] __sched_text_end 84.75% swapper |
dma_sigio 13271040 40000 0.2% 单次耗时:135us |
98.51% swapper |
分析总结:
1. 同步模式下,CPU占用率跟数据量大小强相关,基本成正比;影响CPU占用率的最大因素是DMA传输同步等待,即上面dma_sync_wait()和dma_cookie_status()两个函数。
2. 异步模式下,请求发送后,交出CPU,在收到信号后继续下一次发送,期间不会占用CPU。CPU占用率跟DMA请求次数强相关,主要是发送请求,以及sigsuspend()和SIGIO信号处理占用。
3. 帧的吞吐率受DMA传输的帧大小影响。
3.2 那么异步模式的极限在哪里?
明显DMA的异步极限帧率,同样受限于DMA传输效率,并不会增大吞吐率。
那么看看不同帧率下的CPU情况:
Case | 异步DMA | |
top -d 5 | perf record -a -e cpu-clock -- sleep 60 | |
1080p 550 fps (max) |
2.6% |
96.50% swapper |
1080p 375 fps | 1.8 |
98.44% swapper |
1080p 273 fps | 1.2% |
98.43% swapper |
4K 145 fps (max) |
0.8% |
98.45% swapper |
所以DMA极限帧率,主要受DMA传输大小和传输速度影响。
3.3 kernelshark对比DMA同步/异步模式
分别看看上面3个场景下同步模式下,kernelshark输出可以看出1080p执行时间是1.67ms,4k时间是6.88ms;每次时间间隔跟fps设置也对应。
1080p 50fps、1080p 100fps、4k 25fps三种占用率应该是1.67/21.67=7.7%、1.67/11.67=14.3%、6.88/46.88=14.7%。
再来看一下异步情况下的输出,这时候越是大尺寸DMA传输CPU占用率的收益越大。4k的时候
下面以1080p和4k对比看一下异步的收益。
1080p的DMA传输占用时间从1.65,降到了1.65-1.57=0.08,收益率95%。
可以看出4k情况下异步DMA的CPU占用时间从6.70,降到6.70-6.64=0.06,收益率达到99%。
4. 同进程多SIGIO冲突解决
当测试通过,进入方案的时候遇到SIGIO无法接收到的问题。
检查得知,原来是存在多个设备kill_fasync()。而一个进程范围内,SIGIO只能有一个handler。
通过fcntl()设置F_SETSIG可以定义sig代提SIGIO发送信号。
操作如下:
- #define SIGDMA (SIGRTMIN+1)--------------------------定义一个实时信号
- fcntl(fd, F_SETSIG, SIGDMA);---------------------使用SIGDMA代提SIGIO作为async信号。
- memset(&sa, , sizeof(sa));
- sa.sa_sigaction = sigdma_handler;----------------一定要修改为sa_sigaction,对应的sigdma_handler参数也需要修改。
- sa.sa_flags = SA_RESTART | SA_SIGINFO;-----------一定要增加SA_SIGINFO。
- sigaction(SIGDMA, &sa, NULL);
除了有上面的好处之外,实时信号还能排队,这就比非实时信号更不会丢失。除非队列溢出。
5. 实际场景提升效果
在实际场景中,每40ms来一帧数据进行DMA搬运,
那么这段时间内,整个线程占用多少时间呢?2.303-0.225=2.078ms,对应的CPU占用率应该是5.2%。
再看看异步DMA实际效果如何?可以看出copy线程,中间调度出去的时间增大不小。
那么此时CPU占用率多少呢?2.288-1.177-0.431=0.68ms,对应的CPU占用率应该是1.7%。
从计算来看CPU占用率能降低3%左右。
6. 其他方案
1. 使用AXI DMA两通道,能否提高DMA吞吐率?相当于DMA并发,copy双线程?------------硬件双通道,如何构造同时触发的双通道case?
2. 如何标识每一次DMA传输,通过netlink port端口?--------------------------------------------------修改异步触发方式,port和channel绑定
一款DMA性能优化记录:异步传输和指定实时信号做async IO的更多相关文章
- React性能优化记录(不定期更新)
React性能优化记录(不定期更新) 1. 使用PureComponent代替Component 在新建组件的时候需要继承Component会用到以下代码 import React,{Componen ...
- Mysql 性能优化记录
记录工作中有关mysql性能优化的心得和经验 1. where条件中的字段 尽量建立索引 2. where条件中的查询条件等号左边尽量不做处理 如查询日期相关字段,尽量不使用date_fromat 或 ...
- web性能优化之--合理使用http缓存和localStorage做资源缓存
一.前言 开始先扯点别的: 估计很多前端er的同学应该遇到过:在旧项目中添加新的功能模块.或者修改一些静态文件时候,当代码部署到线上之后,需求方验收OK,此时你送了一口气,当你准备开始得意于自己的ma ...
- SSD性能优化记录
在上一篇博文中,我设计了一个优化方法,方法从业务角度出发,将切图操作涉及到的性能路径剖析出来,分别进行优化,效果显著. 眼下的情况是:一张ArcGIS武汉市城市影像图.该操作由79小时缩短至当前的67 ...
- Webpack 性能优化 (一)(使用别名做重定向)
前言 Webpack 是 OneAPM 前端技术栈中非常重要的一部分.它非常好用,假设你还不了解它,建议你阅读这篇Webpack 入门指迷,在 OneAPM 我们用它完毕静态资源打包.ES6 代码的转 ...
- Yslow网站性能优化工具
Yslow是一款网站性能优化的插件:
- 前端性能优化(三)——传统 JavaScript 优化的误区
注:本文是纯技术探讨文,无图无笑点,希望您喜欢 一.前言 软件行业极其缺乏前端人才这是圈内的共识了,某种程度上讲,同等水平前端的工资都要比后端高上不少,而圈内的另一项共识则是--网页是公司的脸面! 几 ...
- Oracle性能优化1-总体思路和误区
最近在看梁敬彬老师关于Oracle性能优化的一些案例,在这里做一些简单的总结 1.COUNT(*)与COUNT(列)哪个更快 drop table t purge; create table t as ...
- Yahoo关于性能优化的N条规则
本来这是个老生常谈的问题,上周自成又分享了一些性能优化的建议,我这里再做一个全面的Tips整理,谨作为查阅型的文档,不妥之处,还请指正: 一. Yahoo的规则条例: 谨记:80%-90%的终端响应时 ...
随机推荐
- 说说不知道的Golang中参数传递
本文由云+社区发表 导言 几乎每一个C++开发人员,都被面试过有关于函数参数是值传递还是引用传递的问题,其实不止于C++,任何一个语言中,我们都需要关心函数在参数传递时的行为.在golang中存在着m ...
- 为容器化的 Go 程序搭建 CI
本文介绍如何使用 Jenkins 的声明式 pipeline 为一个简单的 Golang web 应用搭建 CI 环境.如果你还不太了解 Jenkins 及其声明式 pipeline,请先参考笔者的 ...
- Mybatis学习(六)————— Spring整合mybatis
一.Spring整合mybatis思路 非常简单,这里先回顾一下mybatis最基础的根基, mybatis,有两个配置文件 全局配置文件SqlMapConfig.xml(配置数据源,全局变量,加载映 ...
- MySQL递归查询_函数语法检查_GROUP_CONCAT组合结果集的使用
1-前言: 在Mysql使用递归查询是很不方便的,不像Sqlserver可以直接使用声明变量,使用虚拟表等等.如:DECLARE,BEGIN ... END ,WHILE ,IF 等等. 在My ...
- Java Calendar类的使用总结
在实际项目当中,我们经常会涉及到对时间的处理,例如登陆网站,我们会看到网站首页显示XXX,欢迎您!今天是XXXX年....某些网站会记录下用户登陆的时间,比如银行的一些网站,对于这些经常需要处理的问题 ...
- Java开发笔记(六十一)Lambda表达式
前面介绍了匿名内部类的简单用法,通过在sort方法中运用匿名内部类,不但能够简化代码数量,还能保持业务代码的连续性.只是匿名内部类的结构仍显啰嗦,虽然它省去了内部类的名称,但是花括号里面的方法定义代码 ...
- Android开发——使用intent传递对象
intent传递对象有两种方法: 方式一:Serializable 方式 方式二:Parcelable方式 在这里不多介绍了,这一篇就是快速上手使用教程,至于详细原理介绍的,请看这一篇http://w ...
- Cesium 之简介以及离线部署运行篇
前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. C ...
- 解决一个Ubuntu中编译NEON优化的OpenCV的错误
在Ubuntu 16中编译开启NEON优化的Opencv时,遇到libpng编译是使用汇编代码的错误,完整错误见文章末尾.通过查询发现解决方案是安装跨平台编译器,安装代码如下: sudo apt-ge ...
- HttpWebRequest 改为 HttpClient 踩坑记-请求头设置
HttpWebRequest 改为 HttpClient 踩坑记-请求头设置 Intro 这两天改了一个项目,原来的项目是.net framework 项目,里面处理 HTTP 请求使用的是 WebR ...