nginx静态文件Cache配置

一直不知道nginx自带了静态文件Cache功能,现在可好了,可以优化一下我的博客了。

开启功能:

1.  open_file_cache max=65535 inactive=60s;

这个将为打开文件指定缓存,默认是没有启用的,max 指定缓存数量,建议和打开文件数一致,inactive 是指经过多长时间文件没被请求后删除缓存。

2.  open_file_cache_valid 80s;

这个是指多长时间检查一次缓存的有效信息。

3.  open_file_cache_min_uses 1

open_file_cache 指令中的inactive 参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive 时间内一次没被使用,它将被移除。

文件Cache的实现原理:http://www.pagefault.info/?p=123

备注:相较传统read/write方式,2.1版本内核引进的sendfile已经减少了内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区的文件copy,而在内核版本2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然一样,细节与2.1版本的不同之处在于,当文件数据被复制到内核缓冲区时,不再将所有数据copy到socket相关的缓冲区,而是仅仅将记录数据位置和长度相关的数据保存到socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次减少了一次copy操作。

Spark正确编译Jar包

Spark的插件,在配置jar包导出时,默认会把Hadoop及Scala等第三方库打包进JAR包,这会造成文件重复。从而导致各种错误,原因是JAR包中依赖与Spark运行环境的依赖文件重复导致加载错误,从而导致一些异常。a

 

a

转: Hadoop伪分布配置

一、      Hadoop伪分布配置

  1. 在conf/Hadoop-env.sh文件中增加:export JAVA_HOME=/home/Java/jdk1.6
  2. 在conf/core-site.xml文件中增加如下内容:

<!–  fs.default.name – 这是一个描述集群中NameNode结点的URI(包括协议、主机名称、端口号),集群里面的每一台机器都需要知道NameNode的地址。DataNode结点会先在NameNode上注册,这样它们的数据才可以被使用。独立的客户端程序通过这个URI跟DataNode交互,以取得文件的块列表。–>

 <property>

<name>fs.default.name</name>

<value>hdfs://localhost:9000</value>

</property>

<!—hadoop.tmp.dir 是hadoop文件系统依赖的基础配置,很多路径都依赖它。如果hdfs-site.xml中不配                     置namenode和datanode的存放位置,默认就放在这个路径中–>

<property>

<name>hadoop.tmp.dir</name>

<value>/home/hdfs/tmp</value>

</property>

  1. 在conf/hdfs-site.xml中增加如下内容:

<!– dfs.replication -它决定着 系统里面的文件块的数据备份个数。对于一个实际的应用,它 应该被设为3(这个           数字并没有上限,但更多的备份可能并没有作用,而且会占用更多的空间)。少于三个的备份,可能会影响到数据的         可靠性(系统故障时,也许会造成数据丢失)–>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

 

<!–  dfs.data.dir – 这是DataNode结点被指定要存储数据的本地文件系统路径。DataNode结点上            的这个路径没有必要完全相同,因为每台机器的环境很可能是不一样的。但如果每台机器上的这            个路径都是统一配置的话,会使工作变得简单一些。默认的情况下,它的值hadoop.tmp.dir, 这             个路径只能用于测试的目的,因为,它很可能会丢失掉一些数据。所以,这个值最好还是被覆                 盖。

dfs.name.dir – 这是NameNode结点存储hadoop文件系统信息的本地系统路径。这个值只对NameNode有效,DataNode并不需要使用到它。上面对于/temp类型的警告,同样也适用于这里。在实际应用中,它最好被覆盖掉。–>

<property>

<name>dfs.name.dir</name>

<value>/home/hdfs/name</value>

</property>

<property>

<name>dfs.data.dir</name>

<value>/home/hdfs/data</value>

</property>

 

 

<!—解决:org.apache.hadoop.security.AccessControlException:Permission                                                 denied:user=Administrator,access=WRITE,inode=”tmp”:root:supergroup:rwxr-xr-x 。

因为Eclipse使用hadoop插件提交作业时,会默认以 DrWho 身份去将作业写入hdfs文件系统中,对应的也就是 HDFS 上的/user/hadoop ,  由于 DrWho 用户对hadoop目录并没有写入权限,所以导致异常的发生。解决方法为:放开 hadoop 目录的权限, 命令如下 :$ hadoop fs -chmod 777 /user/hadoop –>

<property>

<name>dfs.permissions</name>

<value>false</value>

<description>

If “true”, enable permission checking in HDFS. If “false”, permission checking is turned                     off,   but all other behavior is unchanged. Switching from one parameter value to                                   the other does   not change the mode, owner or group of files or directories

</description>

 

</property>

 

  1. 在conf/mapred-site.xml中增加如下内容:

<!– mapred.job.tracker -JobTracker的主机(或者IP)和端口。–>

<property>

<name>mapred.job.tracker</name>

<value>localhost:9001</value>

</property>

二、操作命令

  1. 格式化工作空间

进入bin目录,运行 ./hadoop namenode –format

  1. 启动hdfs

进入hadoop目录,在bin/下面有很多启动脚本,可以根据自己的需要来启动。

* start-all.sh 启动所有的Hadoop守护。包括namenode, datanode, jobtracker, tasktrack

* stop-all.sh 停止所有的Hadoop

* start-mapred.sh 启动Map/Reduce守护。包括Jobtracker和Tasktrack

* stop-mapred.sh 停止Map/Reduce守护

* start-dfs.sh 启动Hadoop DFS守护Namenode和Datanode

* stop-dfs.sh 停止DFS守护

三、Hadoop hdfs 整合

     可按如下步骤删除和更改hdfs不需要的文件:

1.将hadoop-core-1.0.0.jar 移动到lib目录下。

  1. 将ibexec目录下的文件移动到bin目录下。
  2. 删除除bin、lib、conf、logs之外的所有目录和文件。
  3. 如果需要修改日志存储路径,则需要在conf/hadoop-env.sh文件中增加:

export    HADOOP_LOG_DIR=/home/xxxx/xxxx即可。

四、HDFS文件操作

Hadoop使用的是HDFS,能够实现的功能和我们使用的磁盘系统类似。并且支持通配符,如*。

  1. 查看文件列表

查看hdfs中/user/admin/hdfs目录下的文件。

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs -ls /user/admin/hdfs

查看hdfs中/user/admin/hdfs目录下的所有文件(包括子目录下的文件)。

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs -lsr /user/admin/hdfs
  3. 创建文件目录

查看hdfs中/user/admin/hdfs目录下再新建一个叫做newDir的新目录。

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs -mkdir /user/admin/hdfs/newDir
  3. 删除文件

删除hdfs中/user/admin/hdfs目录下一个名叫needDelete的文件

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs -rm /user/admin/hdfs/needDelete

删除hdfs中/user/admin/hdfs目录以及该目录下的所有文件

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs -rmr /user/admin/hdfs
  3. 上传文件

上传一个本机/home/admin/newFile的文件到hdfs中/user/admin/hdfs目录下

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs –put /home/admin/newFile /user/admin/hdfs/
  3.       下载文件

下载hdfs中/user/admin/hdfs目录下的newFile文件到本机/home/admin/newFile中

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs –get /user/admin/hdfs/newFile /home/admin/newFile
  3.       查看文件内容

查看hdfs中/user/admin/hdfs目录下的newFile文件

  1. 进入HADOOP_HOME目录。
  2. 执行sh bin/hadoop fs –cat /home/admin/newFile

hadoop单机配置

1.配置互信,否则在每次启动时,会要求输入密码,且这也是设置随机启动的必要条件。
$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 0600 ~/.ssh/authorized_keys

2.检验互信是否配置成功
$ ssh localhost
$ exit

3.解压haddop,以hadoop2.7.3版本为例,使其解压至/hadoop-2.7.3

4.编辑系统环境变量(vi /etc/profile),添加以下定义。
export HADOOP_HOME=/hadoop-2.7.3
export HADOOP_INSTALL=$HADOOP_HOME
export HADOOP_MAPRED_HOME=$HADOOP_HOME
export HADOOP_COMMON_HOME=$HADOOP_HOME
export HADOOP_HDFS_HOME=$HADOOP_HOME
export YARN_HOME=$HADOOP_HOME
export HADOOP_COMMON_LIB_NATIVE_DIR=$HADOOP_HOME/lib/native
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

5.编辑hadoop-env.sh启动环境文件(vim /hadoop-2.7.3/etc/hadoop/hadoop-env.sh),添加JAVA_HOME,如下。
export JAVA_HOME=/jdk1.8.0_101/

6.编辑core-site.xml文件( vim /hadoop-2.7.3/etc/hadoop/core-site.xml ),添加如下:
<configuration>
<property>
<name>fs.default.name</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>

<!– fs.default.name – 这是一个描述集群中NameNode结点的URI(包括协议、主机名称、端口号),集群里面的每一台机器都需要知道NameNode的地址。DataNode结点会先在NameNode上注册,这样它们的数据才可以被使用。独立的客户端程序通过这个URI跟DataNode交互,以取得文件的块列表。–>

7.编辑hdfs-sizte.xml文件(vim /hadoop-2.7.3/etc/hadoop/hdfs-site.xml ),添加如下:
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>

<property>
<name>dfs.name.dir</name>
<value>file:///root/hdfs/namenode</value> #等价于<value>/root/hdfs/namenode</value>
</property>

<property>
<name>dfs.data.dir</name>
<value>file:///root/hdfs/datanode</value>
</property>
</configuration>

<!– dfs.replication -它决定着 系统里面的文件块的数据备份个数。对于一个实际的应用,它 应该被设为3(这个数字并没有上限,但更多的备份可能并没有作用,而且会占用更多的空间)。少于三个的备份,可能会影响到数据的可靠性(系统故障时,也许会造成数据丢失)–>
<!– dfs.data.dir – 这是DataNode结点被指定要存储数据的本地文件系统路径。DataNode结点上的这个路径没有必要完全相同,因为每台机器的环境很可能是不一样的。但如果每台机器上的这个路径都是统一配置的话,会使工作变得简单一些。默认的情况下,它的值hadoop.tmp.dir, 这个路径只能用于测试的目的,因为,它很可能会丢失掉一些数据。所以,这个值最好还是被覆盖。
dfs.name.dir – 这是NameNode结点存储hadoop文件系统信息的本地系统路径。这个值只对NameNode有效,DataNode并不需要使用到它。上面对于/temp类型的警告,同样也适用于这里。在实际应用中,它最好被覆盖掉。–>

8.编辑mapred-site.xml(vim /hadoop-2.7.3/etc/hadoop/mapred-site.xml),添加如下:
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
9.编辑yarn-site.xml(vim /hadoop-2.7.3/etc/hadoop/yarn-site.xml),添加如下:
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
</configuration>

10.初始化HDFS
$ hdfs namenode -format

11.启动和停止
$ $HADOOP_HOME/sbin/start-all.sh
$ $HADOOP_HOME/sbin/stop-all.sh

12.检查相关节点启动。
http://localhost:8088
http://localhost:50070
http://localhost:50090
http://localhost:50075

● port 8088: cluster and all applications
● port 50070: Hadoop NameNode
● port 50090: Secondary NameNode
● port 50075: DataNode

spark单机版

根据以下步骤,建立单机版。
部署环境,也即主机,内存大于4G为宜,因为spark在执行一项任务时,至少会启动2个woker线程,每个节点为至少1G内存,这也就要求至少2G内存。如果再有HADOOP的话,4G内存是属于最低配置了。

1.配置单机互信,如果不配置,则在启动hadoop或spark的过程中,会要求输入密码,且“互信”是配置随机自启动的必要条件。
$ ssh-keygen -t rsa
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
$ chmod 0600 ~/.ssh/authorized_keys

2.Spark配置,假定spark安装在/spark-2.0.1目录下。
复制配置模板:cp /spark-2.0.1/conf/spark-env.sh.template /spark-2.0.1/conf/spark-env.sh
编辑它:vi /spark-2.0.1/conf/spark-env.sh,增加以下定义。
export JAVA_HOME=/jdk1.8.0_101/ #如果没有设置该项,会在启动过程中提示该项,甚至可能失败。
export SPARK_MASTER_HOST=192.168.239.137 #如果没有设置该项,则默认以SPARK_MASTER_HOST=”hostname” -f替代,且必须配置相应的hosts文件为ip,否则无法被外网访问。
export SPARK_WORKER_MEMORY=1G     #默认就是1G,不能小于1G,例如512M,它会资源不足的错误。
3.添加环境变量,vim /etc/profile,这个并非必要,只是为了方便直接调用脚本罢了。
export SPARK_HOME=/spark-2.0.1
export PATH=$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH

4.启动或停止Spark,为了防止与hadoop等其它脚本混淆,也可重命名它为spark-start-all
start-all.sh
stop-all.sh

5.重启(或source /etc/profile)并验证:
spark-submit –class=org.apache.spark.examples.SparkPi –master=spark://192.168.239.137:7077 /spark-2.0.1/examples/jars/spark-examples_2.11-2.0.1.jar
spark-submit –class=org.apache.spark.examples.LogQuery –master=spark://192.168.239.137:7077 /spark-2.0.1/examples/jars/spark-examples_2.11-2.0.1.jar

修改ROOT密码

在CentOS的安装过程中,需要输入6位数的ROOT密码,而本人是习惯使用123作为密码,这会导致在这安装过程是无法达到目的,该怎么办呢? 

  1. 按界面要求输入6位数的密码。 
  2. 在安装结束,登录系统后,使用passwd root的命令进行修改。

配置网络地址

在虚拟机中安装Centos6.6服务器版,需要手动配置IP地址的DHCP参数,具体命令如下:
1.vi /etc/sysconfig/network-scripts/eth0或vi /etc/sysconfig/network-scripts/icfg-ens03
BOOTPROTO=dhcp //默认为dhcp。
ONBOOT=yes //默认为no,改为yes,使它自动获取。

2.虚拟机是支持NAT或Bridge等模式的,每次切换模式时,均需要执行以下命令,进行切换网络。
service network restart

Jetbrain-IDE大项目性能优化

1.修改vmoptions配置文件,该文件存在以下几个位置,需要修改用户自定义配置的文件,才有效。以CLion-IDE为例。
/clion-2016.2/bin/clion64.vmoptions,如果修改这个参数,则需要把一下行的配置中同名清除掉,否则不会生效。
/home/abc/.CLion2016.2/clion64.vmoptions,建议修改这个,本人是亲测这个参数的修改。
vmoptions的加载优先级是,先加载bin目录下,再加载user目录下,在合并参数时,后者会自动覆盖前者。
2.修改以下几个参数。
原始参数如下:
-Xss2m
-Xms256m
-Xmx2000m
-XX:NewSize=128m
-XX:MaxNewSize=128m
-XX:ReservedCodeCacheSize=96m
修改为如下:
-Xss2m
-Xms1024m
-Xmx4096m
-XX:NewSize=128m
-XX:MaxNewSize=128m
-XX:ReservedCodeCacheSize=1024m
3.检测是否生效,启动CLion,执行ps -ef|grep java。
ps -ef|grep java
abc 3961 3909 16 01:04 ? 00:01:48 /clion-2016.2/bin/../jre/jre/bin/java -Xbootclasspath/a:/clion-2016.2/bin/../lib/boot.jar -classpath /clion-2016.2/bin/../lib/bootstrap.jar:/clion-2016.2/bin/../lib/extensions.jar:/clion-2016.2/bin/../lib/util.jar:/clion-2016.2/bin/../lib/jdom.jar:/clion-2016.2/bin/../lib/log4j.jar:/clion-2016.2/bin/../lib/trove4j.jar:/clion-2016.2/bin/../lib/jna.jar -Xss2m -Xms1024m -Xmx4096m -XX:NewSize=128m -XX:MaxNewSize=128m -XX:ReservedCodeCacheSize=1024m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -ea -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Dawt.useSystemAAFontSettings=lcd -Djb.vmOptionsFile=/home/abc/.CLion2016.2/clion64.vmoptions -XX:ErrorFile=/home/abc/java_error_in_CL_%p.log -XX:HeapDumpPath=/home/abc/java_error_in_CL.hprof -Djb.restart.code=88 -Didea.paths.selector=CLion2016.2 -Didea.platform.prefix=CLion -Didea.no.jre.check=true com.intellij.idea.Main
4.以真实大项目验证吧,如PHP5.57的源码分析,在没有配置前,CLion一直都卡在build symbols这步骤,最终爆内存错误,修改为大内存版的配置后,可以成功完成所有步骤。
5.以上4个步骤后,仍然失败,则检查一下是否虚拟机的内存配置太少了,按上述配置虚拟机物理内存至少为4G,才可以顺利执行。

Xhprof源码分析

核心监控流程:

  1. 初始化监控:通过xhprof_enable函数,设置函数钩子和统计函数。涉及的主要函数钩子有如下:

    zend_compile_file:当使用include\require\include_one\require_once函数加载文件时,均会经历编译过程。这编译过程就是通过该函数完成。

    zend_execute_ex:php引擎每执行一个函数,均会通过该函数进行调用,像xdebug也是通过该函数建立运行堆栈信息,xhprof也不例外,钩子建立过程。

    _zend_execute_ex = zend_compile_file

    zend_compile_file = hp_execute_ex

    zend_execute_internal:该函数只有xhprof_enable配置了XHPROF_FLAGS_NO_BUILTINS参数时,才会被拦截。

  2. 监控核心过程:被动地监控每个函数的执行,由于函数HOOKER的机制,PHP每执行一个函数都将进入钩子的回调函数中,并在相应的回调函数中执行以下过程:

    BEGIN_PROFILING->function execute->END_PROFILING

    函数式表示如下:

    Function hp_execute_ex() //这是钩子的回调函数。

    {

        BEGIN_PROFILING; //开始分配内存、初始化捕获的开始时间,内存值等

        _zend_execute_ex(); //执行函数内容。

        END_PROFILING; //计算函数执行的成本:CPU和内存

    }

    另外三个函数,也是采用同样的方式。

  3. 结束监控,返回数据。

 

源码分析

  1. 初始化函数钩子。并默认把main()函数当作为监控入口,它是一个虚拟函数,目的是表示监控的开始和结束,xhprof_enable调用表示开始执行该虚拟函数,xhprof_disable表示结束该虚拟函数。

 

 

  1. 由于钩子的拦截关系,PHP引擎每执行一个函数,均会进入相应钩子回调中,如hp_execute_ex函数为例。重点关注两个宏定义,分别是BEGIN_PROFILING和END_PROFILING.

  1. BEGIN_PROFILING和END_PROFILING的定义如下:

    这两个宏完成以下三件事:

    1. 分配栈帧,每一个hp_entry_t结构,是栈结构上的一个元素。它记录了当前函数的开始信息,如当前函数名、递归深度、开始时间、开始内存。

      注:它不保存当前函数的结束信息,结束的信息会减去开始信息,然后把结果输出到另一个数组中。

    2. hp_mode_common_beginfn函数是用于执行压栈动作,并检测栈中是否存在递归函数;hp_mode_common_endfn函数用于执行弹栈动作。
    3. begin_fn_cb函数是用于填充hp_entry_t结构信息,也即是函数的开始信息;end_fn_cb函数是用于计算函数的消耗结果,并把该结果输出到另外一个全局数组中,该数组并没有体现在这两个宏定义中。
  2. 在xhprof实际使用过程中,常会看到如下一些情况,举例说明:

    PHP代码如下:

    Xhprof的图表中输出如下:

    图表中的@1,@2,@3代表什么?这个数字代表这个函数在执行过程中的递归深度,具体算法如下。

    这段源码部份变量解释如下:

    func_hash_counters,全局数组是用于快速判断当前栈列表中是否存在同码函数

    hash_code,记录是函数的hash码,xhprof把函数转换为一个0~255的整数。

    name_hprof,记录是函数的真正名字,如bar这个函数。

    rlvl_hprof,记录的是当前函数在栈列表中深度。

    entries,永远指向栈列表的栈顶。

    综上所述,该段函数的功能是指函数在被压入栈表前,由栈顶向栈底搜索是否存在同名函数,如果栈表已经存在同名函数,则表示当前正在执行函数递归操作,并记录当前函数的递归深度。

    下面函数应该可以加强上述的理解。

    hp_get_entry_name就是获取函数名。

  3. 偶然也会遇到如下一些现象,依旧举例说明。

    Php代码如下:

    Xhprof图表如下:

    load::my/MyTest.php也是一个虚拟的函数,它的功能是代表把MyTest.php由源文件翻译成opcode文件的消耗。关键算法截图如下:

该函数解析如下:

zend_compile_file,该函数是把php源文件翻译成opcode指令。

hp_get_base_filename,是把文件路径,截取最后两段路径名作函数名返回来,如下所示

/alidata/www/xhprof/example/../my/MyTest.php => my/MyTest

/a/b/c/a.php =>c/a

???_op,函数是无法获取函数名字的一个默认名称。

  1. Xhprof_disable()返回原始输出结果。

    源码如下:

    以JSON格式输出原始结果如下:

    {“foo==>bar”:{“ct”:5,”wt”:38,”cpu”:0,”mu”:5008,”pmu”:0},”bar==>bar@1″:{“ct”:5,”wt”:22,”cpu”:0,”mu”:4928,”pmu”:0},”bar@1==>bar@2″:{“ct”:4,”wt”:14,”cpu”:0,”mu”:3888,”pmu”:0},”bar@2==>bar@3″:{“ct”:3,”wt”:9,”cpu”:0,”mu”:2848,”pmu”:0},”bar@3==>bar@4″:{“ct”:2,”wt”:3,”cpu”:0,”mu”:1808,”pmu”:0},”main()==>foo”:{“ct”:1,”wt”:80,”cpu”:0,”mu”:6080,”pmu”:0},”bar@4==>bar@5″:{“ct”:1,”wt”:0,”cpu”:0,”mu”:752,”pmu”:0},”main()==>bar”:{“ct”:1,”wt”:8,”cpu”:0,”mu”:1792,”pmu”:0},”main()”:{“ct”:1,”wt”:114,”cpu”:0,”mu”:9240,”pmu”:0}}

    这个JSON如何转换到以下图表呢?

    从图表虽然很直观,但不能很好的反映出它与原始JSON内容的关系,我们选择其中一项,如bar项,展开其父、我、子,这种爷孙三代关系图,截图如下:

    “foo==>bar”:{“ct”:5,”wt”:38,”cpu”:0,”mu”:5008,”pmu”:0}

    “main()==>bar”:{“ct”:1,”wt”:8,”cpu”:0,”mu”:1792,”pmu”:0}

    “bar==>bar@1”:{“ct”:5,”wt”:22,”cpu”:0,”mu”:4928,”pmu”:0}

    通过对比,可以知道Excl.Wall_Time运算公式如下:

    Excl.Wall_Time = Current Function.Incl.Wall_Time – Child Function.Incl.Wall_Time。

    cpu的消耗,个人认为它与wt消耗是重复了。首先cpu的实现方式相比wt的实现方式,毫无优势。如下是它们的对比:

    1. cpu和wt均是时间差计算出来的。
    2. cpu的时间精度为微秒,而wt的时间精度是纳秒。
    3. 每执行一个函数,函数消耗不足微秒时,cpu因精度关系,计算结果为0,导致失误增大,而wt计算结果会被正确地累计。

    mu内存消耗的理解,与wt基本一致。