如何区分IO密集型、CPU密集型任务?
前⾔
⽇常开发中,我们时常会听到什么IO密集型、CPU密集型任务...
那么这⾥提⼀个问题:⼤家知道什么样的任务或者代码会被认定为IO/CPU密集?⼜是⽤什么样的标准来认定IO/CPU密集?
如果你没有明确的答案,那么就随着这篇⽂章⼀起来聊⼀聊吧。
正⽂
最近团队⾥有基础技术的同学对项⽬中的线程池进⾏了重新设计,调整了IO线程池等线程池的优化。因此借助这个机会也就了解了⼀波开篇的那些问题。
⼀、宏观概念区分
这⼀部分经验丰富的同学都很熟悉。⽐如:
1.1、IO密集型任务
⼀般来说:⽂件读写、DB读写、⽹络请求等
1.2、CPU密集型任务
⼀般来说:计算型代码、Bitmap转换、Gson转换等
⼆、⽤代码区分
上⼀part都是咱们凭借经验划分的,这⼀part咱们就来⽤正经的指标来划分任务。
先看有哪些数据指标可以⽤来进⾏评估(以下⽅法以系统⽇志为准,加之开发经验为辅):
1. wallTime
任务的整体运⾏时长(包括了running + runnable + sleep等所有时长)。获取⽅案:
run() {
long start = System.currentTimeMillis();
// 业务代码
long wallTime = System.currentTimeMillis() - start;
}
2. cpuTime
cputime是任务真正在cpu上跑的时长,即为running时长
获取⽅案1:
run() {
long start = SystemClock.currentThreadTimeMillis();
// 业务代码
long cpuTime = SystemClock.currentThreadTimeMillis() - start; }
获取⽅案2:
/proc/pid/task/tid/sched
se.sum_exec_runtime CPU上的运⾏时长
3. iowait time/count
指线程的iowait耗时。获取⽅案:
se.statistics.iowait_sum IO等待累计时间
se.statistics.iowait_count IO等待累计次数
具体⽇志位置同上
4. runnable time
线程runnabel被调度的时长。获取⽅案:
/proc/pid/task/tid/sched
se.statistics.wait_sum 就绪队列等待累计时间
具体⽇志位置同上
5. sleep time
线程阻塞时长(包括Interruptible-sleep和Uninterruptible-sleep和iowait的时长)。获取⽅案:
/proc/pid/task/tid/sched
se.statistics.sum_sleep_runtime 阻塞累计时间
具体⽇志位置同上
6. utime/stime
utime是线程在⽤户态运⾏时长,stime是线程在内核态运⾏时长。获取⽅案:
/
proc/pid/task/tid/stat
第14个字段是utime,第15个字段是stime
7. rchar/wchar
wchar是write和pwrite函数写⼊的byte数。获取⽅案:
/proc/pid/task/tid/io
rchar: ...
wchar: ...
(没到合适的⽇志,暂不讨论此情况)基于读写char数,我们可以将IO细分成读IO密集型和写IO密集型。
8. page_fault
缺页中断次数,分为major/minor fault。获取⽅案:
第10个字段是minor_fault,第12个字段是major_fault
9. ctx_switches
线程在⽤户/内核态的切换次数,分为voluntary和involuntary两种切换。获取⽅案:
/proc/pid/task/tid/sched
nr_switches 总共切换次数
nr_voluntary_switches ⾃愿切换次数
nr_involuntary_switches ⾮⾃愿切换次数
⽇志位置同上
10. percpuload
平均每个cpu的执⾏时长。获取⽅案:
/proc/pid/task/tid/sched
avg_per_cpu
⽇志位置同上
有了上述这些指标,我们就可以开始我们的任务确定了。
以下内容,⼤家可以⾃⾏测试加深印象。
2.1、IO密集型任务
⽐如这段代码:
val br = BufferedReader(FileReader("xxxx"), 1024)
try {
while (br.readLine() != null) {}
} finally {
if (br != null) {
br.close()
}
}
基于上述部分3. iowait time/count,我们可以在对应的⽇志⽂件中看出这段代码有明显的iowait。
2.2、CPU密集型任务
⽐如这段代码:
var n = 0.0
for (i in 0..9999999) {
n = Double())
}
基于上述部分6. utime/stime的内容,看⼀看出这段代码utime会占⽐⾮常⾼,且⼏乎没有stime,此外没有io相关的耗时。
三、这玩意有啥⽤?
thread技术说⽩了,我们⼀切的优化⼿段都是为了服务于业务。对于业务开发来说:
为了不占⽤主线程 -> 所以启⼀个新线程 -> 频繁的new线程⼜会带来⼤量的开销 -> 所以使⽤线程池进⾏复⽤ -> ⽽不合理的线程池设计⼜会带来线程使⽤低效,甚⾄新加⼊的任务只能等待 -> 优化线程池
举个最简单的例⼦:线程池中放了最⼤允许俩个线程并⾏,那么假设运⾏中的俩个都是长IO的任务。那么新来的任务就只能等,哪怕它并不是特别耗时...
因此这玩意有啥⽤,还不是为更好的线程池设计做指导思想,更好的提升线程运⾏效率,降低业务上不必要的等待。
这⾥提供⼀些可供参考的⼯具⽅法和线程池设计:
3.1、判断任务类型
这⾥贴⼀些核⼼的思路,毕竟全部⽅案数据公司的代码,我也不⽅便全部贴出来:
class TaskInfo {
var cpuTimeStamp = 0.0
var timeStamp = 0.0
var iowaitTime = 0.0
var sleepTime = 0.0
var runnableTime = 0.0
var totalSwitches = 0.0
var voluntarySwitches = 0.0
}

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。