Android系统性能调优工具介绍
经作者授权,发表Tieto某青年牛的一篇《程序员》大作。
Android系统性能调优工具介绍
在软件开发过程中,想必很多读者都遇到过系统性能问题。而解决系统性能问题的几个主要步骤是:
- 测评:对系统进行大量有针对性的测试,以得到合适的测试数据。
- 分析系统瓶颈:分析测试数据,找到其中的hotspot(热点,即bottleneck)。
- 性能优化:对hotspot相关的代码进行优化。
由上述步骤可知,性能优化的目标对象是hotspot。如果找到的hotspot并非真正的热点,则性能优化的结果必然是事倍功半甚至竹篮打水一场空。所以,作为Android性能调优相关知识的第一部分,本篇首先将向读者介绍Android平台中三个重要的性能测试工具,它们能很好得帮助开发者找到hotspot。
一Traceview介绍
1.1 Traceview简介
Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下:
- 开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。
- 借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如图1-1所示。
图1-1 DDMS中Traceview使用示意图
点击图1-1中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。
下面,我们通过一个示例程序向读者介绍Debug类以及Traceview的使用。
1.2 Traceview示例分析
示例程序运行时的界面如图1-2所示:
图1-2 示例界面图
图1-2中:
- SystraceDemoStringAAA等字样是TraceviewDemo程序启动时ListView最初显示的字符串。
- 当用户点击ListView中的某一项时,Traceview将计算对应项当前显示的字符串的MD5值40次,然后用计算得到的MD5字符串替换该项之前显示的内容。其效果如图1-2中的“MD5值“箭头所示。
该示例的关键代码如图1-3所示:
图1-3示例代码
由图1-3可知:
- 左图中,Debug类的startMethodTracing和stopMethodTracing分别在MainAcvtivity的构造方法和onDestroy函数中调用。
- onCreate函数中我们设置了第一个hotspot,即getStringToShow函数。它将解析一个XML文件,并将解析后的字符串保存到mListItem中以作为ListView的显示内容。
- 右图中,当用户点击ListView中的某个Item时,程序在onListItem中将计算MD5值40次,然后用计算结果做为被点击项的新字符串显示。generateMD5中的函数是本示例的第二个hotspot。
现在,我们用Traceview工具将测试结果文件TraceviewDemo.trace打开。
Traceview界面比较复杂,其UI划分为上下两个面板,即Timeline Panel(时间线面板)和Profile Panel(分析面板)。图1-4所示为Timeline Panel界面:
图1-4 Traceview Timeline Panel示意图
图1-4中的Timeline Panel又可细分为左右两个Pane:
- 左边Pane显示的是测试数据中所采集的线程信息。由图1-4可知,本次测试数据采集了main线程,两个Binder线程和其它系统辅助线程(例如GC线程等)的信息。
- 右边Pane所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图1-4可知,main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
- 另外,开发者可以在时间线Pane中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。
现在来看Traceview的Profile Panel界面,如图1-5所示:
图1-5 TraceviewProfile Panel界面
Profile Panel是Traceview的核心界面,其内涵非常丰富。它主要展示了某个线程(先在Timeline Panel中选择线程)中各个函数调用的情况,包括CPU使用时间、调用次数等信息。而这些信息正是查找hotspot的关键依据。所以,对开发者而言,一定要了解Profile Panel中各列的含义。笔者总结了其中几个重要列的作用,如表1-1所示:
表1-1 Profile Panel各列作用说明
列名 |
描述 |
Name |
该线程运行过程中所调用的函数名 |
Incl Cpu Time |
某函数占用的CPU时间,包含内部调用其它函数的CPU时间 |
Excl Cpu Time |
某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间 |
Incl Real Time |
某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 |
Excl Real Time |
某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间 |
Call+Recur Calls/Total |
某函数被调用次数以及递归调用占总调用次数的百分比 |
Cpu Time/Call |
某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间 |
Real Time/Call |
同CPU Time/Call类似,只不过统计单位换成了真实时间 |
另外,每一个Time列还对应有一个用时间百分比来统计的列(如Incl Cpu Time列对应还有一个列名为Incl Cpu Time %的列,表示以时间百分比来统计的Incl Cpu Time)。
了解完Traceview的UI后,现在介绍如何利用Traceview来查找hotspot。
一般而言,hotspot包括两种类型的函数:
- 一类是调用次数不多,但每次调用却需要花费很长时间的函数。在示例代码中,它就是hotspot 1。
- 一类是那些自身占用时间不长,但调用却非常频繁的函数。在示例代码中,它就是hotspot 2。
首先,我们来查找hotspot 1。
在Profile Panel中,选择按Cpu Time/Call进行降序排序(从上之下排列,每项的耗费时间由高到低),得到如图1-6所示的结果:
图1-6 按CPU Time/Call降序排列数据
图1-6中:
- MainActivity.onCreate是应用程序中的函数,它耗时为4618.684。然后,点击MainActivity.onCreate项,得到箭头所示的小图。
- 小图中,Parents一行显示的是MainActivity.onCreate的调用者,本例中它是performCreate函数。这部分代码属于Framework部分。Children行显示的是MainActivity.onCreate调用的子函数。
- 在MainActivity.onCreate调用的子函数中,我们发现getStringsToShow在Incl Cpu Time %一列中占据了63.3%,它是onCreate子函数耗费时间最长的,而且Calls+Recur Calls/Total列显示其调用次数为1,即它仅仅被调用一次了。这个函数是应用程序实现的,所以极有可能是一个潜在的Hotspot。
- 另外,由于笔者已经知道getStringsToShow是示例应用自己实现的函数,故在图1-6的大图中,可直接根据MainActivity.getStringsToShow花费了2921.913CPU时间这个信息来确定Hotspot就是它。
相对来说,类型1的hotspot比较好找,步骤是先按降序对时间项进行排列(可以是时间百分比、真实时间或CPU时间),然后查找耗费时间最多的函数。一般而言,先应对应用程序自己实现的函数进行排查,Framework的函数也有可能是hotspot,但主因一般还是在应用本身(例如设置复杂的界面,导致对应XML解析非常慢)。
现在,我们来看如何查找类型2的hotspot。
点击Call/Recur Calls/Total列头,使之按降序排列。关注点放在那些调用频繁并且占用资源较多的函数。图1-7为降序排列的结果图。
图1-7类型2 Hotspot查找过程示意之一
图1-7所示的运行最频繁的几个函数中,我们发现了几个怀疑点,由图中的1和2箭头标示。
- 结合代码,箭头1所指的函数在代码中实际并不存在。这是因为代码中直接访问了内部类的私有成员,导致java编译器在编译时自动生成了这个函数。这个函数的调用次数非常多。所以,为了提高效率,我们可以修改内部类成员的访问类型定义为public。不过,该函数的Incl Cpu Time并不高,只有3.2%。
- 同样,箭头2所指部分的函数调用次数也很多,达到了5888多次。不过它们占用的时间百分比只有0.9%。
第一次查找的潜在点被排除后,继续浏览数据,得到如图1-8所示的结果。
图1-8 类型2 Hotspot查找过程示意之二
在图1-8中:
- 红框处有两个重载的MyMD5.getHashString函数调用,它们各运行了368次,而且占用的CPU时间百分比达到了31.8%和53.2%。很显然,这2处调用就有优化的余地,这就是我们所怀疑的hotspot2。
找到hotspot之后,开发者就需要结合代码来进行对应的优化了。关于Java代码优化,读者可参考如下资料:http://developer.android.com/training/articles/perf-tips.html
总体而言,Hotspot的查找是一个细致的工作,需要开发者对目标程序的代码,以及Traceview工具都比较熟悉才行。
1.3 Traceview小结
Traceview工具是Android平台应用程序性能分析的利器。不过笔者觉得它的UI还是有些复杂。并且使用时感觉流畅度不够好。
Google官方关于Traceview的介绍可参考以下链接,不过其内容以及较久未更新了。http://developer.android.com/tools/debugging/debugging-tracing.html。
二Systrace介绍
2.1 Systrace简介
Systrace是Android4.1中新增的性能数据采样和分析工具。它可帮助开发者收集Android关键子系统(如surfaceflinger、WindowManagerService等Framework部分关键模块、服务)的运行信息,从而帮助开发者更直观的分析系统瓶颈,改进性能。
Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:
- 内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的话,必须开启kernel中和ftrace相关的模块。
- 数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。
- 数据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集ftrace统计数据并生成一个结果网页文件供用户查看。
从本质上说,Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace。Android 4.1为系统中的几个关键进程和模块都添加了Systrace功能。以显示系统中重要模块Hwcomposer为例,其代码中使用Systrace的方法如图2-1所示:
图2-1 Hwcomposer模块Systrace使用示例
图2-1中,应用程序只要通过三个宏就可使用Systrace了:
- 定义ATRACE_TAG:Hwcomposer使用了ATRACE_TAG_GRAPHICS,表示它和Graphics相关。
- ATRACE_INIT:用于统计某个变量使用的情况。下文将见到代码中”VSYNC”的统计结果。
- ATRACE_CALL:用于统计函数的调用情况。
由于篇幅关系,关于Trace使用更多的信息请读者阅读frameworks/native/include/utils/Trace.h或者android.os.Trace类。下面,我们通过一个示例来展示Systrace的使用。
2.2 Systrace实例
首先,在PC机上运行如下命令以启动Systrace,如图2-2所示:
图2-2 Systrace操作步骤
执行上述命令后,将得到一个名为trace.html的文件(trace.html是默认文件名,读者也可在命令行中指定其他文件名)。通过浏览器打开此文件,结果如图2-3所示:
图 2-3 trace.html内容示意
图2-3中所示的trace.html页面内容和Traceview的Timeline Panel非常类似。图中包含的内容如下:
- 由于在systrace.py中指定了-f -l和-i参数,Systrace将生成CPU频率、负载和状态相关的信息。它们为图2-1中第一个红框所示。由于笔者所测手机CPU为双核,故图中有CPU 0和CPU 1之分。为行文方便,笔者用CPU N来指代CPU的某个核。
- “CPU N“所示行对应于整个测试时间内,某个核上运行的进程信息。
- “CPU N C-State“所示行为整个测试时间内,某个CPU状态的变化。C-State取值见表2-1。
- “CPU N Clock Frequency”所示行展示了某个CPU运行的频率。通过点击这一行的色块可以查看某个时间点上CPU N的运行频率。
- “cpufreq”:该行所示内容和CPU交互式频率调节器(Interactive Governor)的工作有关。交互式CPU调节器驱动添加了对CPU频率调节事件的跟踪。感兴趣的读者不妨阅读kernel中的include/trace/events/cpufreq_interactive.h文件以了解更多的信息。
图2-1中,CPU信息以下的行就是通过Trace.h提供的宏而添加的统计信息,其中:
- VSYNC:这一行的统计信息来自于图2-1中ATRACE_INIT宏的使用。在Hwcomposer代码中,ATRACE_INIT宏被用于统计VSYNC[1]的Tick-Tack情况(即0,1,0,1交错输出)。VSYNC行显示了每次Tick Tack的时间大概都在16ms左右。
- 由于Framework代码也在显示部分添加了ATRACE_INIT的使用,所以图中com.example.systracedemo/com.example.systracedemo.MainActivity所示为应用程序占用显示Buffer的Tick-Tack情况。如果使用时间超过16ms,将导致界面显示迟滞等现象。
- SurfaceFlinger使用了ATRACE_CALL宏,故图中SurfaceFlinger行展示了其函数调用的CPU耗时情况(如箭头1所指,SurfaceFlinger中的onMessageReceived函数的运行信息)。
- 在图2-1最下部的方框中,详细显示了当前鼠标在时间线中选择的部分(即SurfaceFlinger中的onMessageReceived)的详细信息。
表2-1所示为CPU状态取值信息:
表2-1 CPU状态
C-state |
描述 |
C-0 |
RUN MODE,运行模式。 |
C-1 |
STANDBY,就位模式,随时准备投入运行 |
C-2 |
DORMANT,休眠状态,被唤醒投入运行时有一定的延迟 |
C-3 |
SHUTDOWN,关闭状态,需要有较长的延迟才能进入运行状态,减少耗电 |
2.3 Systrace小结
总体来说,Systrace比Traceview用途更广泛,它支持对CPU、Native进程甚至Kernel线程进行性能数据采样,可帮助开发者对整个系统的性能情况进行一个详尽的分析。不过其用法比Traceview要复杂,而且还需要对Kernel做一些配置调整。
Android官方对Systrace也有一些介绍,请读者阅读:
http://developer.android.com/tools/debugging/systrace.html
三Oprofile的使用
3.1 Oprofile简介
Oprofile是另一个功能更强大的性能数据采集和分析工具,其工作原理如下:
- 它利用性能计数器(Performance Counter)或者定时器(针对kernel不支持性能计数器的情况),通过连续的采样获得统计数据,从而对内核和用户空间进程进行性能分析。
- 以性能计数器为例,在系统运行过程中,当某个事件发生时,对应的性能计数器就会自加。当达到计数器的设定值时会产生一个中断。Oprofile驱动利用这个中断来进行采样统计。通过获取中断发生时PC指针的值以及内核中保存运行的任务的信息等,并把它们转化成对测评有用的数据。
- Oprofile包括内核驱动和用户空间工具两个部分,其中:
- 内核驱动实现了一个oprofilefs虚拟文件系统。它挂载到/dev/oprofile,用来向用户空间报告数据和接收来自用户空间的设置。它是用户空间进程与内核通信的桥梁。驱动中还包括了与架构相关和通用的驱动,通过它们访问性能计数器寄存器、收集数据后报告给用户空间。守护进程用户从内核接收数据并保存在磁盘上以备分析使用。
- 在用户空间提供了两个工具:oprofiled(作为守护进程在后台通过和/dev/oprofile交互以获取驱动收集的数据)、opcontrol(用户操作的控制工具,它通过读写oprofilefs来控制采样相关的设置)。
Android默认提供了对Oprofile的支持,其组成包括:
- 代码:位于exetrnal/oprofile中。不过,只有编译类型为非user的系统才会使用它。
- 四个主要工具,即opcontrol,oprofiled、opreport和opimport。开发者只要使用opcontrol和opreport即可。
- 读者应该熟练掌握opcontrol和oprofiled工具的作用,我们此处也总结了它们的用法:
- opcontrol:它用来控制采样过程,比如采样的开始和结束、采样的事件类型和频率等。其内部通过读写oprofilefs来实现。opcontrol的常用选项如表3-1所示:
表3-1 opcontrol常用选项
opcontrol选项 |
功能 |
--list-events |
列出当前CPU所支持的事件 |
--setup |
对测评进行设置,比如关闭旧的守护进程、挂载oprofilefs |
--vmlinux= |
设置将要分析的Android内核镜像文件 |
--callgraph |
设置跟踪函数调用的层数 |
--kernel-range=start,end |
内核二进制文件起始和结束的虚拟地址 |
--start/--stop |
开始/停止采样 |
--event=name:count:unitmask:kernel:user |
设置对某事件进行采样。 Name:事件的名字 Count:采样时事件发生的次数Unitmask:事件的掩码(CPU支持的事件以及掩码见oprofile的文档) Kernel:是否采样内核事件 User:是否采样用户事件 |
- opreport:opreport是使用采样数据生成报告的工具,可根据用户要求生成不同的报告。一般用法是“opreport [options] [image]”,其中image指定报告需要显示的程序的名字(指程序名字、共享库名字和内核)。image参数可选。不指定它时,opreport将打印所有进程的报告结果。常用options如表3-2所示:
表3-2 opreport常用选项
opreprt选项 |
功能 |
-l |
显示函数调用的符号名字 |
-g |
以调试的形式打印函数符号,包括函数所在文件及行数等。 |
-c |
显示函数调用堆栈 |
-o |
报告输出到指定文件 |
另外,Android提供了一个特别的工具opimport_pull。它可把采样数据从手机中pull到PC上,并对数据进行一些简单处理以供opreport使用。所以,在Android平台上,开发者只要使用opimport_pull了就可以了。
现在,我们来看Oprofile的使用实例。
3.2 Oprofile实例
Oprofile的使用大体可以分成以下三步:
- 内核加载oprofile驱动(如果该驱动静态编译到内核中,则可略过此步骤)。
- 配置采样事件、然后进行采样。
- 获取报告、进行分析,针对分析结果进行改进。
下面分别来看这三个步骤:
3.2.1 Oprofile内核配置
如下所示为内核配置的示例,如图3-1所示:
图3-1 Oprofile内核配置示意
运行Oprofile需要root权限,所以目标设备中最好运行的是userdebug或者engineer版本的Android OS。
3.2.2 Oprofile用户空间配置
Oprofile用户空间配置的示例如图3-2所示。假设当前目录为Android源码根目录,并且已经初始化Android编译环境(执行完毕build/envsetup.sh和lunch)。
图3-2 Oprofile用户空间配置示意
用户空间的配置主要通过执行opcontrol命令来完成。而opcontrol内部是通过往oprofilefs传递对应的控制参数来完成的。例如图3-2中“opcontrol --callgraph=16”命令也可通过“echo 16> /dev/oprofile/backtrace_depth”来实现。
3.2.3 结果分析
在上一步中,我们已经获取了测评采样的数据。现在,就可以使用它们来生成采样报告了,方法如图3-3所示:
图3-3 oprofile生成采样报告方法示意
图3-4为报告的一部分内容:
图3-4 Oprofile测评报告概要
图3-4中,我们发现libc.so调用的采样数为117299,排第4位。那么libc.so中哪个函数调用次数最多呢?开发者可通过如下命令获取libc.so的更为详细的信息。方法如图3-5所示:
图3-5 opreport使用示例
执行上述命令后的结果如图3-6所示:
图3-6 Oprofile关于libc的详细结果
由图3-6可知,memcpy()函数占用最多的CPU资源。所以可以考虑优化memcpy()。
此处,笔者针对Cortex-A9双核SMP处理器,使用ARM汇编的方法对memcpy进行了优化。优化后的结果如图3-7所示。对比图3-6和图3-7可明显看出,优化后的memcpy对资源的占用降低了2.7个百分点。
图3-7 优化memcpy()后的测试结果
3.3 Oprofile小结
在性能分析中,Oprofile无疑是一个使用最广泛、功能最强大的测评工具。对于Android平台开发者来说,它可以采集和分析整个系统的运行状态信息,对于分析查找系统瓶颈进而优化系统具有重大意义。
四 总结
性能调优向来是一件“高深莫测”的任务,但打破它们神秘面纱的工具就是上文所述的工具了。所以,对有志开展这方面工作的读者而言,首要一步的工作就是先了解各个工具的作用及优缺点。
除了本文介绍的这三个工具外,Android系统还支持其他一些更有针对性的测试工具,例如用于测评系统整体功能的lmbench,lttng、测试系统启动性能的bootchart、测试文件系统性能的iozone等。由于篇幅关系,笔者就不再一一介绍它们了。
[1]关于VSYNC的详情,读者可参考http://blog.csdn.net/innost/article/details/8272867,“Android Project Butter分析“一文。
Android系统性能调优工具介绍相关推荐
- Perf -- Linux下的系统性能调优工具,第 2 部分
https://www.ibm.com/developerworks/cn/linux/l-cn-perf2/ Perf -- Linux下的系统性能调优工具,第 2 部分 刘 明, 软件工程师, 上 ...
- Java应用性能调优工具介绍及实践
一.背景 (1).随着微服务架构的逐渐推广,一个大型的单个应用程序被拆分为数个微服务系统,这为研发人员的本地调试跟踪带来困难 (2).在微服务架构中,由于业务的复杂性,常常一个业务流程涉及好数个微服务 ...
- android性能调优的工具,神兵利器-Android 性能调优工具 Hugo
在进行Android性能调优.减少应用卡顿时,寻找可优化的code是一个必要的过程.如何发现应用中的耗时任务甚至是耗时函数呢,如果可以在log中打印每个方法的执行时间,甚至把执行方法时的输入输出同时打 ...
- 编写一个能监控到windows进程占用内存大小的脚本_Java性能监控分析及调优工具...
>>>推荐阅读<<< 1.性能测试学习笔记-场景设计 2.性能测试的重要意义 3.性能分析流程及方法 4.应用系统性能调优之性能分析 Java性能监控分析及调优工具 ...
- 【建议收藏】15755字,讲透MySQL性能优化(包含MySQL架构、存储引擎、调优工具、SQL、索引、建议等等)
0. 目录 1)MySQL总体架构介绍 2)MySQL存储引擎调优 3)常用慢查询分析工具 4)如何定位不合理的SQL 5)SQL优化的一些建议 1 MySQL总体架构介绍 1.1 MySQL总体架构 ...
- 原理剖析(第 013 篇)应用系统性能调优
原理剖析(第 013 篇)应用系统性能调优 - 一.大致介绍 1. 本人接手的一个打车系统,因为出现了一次响应十分缓慢的情况,因此才有了应用调优的篇章: 2.由于过程中可能没有阐述的太清楚,如想详细了 ...
- 【建议收藏】15755 字,讲透 MySQL 性能优化(包含 MySQL 架构、存储引擎、调优工具、SQL、索引、建议等等)
0. 目录 1)MySQL 总体架构介绍 2)MySQL 存储引擎调优 3)常用慢查询分析工具 4)如何定位不合理的 SQL 5)SQL 优化的一些建议 1 MySQL 总体架构介绍 1.1 MySQ ...
- jvm调优工具_JVM性能调优监控工具jps、jstack、jmap、jhat、hprof使用详解
来自:ITeye博客, 作者:Josh_Persistence 链接:https://www.iteye.com/blog/josh-persistence-2161848 现实企业级Java应用开发 ...
- Jvm 系列(七):Jvm 调优-工具篇
工具做为图形化界面来展示更能直观的发现问题,另一方面一些耗费性能的分析(dump文件分析)一般也不会在生产直接分析,往往dump下来的文件达1G左右,人工分析效率较低,因此利用工具来分析jvm相关问题 ...
最新文章
- 电子商务创造的第二次产业机会
- CVPR 2019 | 一种用于年龄估计的连续感知概率网络
- MMU关闭时Cache的缓存策略是怎样的
- 端口安全原理介绍及配置命令
- tcp port numbers reused出现原因_python socket(tcp 线程)实现简单聊天室
- 学Java技术,这些问题要避免!
- RocketMQ初步应用架构理论
- Leetcode每日一题:52.N-Queens II(N皇后Ⅱ)
- WM_TIMER消息在线程被阻塞时的系统处理
- jmeter 分布式注意事项
- linux smartctl 命令,在 CentOS 7 里用 smartctl 和 hdparm 对硬盘进行基本测试
- 最新cs1.6服务器ip地址,CS1.6服务器IP地址
- edge设置IE兼容模式
- github python100天_GitHub - ychgithub/Python-100-Days: Python - 100天从新手到大师
- python三级菜单
- java类与接口思维导图_详解java接口基础知识附思维导图
- C语言基础之5:运算符、表达式和语句
- 摄像头poe供电原理_无线监控中poe供电原理图解
- buuoj Pwn writeup 106-110
- 《鸟哥Linux私房菜》——第九章、文件与文件系统的压缩与打包
热门文章
- 30万美元:Zerodium 出3倍价格求 WordPress RCE exploit
- 苹果 “Find My” 功能中的漏洞可暴露用户的位置信息
- 速修复!开源 IT 基础设施管理解决方案 Salt 被曝多个严重漏洞
- Npm 恶意包试图窃取 Discord 敏感信息和浏览器文件
- Promise面试题
- 要把人工智能提速50倍的ARM,却依然坚持做“通用的计算架构”
- 学号:201621123032 《Java程序设计》第3周学习总结
- android手机状态解释,比方android.os.Build.VERSION.SDK
- 为什么用 Java:一个 Python 程序员告诉你
- PenMount Touch显示鼠标指针驱动安装