ubuntu下gitosis的安装

还是使用gitlab吧~~~~ https://about.gitlab.com/

参考 ubuntu wiki上的文章: Git服务器Gitosis安装设置

毫无疑问,工具的安装过程中总是会出现教程未提及的错误处理,解决起来很是麻烦,在此记录一二,也许对未来人有帮助。

1 获取并安装gitosis

教程里是 git clone git://eagain.net/gitosis.git,但是我clone不下来,于是取github上获取,项目地址:https://github.com/res0nat0r/gitosis.git

2 gitosis 安装后,会有3个可执行命令生成到 /usr/local/bin 下

gitosis-server gitosis-ini gitosis-run-hook

但是安装过程中提示 bash: gitosis-serve: command not found 等错误,解决方案

软链这几个命令到  /usr/bin 目录之下。

3 gitweb安装之后,倒不用像 wiki 里面那么复杂的配置

<VirtualHost *:80>
        ServerName www.yooshang.com
        ScriptAlias /gitweb/ /usr/lib/cgi-bin/
        DirectoryIndex /gitweb/gitweb.cgi
        DocumentRoot /home/git/gitweb
</VirtualHost>

设置了 gitweb 的解析路径为 /home/git/gitweb 把,gitweb需要引用的静态资源软链到此路径

sudo vi /etc/gitweb.conf 修改 $projectroot 改为git仓库存储目录(例如:/home/git/repositories),apache进程需要有该路径的755权限,
那么所有的可读项目都会暴露出去了,不好控制。
所以我的做法是在gitweb创建一个 repositories 目录,我需要在 gitweb 展示的仓库,则软链到 repositories 路径下。

emacs下的less-mode

在淘宝的时候就调研过less,一个强大的 CSS 预处理器,但是一直没有整合到项目里。

实践才能出真知,宇宙最新、最强的东西一定要一股脑塞进mss项目里去。

less项目相关

github上的less.js项目,相关使用教程:http://lesscss.org/ (需要翻墙)

emacs下的less文件编辑

emacs下的css-mode无法满足less的嵌套对齐问题,纠结,不过强大的github应该有less-mode的实现,否则我也乘机学习下lisp整一个出来?哈,没那么简单。

运气不错,github上的emacs下less-mode插件

下载 less-mode.el 添加进emacs的配置路径,在 .emacs 添加以下配置

(require 'less-mode) 
(add-to-list 'auto-mode-alist '("\\.less$" . less-mode))

If you use smart-compile:

(add-to-list 'smart-compile-alist '(less-mode . (less-compile)))

If you use Flymake, it will work automatically.

该扩展支持:保存文件时候的自动编译

之前一直臆测,coffeescript,lesscss 服务端编译的话,每次调式都得手动执行编译命令,岂不是非常的烦?

强大的emacs,保存时自动编译可以成为解决方案之一,完全不会有开发时候的不爽呀。

例如:less/xx.less -> css/xx.css, 项目引用 css 路径下的文件,真实编辑的文件是 less 路径下对应的文件。

less-mode.el 继承的也是css-mode, 依旧有缩进的问题,elisp又不熟悉,只能简单的调整了下,不要css-mode的关键词高亮了,能对齐就好了。

改动的地方很少,

(define-derived-mode less-mode css-mode "Less" 
改为 
(define-derived-mode less-mode c-mode "Less"

并添加了2个缩进设置

(c-set-offset 'label' +)
(c-set-offset 'defun-close' /)

根据个人习惯改变了编译命令,会自动的把less文件编译并压缩到css路径

/home/yoo/music/public/less/css.less -> /home/yoo/music/public/css/css.less.css

完整的内容如下:

(require 'derived)
(require 'compile)

(defconst less-font-lock-keywords
  '(("@[^\s:;]+" . font-lock-constant-face)
    ("//.*$" . font-lock-comment-face)))

(defgroup less nil
  "Less mode"
  :prefix "less-"
  :group 'css)

(defcustom less-lessc-command "lessc --no-color"
  "Less compiler command"
  :group 'less)

(defcustom less-lessc-command-c "lessc --no-color -x"
  "Less compiler command"
  :group 'less)

(defcustom less-compile-at-save t
  "If not nil, Less buffers will be compiled on each save"
  :type 'boolean
  :group 'less)

(defcustom less-mode-hook nil
  "Hook run when entering Less mode"
  :type 'hook
  :group 'less)

(defun less-compile ()
  "Compiles the current buffer"
  (interactive)
  ;;(compile (concat less-lessc-command " " buffer-file-name))                                                                                                            
  (compile (concat less-lessc-command-c " " buffer-file-name " > " (replace-regexp-in-string "/less/" "/css/" buffer-file-name) ".css")))

(defun less-compile-maybe ()
  "Runs `less-compile' on if `less-compile-at-save' is not nil"
  (if less-compile-at-save
      (less-compile)))

(defun flymake-less-init ()
  (let* ((temp-file (flymake-init-create-temp-buffer-copy
                     'flymake-create-temp-inplace))
         (local-file (file-relative-name
                      temp-file
                      (file-name-directory buffer-file-name))))
    (list "lessc" (list "--no-color" local-file))))

(when (featurep 'flymake)
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.less$" flymake-less-init))
  (add-to-list 'flymake-err-line-patterns
               '("! \\(.*\\): on line \\([0-9]+\\): \\(.*\\)"
                 nil 2 nil 3))
  (add-to-list 'flymake-err-line-patterns
               '("! \\(.*\\): \\(.*\\)"
                 nil nil nil 2)))

(define-derived-mode less-mode c-mode "Less"
  "Major mode for editing Less files, http://lesscss.org"
  (run-hooks 'css-mode-hook)
  (c-set-offset 'label' +)
  (c-set-offset 'defun-close' /)
  (when (featurep 'flymake) (flymake-mode t))
  (font-lock-add-keywords nil less-font-lock-keywords)
  (add-hook 'after-save-hook 'less-compile-maybe nil t))

(provide 'less-mode)
;;; less-mode.el ends here

捉鬼游戏帮助

清明节去浙江舟山旅游的时候,同学教会了一个游戏,叫捉鬼,玩的挺有意思的,于是用51的时间用nodejs和socket.io简单开发了一个在线版,网址 http://zg.yooshang.com,游戏的规则感觉挺简单的。

规则:

1 人群里每个人都分配到一个词语

2 其中只有一个人的词和别人的不一样

3 大家通过每一轮的轮流阐述,找到鬼的存在,

4 而是鬼的人,需要通过别人的描述判断出自己是鬼,然后进行伪装

 

简单模式只有2种角色:

人 — 大部门的玩家都是人,他们有一样的词语A。

鬼 — 只有一个人是词语B

 

胜利条件:

鬼 — 剩余玩家数<=2,鬼赢得比赛。

鬼被指认后死亡,但是猜对了“人”的词语A,鬼赢得比赛。

人 — 揪出鬼,并不能让鬼猜出自己的词语!

 

流程:

1 系统随机分配每个人的角色

2 可以选择系统随机出题,或者某一个玩家出题,出题的玩家将不能参与之后的比赛。

3 系统随机阐述顺序

4 按照顺序,每个人对自己拿到的词语进行阐述,如自己是“花生”,可以说:“白胖子”

5 第一次描述两轮,触发指认环节,之后就是每一轮描述都会触发指认环节。

6 指认环节就是投票,选择自己认为是鬼的玩家。

7 得票多者将死亡。

8 如果死亡的是“人”,且未达到胜利条件,游戏继续。

9 死亡者是“鬼”,则鬼有一次猜词机会。

10 开始下一轮游戏。

 

界面帮助:

可以自己建一个房间,然后邀请朋友们一起来玩游戏。捉鬼应该是和认识的人玩,一个圈子的人玩才有意思,所以没有暴露出已经存在的房间。

进入房间后,需要输入昵称,点击左侧的图片可选择头像。

点击查看大图

整个界面的功能导图。

功能区会有 准备、开始、系统出题、出题等按钮,游戏进行时,会切换成发言队列,高亮的为当前发言人,发言完毕之后会有声音提示。

捉鬼的乐趣在于和朋友们一起玩,因为有共同的话题,这样才能出一些比较契合的题目。

比如一堆程序员,分配到 nosql 和 mysql,也是蛮好玩的。

技巧:

多数派在发现同伴之后,要把鬼误导到错误的胡同里,让他猜不到词语。

少数派的鬼,要仔细推敲多数派的词语,在已经大致猜到词语后,要混进多数派里。

这个度就是乐趣所在了。

经典案例:

 

Have fun!

 

 

Linode VPS OpenVPN安装配置教程(基于Debian/Ubuntu)

转载至:VPS侦探  链接地址:http://www.vpser.net/build/linode-install-openvpn.html

因为众所周知的原因,目前Gmail等相关服务原来越不稳定,虽然可以通过SSH来解决,但是打开网站一多SSH也有些力不从心,VPS侦探以前发表过在Linode VPS安装PPTP VPN的教程,相对于PPTP来说OpenVPN加密型更强,而且穿透性更强。

本教程也适用其他的基于Debian/Ubuntu Linux的VPS/服务器,也已在DiaHostingVPSYOUPhotonVPS123Systems、oplink上测试通过,如有问题欢迎反馈。(Linode购买及测试教程:http://www.vpser.net/usa-vps/linode.html

1、安装

apt-get install openvpn udev lzop

2、使用easy-rsa生成服务端证书

将OpenVPN所需的配置文件复制到/etc/openvpn/下面:

cp -r /usr/share/doc/openvpn/examples/easy-rsa/ /etc/openvpn/

生产CA证书:

cd /etc/openvpn/easy-rsa/2.0
source vars
./clean-all
./build-ca

./build-ca时会提示输入一些信息,可以都直接回车按默认信息。

生成服务器端证书和密钥,server为名字可以自定义:

./build-key-server server

此步也是会提示输入一些信息,前面的信息直接回车按默认信息,提示Sign the certificate? [y/n]:时输入y,提示1 out of 1 certificate requests certified, commit? [y/n] 也是输入y。

生成客户端证书和密钥,client为名字可以自定义,注意前面的./build-key-server与./build-key client输入的名字不能相同:

./build-key client

前面的信息直接回车按默认信息,提示Sign the certificate? [y/n]:时输入y,提示1 out of 1 certificate requests certified, commit? [y/n] 也是输入y

生成其他的客户端就是执行:./build-key 你想添加的客户端的名字。

生成的证书和密钥存放在/etc/openvpn/easy-rsa/2.0/keys/下面。

生成Diffie Hellman参数:

./build-dh

3、配置OpenVPN服务

编辑/etc/openvpn/server.conf 文件,如果没有可以创建一个,加入下面的内容:

local 服务器IP
port 8080    #端口,需要与客户端配置保持一致
proto udp    #使用协议,需要与客户端配置保持一致
dev tun         #也可以选择tap模式
ca /etc/openvpn/easy-rsa/2.0/keys/ca.crt
cert /etc/openvpn/easy-rsa/2.0/keys/server.crt
key /etc/openvpn/easy-rsa/2.0/keys/server.key
dh /etc/openvpn/easy-rsa/2.0/keys/dh1024.pemifconfig-pool-persist ipp.txtserver 10.168.1.0 255.255.255.0    #给客户的分配的IP段,注意不要与客户端网段冲突!push "redirect-gateway"

push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
client-to-client

;duplicate-cn

keepalive 20 60

comp-lzo

max-clients 50

persist-key
persist-tun

status openvpn-status.log
log-append openvpn.log
verb 3
mute 20

按上述说明修改服务器IP,复制到VPS上是可以把注释信息删除。

安装iptables

apt-get install iptables   #如果已经安装可以跳过

设置IP转发

iptables -t nat -A POSTROUTING -s 10.168.0.0/16 -o eth0 -j MASQUERADE
iptables-save > /etc/iptables.rules

上面的eth0要替换为你的网卡标识,可以通过ifconfig查看。

在/etc/network/if-up.d/目录下创建iptables文件,内容如下:

#!/bin/sh
iptables-restore < /etc/iptables.rules

给脚本添加执行权限:

chmod +x /etc/network/if-up.d/iptables

修改/etc/sysctl.conf的内容为:

net.ipv4.ip_forward = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

重新载入/etc/sysctl.conf使其生效,执行如下命令:

sysctl -p

重启OpenVPN及网络:

/etc/init.d/openvpn restart
/etc/init.d/networking restart

4、安装配置OpenVPN客户端

下载客户端

打开http://openvpn.net/download.html,点击Windows Installer后的链接,下载OpenVPN Windows客户端。

下载完成后,安装,安装中的选项全部按默认即可。

下载客户端证书及密钥:

证书和密钥存放在/etc/openvpn/easy-rsa/2.0/keys/下面,可以使用winscp链接到VPS上下载。

将/etc/openvpn/easy-rsa/2.0/keys/下面的ca.crt、client.crt、client.key下载到C:\Program Files\OpenVPN\config 下面。

创建客户端配置文件

在C:\Program Files\OpenVPN\config 下面创建一个linode.ovpn的文件,添加如下内容:

client
dev tun       #要与前面server.conf中的配置一致。
proto udp              #要与前面server.conf中的配置一致。
remote 服务器IP 8080    #将服务器IP替换为你的服务器IP,端口与前面的server.conf中配置一致。
resolv-retry infinite
nobind
persist-key
persist-tun
ca linodeca.crt
cert linodeclient.crt
key linodeclient.key
ns-cert-type server
redirect-gateway
keepalive 20 60
#tls-auth ta.key 1
comp-lzo
verb 3
mute 20
route-method exe
route-delay 2

5、OpenVPN客户端连接测试:

运行OpenVPN GUI,会在屏幕右下角的系统托盘区,会显示右击该图标,会在菜单中出现我们添加的服务器,点击Connect,OpenVPN客户端就会开通链接OpenVPN服务器,过一会儿,OpenVPN图标变成绿色就是链接成功了。

如果想实现国内网站不走VPN,国外网站走VPN可以看一下:http://code.google.com/p/chnroutes/wiki/Usage 这个教程。

如有问题欢迎反馈,或到VPS论坛交流。

 

在自己的linode vps搭建成功,成功翻墙~

【收集】html5中audio标签的事件、方法和属性

audio标签属于html5中的媒体标签,所以具有html5中的所有媒体事件。事件类型如下表所示。

事件 描述
canplay 当媒介能够开始播放但可能因缓冲而需要停止时运行脚本
canplaythrough 当媒介能够无需因缓冲而停止即可播放至结尾时运行脚本
durationchange 当媒介长度改变时运行脚本
emptied 当媒介资源元素突然为空时(网络错误、加载错误等)运行脚本
ended 当媒介已抵达结尾时运行脚本
error 当在元素加载期间发生错误时运行脚本
onloadeddata 当加载媒介数据时运行脚本
loadedmetadata 当媒介元素的持续时间以及其他媒介数据已加载时运行脚本
loadstart 当浏览器开始加载媒介数据时运行脚本
pause 当媒介数据暂停时运行脚本
play 当媒介数据将要开始播放时运行脚本
playing 当媒介数据已开始播放时运行脚本
progress 当浏览器正在取媒介数据时运行脚本
ratechange 当媒介数据的播放速率改变时运行脚本
readystatechange 当就绪状态(ready-state)改变时运行脚本
seeked 当媒介元素的定位属性 [1] 不再为真且定位已结束时运行脚本
seeking 当媒介元素的定位属性为真且定位已开始时运行脚本
stalled 当取回媒介数据过程中(延迟)存在错误时运行脚本
suspend 当浏览器已在取媒介数据但在取回整个媒介文件之前停止时运行脚本
timeupdate 当媒介改变其播放位置时运行脚本
volumechange 当媒介改变音量亦或当音量被设置为静音时运行脚本
waiting 当媒介已停止播放但打算继续播放时运行脚本

 

audio标签的方法

html5中的媒体标签所提供的方法很简单,如下。

  1. p.load();
  2. p.play();
  3. p.pause();
  4. p.stop();

 

audio标签有如下几个属性

属性 描述
src 音频文件的url地址
preload 是否自动预加载,在设置了autoplay属性时此属性无效。
autoplay 自动开始播放
loop 循环播放
controls 显示浏览器默认播放控制器

 

必读文章:

Unlocking the power of HTML5 <audio>

释放 HTML5 <audio> 的力量

hostCenter firefox插件开发

中秋三天闲来无事,作为一个寂寞的coder,给自己制定了个计划:学习firefox插件开发。

一直想做一个这样的应用,场景如下:

  • 项目A开发过程中,需要告之其他开发人员开发环境host;
  • 项目A测试过程中,需要告之其他开发人员、测试人员、产品等测试环境host;
  • 项目A预发、上线等过程中,需要告之所有人预发环境、线上测试环境host等。

每个人都接收到指令后去配置自己的host,反正我host文件是已经混乱不堪了。

如果有一个hostCenter配置中心,只要一个人维护一个列表,并书于比较明确、区分的注释,那么之后每个开发、测试、产品只需点击一下,即可修改host并打开对应页面。

ok,开发一个这样的firefox插件,走起。

mozilla现在有了一个在线插件开发的工具jetpack,真是imba到极致:

Mozilla Lab Jetpack:开发 Firefox 扩展的新方法,也就是 Add-on SDK, 它不再依赖 XPCOM、XUL,而是采用了 HTML、CSS、JavaScript。和 Chrome、Opera 的扩展开发类似。这里,Add-on SDK 和 Chrome 都使用 JSON 描述扩展元数据,而 Opera 使用 XML。现在应用扩展都在走 Web 路线。

ok,入口在下:

主页:https://builder.addons.mozilla.org/

手册:https://addons.mozilla.org/en-US/developers/docs/sdk/1.0/packages/addon-kit/addon-kit.html

仅仅这些api,对于我们的需求来说不够用的,不过我还是可以用html、css的,免得去学xul,

于是require了基本的api后,download一个框架版,自己添加xpcom的调用逻辑,才有权限修改host文件。

xpcom接口:https://developer.mozilla.org/en/XPCOM_Interface_Reference

捣啊捣啊捣啊捣,over了,产出如下:

点击后news.163.com lz.taobao.com 这2个域名都将指向10.0.0.4, 并立即新开一个页面,域名是第一个news.163.com (考虑到一些 api host是放在页面host后面的)。

点击配置按钮后打开以下panel:

默认向 http://localhost/test.js去请求json格式的host列表,可以修改该配置,该配置会保持在 simple-storage里。

Firebug调试压缩js

今天在公司要定位一个bug,但是线上生产环境的js代码是压缩后的,为此给断点跟踪带来难度:

1 代码是压缩替换了的

2firebug追踪,只能到一行,一个step into  or  step over, 就跳过了一行程序

好似如此:

纠结了许久,想出了一个办法:

追根到底,js一切皆对象,脚本语言解释执行后,东东也都是放在内存里的,我在firebug里直接把本地开发代码替换上去就好了!

当然,得注意替换被混淆后的外部变量,但是有个弊端,此时的函数定义位置不一致了,如果之前闭包享用的变量就不能使用了。

之前我在openresty.js的请求回调函数加了一个try{}catch(e){},解决了直接把错误暴露给普通用户,影响用户使用的bug,但是有些时候错误e对象转化为文本后会丢失一部分信息,抑或提示的不对(有此印象- -),

于是firebug中执行openresty.get = function(url….blablabla….,此处代码为开发代码,随意添加log打印信息,

没有输出log信息,自然是函数没有执行到这一步咯。

然后不断用此法替换函数,逐步定位到bug所在~~ 确实为一次比较难复现,特定情况触发的bug。

定位过程中不能刷新,还好我们的应用都是类似ajax堆起来的,普通应用,就得自己手动调用触发函数了。

不过终极解决方案应该是firebug 断点调试结合yslow的All JS Beautified,提供格式化压缩代码后的调试功能,哈哈,如果菜菜的我能补充firebug个这么个功能就好了,sigh

Html5动态分享

3月15日,IE9正式发布,来关注下ie9对于html5一系列技术的支持:

  • IE9对CSS3新特性的支持

IE9对于css3的支持力度不够,CSS3动画相关的几个属性是:transition, transform, animation, 对应的是过渡,变换,动画,但是IE9也只是支持一个2D的变换。

其他支持的属性为:border-radius: 边框圆角 opacity:透明度 rgba和hsla是关于颜色的属性,都支持透明参数 box-shadow:图层阴影 Background Size:设置背景图片的大小 Multiple backgrounds:多重背景图象

这些属性的支持也将给前端工作者带来极大的便利。css语言的逐步丰富,可以完成越来越复杂的交互,可以遇见,不久的将来会有越来越多的专职css开发人员。

  • IE9对HTML5特性的支持

    IE9对于html5的支持相对来说比较到位,关注一下IE9不支持的属性:Offline Applications:离线应用

    Web worders:为WEB前端网页上的脚本提供了一种能在后台进程中运行的方法,能够在不影响用户界面的情况下处理任务

    WebSQL Database:客户端的数据库

    IndexDB Database:客户端存储大容量的结构化数据

    Touch Events:触控事件

    History Management:历史管理

    Web Sockets:允许用户在浏览器中实现双向通信,实现数据的及时推送

    Ogg、wav:音频压缩格式

    WebM:媒体文件格式

    以上测试测试数据来自于 Findmebyip.com,findmebyid可以快速检测出浏览器对于HTML5新特性的支持

  • WEBGL
    • WebGL是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起 。WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。
    • Firefox 4.0 Beta、Chrome 9.0、Opera预览版、Safari每日构建版都已经提供了对WebGL 1.0的支持
    • WebGL 1.0正式版标准规范全文:http://www.khronos.org/registry/webgl/specs/latest/
    • 示例网站:

  • CSS3
  • Canvas库 – cakejs
    • CAKEjs是一个提供了很多功能模块的canvas库,可以帮你快速解决你的canvas问题
    • 示例demo
    • Apidemo
  • 我的demo
    • 此demo通过Sencha Animator和cakejs来编写的,safari等webkit核心浏览器下还有一个css 3d transform的效果

[转]V8 Javascript 引擎设计理念

【转载】,转载至 “V8 Javascript引擎设计理念”  by pluskid

英文原文: http://code.google.com/apis/v8/design.html

本文翻译自 Google 的开源 Javascript 引擎 V8 的在线文档。 其实我都没有真正翻译过什么东西,本来我的英文就比较一般,中文语言组织也很弱。而且许多文档(比如这篇)基本上如果是对此感兴趣的人,直接阅读英文原文 文档肯定都是没有问题的。不过既然突然心血来潮,就试一试吧,能力总是要锻炼才会有的。我自己对 Language VM 比较感兴趣,V8 其实并不是一个 VM ,因为它是直接编译为本地机器码执行的,但是也有不少相通的地方。废话少说,下面是译文。

Netscape Navigator 在 90 在年代中期对 JavaScript 进行了集成,这让网页开发人员对 HTML 页面中诸如 form 、frame 和 image 之类的元素的访问变得非常容易。由此 JavaScript 很快成为了用于定制控件和添加动画的工具,到 90 年代后期的时候,大部分的 JavaScript 脚本仅仅完成像“根据用户的鼠标动作把一幅图换成另一幅图”这样简单的功能。

随着最近 AJAX 技术的兴起,JavaScript 现在已经变成了实现基于 web 的应用程序(例如我们自己的 Gmail)的核心技术。JavaScript 程序从聊聊几行变成数百 KB 的代码。JavaScript 被设计于完成一些特定的任务,虽然 JavaScript 在做这些事情的时候通常都很高效,但是性能已经逐渐成为进一步用 JavaScript 开发复杂的基于 web 的应用程序的瓶颈。

V8 是一个全新的 JavaScript 引擎,它在设计之初就以高效地执行大型的 JavaScript 应用程序为目的。在一些性能测试中,V8 比 Internet Explorer 的 JScript 、Firefox 中的 SpiderMonkey 以及 Safari 中的 JavaScriptCore 要快上数倍。如果你的 web 程序的瓶颈在于 JavaScript 的运行效率,用 V8 代替你现在的 JavaScript 引擎很可能可以提升你的程序的运行效率。具体会有多大的性能提升依赖于程序执行了多少 JavaScript 代码以及这些代码本身的性质。比如,如果你的程序中的函数会被反复执行很多遍的话,性能提升通常会比较大,反过来,如果代码中有很多不同的函数并且都只会 被调用一次左右,那么性能提升就不会那么明显了。其中的原因在你读过这份文档余下的部分之后就会明白了。

V8 的性能提升主要来自三个关键部分:

快速属性访问

JavaScript 是一门动态语言,属性可以在运行时添加到或从对象中删除。这意味着对象的属性经常会发生变化。大部分 JavaScript 引擎都使用一个类似于字典的数据结构来存储对象的属性,这样每次访问对象的属性都需要进行一次动态的字典查找来获取属性在内存中的位置。这种实现方式让 JavaScript 中属性的访问比诸如 Java 和 Smalltalk 这样的语言中的成员变量的访问慢了许多。成员变量在内存中的位置离对象的地址的距离是固定的,这个偏移量由编译器在编译的时候根据对象的类的定义决定下 来。因此对成员变量的访问只是一个简单的内存读取或写入的操作,通常只需要一条指令即可。

为了减少 JavaScript 中访问属性所花的时间,V8 采用了和动态查找完全不同的技术来实现属性的访问:动态地为对象创建隐藏类。这并不是什么新的想法,基于原型的编程语言 Self 就用 map 来实现了类似的功能(参见 An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes )。在 V8 里,当一个新的属性被添加到对象中时,对象所对应的隐藏类会随之改变。

下面我们用一个简单的 JavaScript 函数来加以说明:

function Point(x, y) {
    this.x = x;
    this.y = y;
}

new Point(x, y) 执行的时候,一个新的 Point 对象会被创建出来。如果这是 Point 对象第一次被创建,V8 会为它初始化一个隐藏类,不妨称作 C0。因为这个对象还没有定义任何属性,所以这个初始类是一个空类。到这个时候为止,对象 Point 的隐藏类是 C0

map_trans_a

执行函数 Point 中的第一条语句(this.x = x;)会为对象 Point 创建一个新的属性 x。此时,V8 会:

  • C0 的基础上创建另一个隐藏类 C1,并将属性 x 的信息添加到 C1 中:这个属性的值会被存储在距 Point 对象的偏移量为 0 的地方。
  • C0 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 x 之后能够找到 C1 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C1

map_trans_b

执行函数 Point 中的第二条语句(this.y = y;)会添加一个新的属性 y 到对象 Point 中。同理,此时 V8 会:

  • C1 的基础上创建另一个隐藏类 C2,并在 C2 中添加关于属性 y 的信息:这个属性将被存储在内存中离 Point 对象的偏移量为 1 的地方。
  • C1 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 y 之后能够找到 C2 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C2

map_trans_c

咋一看似乎每次添加一个属性都创建一个新的隐藏类非常低效。实际上,利用类转移信息,隐藏类可以被重用。下次创建一个 Point 对象的时候,就可以直接共享由最初那个 Point 对象所创建出来的隐藏类。例如,如果又一个 Point 对象被创建出来了:

  • 一开始 Point 对象没有任何属性,它的隐藏类将会被设置为 C0
  • 当属性 x 被添加到对象中的时候,V8 通过 C0C1 的类转移信息将对象的隐藏类更新为 C1 ,并直接将 x 的属性值写入到由 C1 所指定的位置(偏移量 0)。
  • 当属性 y 被添加到对象中的时候,V8 又通过 C1C2 的类转移信息将对象的隐藏类更新为 C2 ,并直接将 y 的属性值写入到由 C2 所指定的位置(偏移量 1)。

尽管 JavaScript 比通常的面向对象的编程语言都要更加动态一些,然而大部分的 JavaScript 程序都会表现出像上述描述的那样的运行时高度结构重用的行为特征来。使用隐藏类主要有两个好处:属性访问不再需要动态字典查找了;为 V8 使用经典的基于类的优化和内联缓存技术创造了条件。关于内联缓存的更多信息可以参考 Efficient Implementation of the Smalltalk-80 System 这篇论文。

动态机器码生成

V8 在第一次执行 JavaScript 代码的时候会将其直接编译为本地机器码,而不是使用中间字节码的形式,因此也没有解释器的存在。属性访问由内联缓存代码来完成,这些代码通常会在运行时由 V8 修改为合适的机器指令。

在第一次执行到访问某个对象的属性的代码时,V8 会找出对象当前的隐藏类。同时,V8 会假设在相同代码段里的其他所有对象的属性访问都由这个隐藏类进行描述,并修改相应的内联代码让他们直接使用这个隐藏类。当 V8 预测正确的时候,属性值的存取仅需一条指令即可完成。如果预测失败了,V8 会再次修改内联代码并移除刚才加入的内联优化。

例如,访问一个 Point 对象的 x 属性的代码如下:

point.x

在 V8 中,对应生成的机器码如下:

; ebx = the point object
cmp [ebx, <hidden class offset>], <cached hidden class>
jne <inline cache miss>
mov eax, [ebx, <cached x offset>]

如果对象的隐藏类和缓存的隐藏类不一样,执行会跳转到 V8 运行系统中处理内联缓存预测失败的地方,在那里原来的内联代码会被修改以移除相应的内联缓存优化。如果预测成功了,属性 x 的值会被直接读出来。

当有许多对象共享同一个隐藏类的时候,这样的实现方式下属性的访问速度可以接近大多数动态语言。使用内联缓存代码和隐藏类实现属性访问的方式和动态代码生成和优化的方式结合起来,让大部分 JavaScript 代码的运行效率得以大幅提升。

高效的垃圾收集

V8 会自动回收不再被对象使用的内存,这个过程通常被称为“垃圾收集(Garbage Collection)”。为了保证快速的对象分配和缩短由垃圾收集造成的停顿,并杜绝内存碎片,V8 使用了一个 stop-the-world, generational, accurate 的垃圾收集器,换句话说,V8 的垃圾收集器:

  • 在执行垃圾回收的时候会中断程序的执行。
  • 大部分情况下,每个垃圾收集周期只处理整个对象堆的一部分,这让程序中断造成的影响得以减轻。
  • 总是知道内存中所有的对象和指针所在的位置,这避免了非 accurate 的垃圾收集器中普遍存在的由于错误地把对象当作指针而造成的内存溢出的情况。

在 V8 中,对象堆被分成两部分:用于为新创建的对象分配空间的部分和用于存放在垃圾收集周期中生存下来的那些老的对象的部分。如果一个对象在垃圾收集的过程中被移动了,V8 会更新所有指向这个对象的指针到新的地址。

疑问:阅读此文的时候,有一点疑惑,因为列举了2个创建对象的过程,对于v8引擎,是在哪个步骤减少 JavaScript 中访问属性所花的时间?

假设一个查询 this.y 的过程, v8是查询到 Hidden class C2中,返回 offset 1, 然后读取 内存offset 1地址的数据。

在Hidden class C2 中,
For x see offset 0, for y see offset 1

为何比传统的
进行一次动态的字典查找来获取属性在内存中的位置
for x see 内存地址a, for y see 内存地址b

快呢?期待自己哪天能豁然开朗- –