注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

我的个人博客

欢迎访问我的网站www.shaccp.cn,学习软件编程

 
 
 

日志

 
 

代码中的无形性能损耗  

2014-11-17 13:31:12|  分类: 默认分类 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

最近研究了代码底层的一些实现机制,发现很多“真相”,总结了一下,形成本文。下面有点瞎扯(扯完我也不知道要给大家强调什么)如有不同意见,欢迎大家拍砖。

 

关于我,邯郸人
对这类话题感兴趣?欢迎发送邮件至donlianli@126.com
请支持原创http://donlianli.iteye.com/blog/2156656
 

1、编程语言本身带来的损耗

Java语言编写的程序(class文件),程序需要首先运行java虚拟机(先运行java.exe,建立java虚拟机,加载、验证、准备、解析和初始化class等一系列步骤),然后才能运行你的main方法。

同样.net平台运行的程序也一样,另外新兴的ruby,Scala等也是运行在Java虚拟机上面的,所以肯定也存在这样的问题。

 

这些语言还有另外一个问题,就是这些语言属于解释性语言。尽管有git技术的加速,但仍然逃脱不了其天生的劣势。运行速度和编译性语言相比,还是差不少的。尤其是像JS(Javascript)中这种语言,单线程执行,当一个网页包含的js过多时,会出现客户端无响应的问题。这种情况直到出现chrome浏览器和IE8之后,问题才得到缓解。

 

2、语言本身插入的"通用代码"

下面几行简单的c代码

int main(void){

    return 0;

}

编译成可执行文件(linux64 OS)后,竟然多出12个函数代码(需要查看汇编指令)。也就是说为了运行你的一句return 0,系统需要在你的代码前和代码后面执行多达12次调用。

其实这种情况很普遍,我就拿我很熟悉的Java语言为例,为了支持gc,jvm需要在你的Java代码中种植很多安全点,以方便在gc时,各个线程在安全点停止下来(线程是主动停止下来等待gc)。这些安全点都是语言在你的代码中插入的无形损耗。为了让同步的变量可见,Java会在某些代码中间插入CyclicBarrier,让多个需要立即可见的变量互相等待一下,然后让cpu将变量刷回主存。

 

3、可执行程序加载

所有的程序都会有这个问题,不可避免。原因是所有程序都在OS的管理下运行的,除非你的程序就是操作系统。

在程序开始运行前,OS要做以下工作:

  • 创建进程PCB(进程属于新建态),
  • 将exe文件加载到内存
  • 映射虚拟内存,分配页表,设置MMU
  • 操作系统为程序运行分配必须的资源(如输入输出文件等).

通常,因此这种损耗基本上可以忽略。

 

4、模式切换

从OS调度来说一般有线程切换和进程切换,现在的OS一般是以线程为单位进行调度,无论是线程切换还是进程切换,都会引起用户模式和内核模式的切换。因此线程切换,虽然损耗比较小,但当线程的数量非常多的时候,其损耗也是不可忽略的。

线程切换需要进行的工作:

step 1. 保存被中断(一般上下文切换都是由中断引起的,比如说时间片用完)线程A的CPU相关寄存器。

step 2. 保存现程控制块(TCB)相关信息,标志现程当前状态(如果是时间片用完,则属于就绪态)

step 3. 根据当前线程的状态,将其放入相关队列(假如是时间片用完,则放入就绪队列)

step 4. 从就绪队列选择另外一个线程B(可能会有一个算法在这里面,因为队列有很多就绪线程,怎么选)

step 5. 修改线程B的线程控制块(TCB),标记其为运行态。

step 6. 恢复线程B的CPU现场

 

我们的代码中,如果需要大量的io时,这时你要注意了,通常每次调用io函数,都会发生内核模式和用户模式切换。这种切换速度是非常快的,但一般的处理器也有硬件支持。

用户模式切内核模式进行的工作

step1 根据线程(进程)状态,将线程放到挂起队列末尾。

step2 保存用户的CPU的寄存器现场

step2 按照io号去处理。。。。(这个可能会很长)

在linux中,可以通过vmstat 的cs(context switch)列看到上下文切换的次数

5、函数调用

其实我们的程序入口都是从一个main函数开始,然后就是一层又一层的函数调用,这个是不可避免的。这方面的损耗也比较小。其主要损耗在于调用参数及现场保护。

当调用一个参数时,需要进行以下一些列工作

  • 参数入栈(将函数调用的参数压入函数栈,参数越多,耗时越长)
  • 跳转地址(跳转前还需要保护当前代码地址)
  • 函数调用
  • 函数调用完毕,逆过程(回跳至调用者代码,释放之前入栈参数空间)

在64位系统,AMD对函数堆栈稍微做了些改变,当函数参数正数不超过6个,浮点数参数不超过8个时,可以将参数直接存放在寄存器中,不用再将参数进行入栈。但这个优化似乎不起太大作用,因为一旦函数调用超过2层,同样还需要将参数入栈。

从上面的过程,函数参数有个入栈工程,通常函数的内的局部变量也在函数站内。一般这个栈的大小是有限制的(Java中默认是512k)。如果你的函数调用太深或者你的函数出现了死循环,cpu就会不断的往函数栈中push参数,直到这个栈满了,就出现了stack overflow的异常。

 

6、os与用户程序之间的数据传递损耗

比如用户需要读取一个配置文件,首先,文件数据到达操作系统的缓冲区,然后,再传递给用户的目的缓存区(至于为什么这么做,大家可以看看Java nio编程),甚至有时候,我们代码传递到用户缓存区,还没有到达正确的位置,可能还要转换成某个对象,存放到共享数据区。



 


<!--[if !supportLineBreakNewLine]-->
<!--[endif]-->

总结:记得有人说过这样的话,通用即难用。现在想想是有一定道理的,上面的每一种损耗,都是为了让我们的语言更优雅,或者为了降低我们编程的难度而采取的措施。我老觉得苹果手机比android手机快,可能很大一部分原因是android采用的Java引起。尽管上面所述有些损耗都非常小,以至于我们根本无法察觉。但只要执行指令就会耗电,而且冗余指令越多,耗电量越大。本文由上海java培训机构推荐阅读,更多精彩请浏览上海it培训官网。http://www.shaccp.cn/

  评论这张
 
阅读(26)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017