Zen Space | 寻找善美

寻找善美

Go学习笔记:空标示符-

| Comments

开始学习下Go语言,为了强化记忆开始记一些学习笔记。

Go语言是一门很简单的语言,它为我们做了很多的决定,比如很多在其他语言中 不推荐使用的编码风格在Go是不允许的。比如: 变量或者包声明或导入后没有使用是无法编译通过的。 它的编译只有Fatal没有Warning,这对于代码质量是很有好处的。在其他语言中 最佳实践也是编写warning-free的代码。Go把这个最佳实践放到了语言级别。

Go中的空标示符(blank indentifier): _

刚开始在看到Go中的空标示符是以为它只是一种约定,因为下划线看起来比那么的 显眼,而普通变量又不太可能只使用一个下划线来命名,在其他语言中我们不想使用 一个变量的话很简单忽略之就可以了,而如果对一个函数的返回值不感兴趣的话, 不对返回值赋值即可。

先记录下结论:空标示符不是一个普通变量或标示符,而是一个特殊的标示符, 对于这种类型的标示符绑定表达式时不进行真正的绑定。

这是什么意思呢?也就说比如将一个值赋值给空操作符是不会进行值绑定的。

blank indentifier - test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
  _ "io"  // 如果不重名名包为_ 而在代码中没有使用这个包会编译不通过
          // 这样导入一个包是有副作用的,导入一个包后会执行包的init()方法,
          // 如果只是为了避免编译不通过而绑定到_是不推荐的做法。
    "fmt"
)

func getMulti() (int, int) {
  return 3, 4
}

func main() {
  _ = 20        // 绑定没有作用,不会报错
  // _ := 10  // 编译不通过,因为表达式左边没有一个有效的新的标示符
              // no new variables on left side of :=

  x, _ := getMulti()

  fmt.Printf("%d\n", x)
  // fmt.Printf("%d\n", _)   这样是编译不通过的,因为_并不能被赋值
  //                         编译 "cannot use _ as value"
}

总结

空标示符不是普通标示符,是一个语言级别的标示符,通常用来:

  1. 显式的忽略函数或其他多值赋值表达式中的某些的返回值,多值表达式通常有:

    • 函数的多个返回值
    • range循环中的key-value值
    • 多值赋值,比如: x, y, z := 10, 20, 30, 不过这种情况比较小
  2. 或者导入包不使用包而只利用包的初始化函数的副作用。但是不推荐用这种方式 来绕过因为包未被使用而编译不通过的问题

这里只是做一个笔记,其实Go的语言规范中写的还是很详细的。对于一些有疑惑的地方 一翻手册就会发现答案。这也是一门语言小的好处,歧义会非常少。

参考资料

  1. Golang 官方 http://golang.org
  2. Golang 语言规范 http://golang.org/ref/specGo的语言规范非常小,所有语言语法规范 都在这里可以找到。
  3. 《The Way to Go》这本书是目前比较全面的一本,在看完Go的手册之后推荐阅读

The Nearly End of the End of the World

| Comments

不巧刚才突然发现家里的网络怎么都连不上了,登陆路由器发现网络已经连不上了, 才发现,我们的网已经到期了。看着屏幕右上角的日期,猛然发现原来,世界末日 都快要过去了。

很多人都会在年终写下点总结,我想我也会写的,不过刚好今天有一个很好的契机: 我不会受到网络的任何干扰,可以静静的写东西。

2012并不是世界末日,对于我以及我周围的人和世界来说,大家都很理性,很少有人 真的把今天当成是末日来过,偶尔在微博上看到一些小段子,一些励志的话语, 也会受到感染,想着要把这平静的日子过的风火起来,让自己的每一天充实而又意义起来。

最近很少有时间静下来好好思考,尤其是到了这个新的环境之后,一来需要熟悉新环境, 二来想要更加努力一点,这个环境是我一直想要的环境。先说说这段时间依赖的感受吧。

2012年的总结

2013年的展望和计划

  1. 完成 TiPHP的功能实现

效率

效率低下

拖延症

推迟自己面对问题时的压力,逃避压力。

得失心过重

害怕做的不好

改进措施

工作

到新的部门已经快5个月了,时间从来没有这么快过。

收获

计划

生活

收获

计划

LevelDB扩展发布V0.1版本

| Comments

很开心,第一个提交的PHP扩展已经在PECL官方发布了,这是一个Google LevelDB的PHP 封装,主要用于对LevelDB的访问,目前已经实现了LevelDB最具价值的一些特性:迭代器,快照等。

LevelDB数据的设计是只能单进程访问的(多线程没有问题),所以通常这个扩展不合适作为普通的Web应用数据存储, 可以作为离线的数据存储用,或者只是方便读取现有leveldb的数据。

如果有需要可以前去 http://pecl.php.net/package/leveldb 下载。基本的使用说明在http://reeze.cn/php-leveldb/ 详细的API文档由勤劳高效的胖胖http://www.phppan.com/编写,不过还没有发布。

同时,还有了@php.net的马甲一枚: reeze(at)php.net。MARK一下。

怎么样获取PHP函数默认参数的常量名

| Comments

好久没有更新了,发篇占位文:如果某个函数的默认参数是个常量,那么怎么样获取这个参数的常量名称?见代码:

1
2
3
4
5
<?php

function new_blog($title = DEFAULT_TITLE) {
  // blahblah
}

在上面的代码中,怎么样获取函数new_blog函数的参数$title所对应的默认值常量名: DEFAULT_TITLE。这个问题和以前我曾写过的一篇 关于如何获取变量名称的博文相似。

这个问题,在PHP5.4.6之前基本上没有解决方法了,因为函数定义是编译时的信息,在PHP运行时是获取不到的。 当然这里说的无法实现是指的使用官方PHP版本时没法搞定。

在PHP中类似的需求,一般都可以使用PHP的反射扩展。

PHP的反射(Reflection)

反射是PHP5中提供的用于获取或操作PHP内部信息的标准扩展,可能写应用代码的用户使用的较少一些, 编写框架或者平台性的系统会使用到。

比如你的框架需要实现一种插件机制,而你可能需要利用反射来获取类或者函数的元信息。 这里就不对Reflection的使用做过多的介绍了,详细信息见官方文档: http://cn.php.net/manual/en/book.reflection.php

新的函数ReflectionParameter::getDefaultValueConstantName()

不过在PHP5.4.6之前,Reflection是没有实现该功能的。这个需求其实来自PHPUnit的作者Sebastian Bergmann。 因为这个需求在Reflection模块来说是一个缺失,不属于大功能的升级,所以直接进入了目前的最新分支PHP-5.4。 同时这个功能在PHP-5.4.6中可用了。https://github.com/php/php-src/blob/PHP-5.4.6/NEWS#L41

实现代码见:https://github.com/php/php-src/commit/13a9555342a4156a6150818234639b49a596ccd6, 这个方法目前没有使用说明,不过看名字应该也能明白。不过可以参考测试用例

这个提交给ReflectionParameter类增加了两个函数:

  1. ReflectionParameter::isDefaultValueConstant() 用于判断函数的这个参数是否是常量默认参数
  2. ReflectionParameter::getDefaultValueConstantName() 用于获取这个常量默认参数的参数名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
define("CONST_TEST_1", "const1");

function ReflectionParameterTest($test1=array(), $test2 = CONST_TEST_1) {
  echo $test;
}
$reflect = new ReflectionFunction('ReflectionParameterTest');
foreach($reflect->getParameters() as $param) {
  if($param->getName() == 'test1') {
      var_dump($param->isDefaultValueConstant());
  }
  if($param->getName() == 'test2') {
      var_dump($param->isDefaultValueConstant());
  }
  if($param->isDefaultValueAvailable() && $param->isDefaultValueConstant()) {
      var_dump($param->getDefaultValueConstantName());
  }
}

class Foo2 {
  const bar = 'Foo2::bar';
}

class Foo {
  const bar = 'Foo::bar';

  public function baz($param1 = self::bar, $param2=Foo2::bar, $param3=CONST_TEST_1) {
  }
}

$method = new ReflectionMethod('Foo', 'baz');
$params = $method->getParameters();

foreach ($params as $param) {
    if ($param->isDefaultValueConstant()) {
        var_dump($param->getDefaultValueConstantName());
    }
}
?>
// 运行结果
bool(false)
bool(true)
string(12) "CONST_TEST_1"
string(9) "self::bar"
string(9) "Foo2::bar"
string(12) "CONST_TEST_1"

Intro

| Comments

Testing!

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis sollicitudin, massa eu vestibulum laoreet, nibh ante vulputate lorem, ac lobortis ante tellus eu mi. Duis sem nisi, luctus at feugiat eget, fringilla ut tellus. Nam a molestie justo. Sed pulvinar est vitae tellus semper tincidunt. Fusce euismod luctus lacus nec placerat. Mauris rutrum scelerisque nulla ut tempor. Nunc porttitor posuere mi, aliquet vehicula lorem feugiat in. Ut ut fermentum risus. Aliquam tincidunt ultricies ante sit amet bibendum. Cras nec sapien odio. Duis posuere congue sem, at congue massa faucibus at.

Integer ut sapien eget nisl auctor faucibus ut fermentum arcu. Nunc rutrum urna non risus congue et tristique felis eleifend. Maecenas blandit est eu mauris aliquam aliquet. Quisque porttitor enim eget risus blandit in mollis orci eleifend. Nam malesuada nulla sed lacus elementum placerat accumsan arcu rhoncus. Phasellus feugiat cursus turpis nec facilisis. Duis eget metus arcu, eget commodo velit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer cursus vulputate enim, vel gravida velit faucibus et. Ut a urna vitae tellus cursus rhoncus. Maecenas at odio eget quam cursus elementum. Aliquam vitae eros quis tellus laoreet accumsan sed id lorem. Suspendisse et rutrum leo. Integer scelerisque vestibulum adipiscing. In posuere, libero ac accumsan suscipit, nulla ligula gravida erat, ut tempor odio erat nec sem. Quisque justo ipsum, adipiscing volutpat varius vitae, blandit eget nisi.

Nullam adipiscing neque ac lacus commodo vitae imperdiet dui sollicitudin. Ut ac nunc augue. Nam at sem ut quam commodo aliquet vitae vitae dui. Vivamus scelerisque felis eget dolor cursus feugiat. Phasellus at dui sed lectus scelerisque pretium. Etiam nec massa ut justo vestibulum fringilla ac vitae urna. Morbi tortor erat, tempus sed consectetur at, elementum nec eros. Vivamus mattis arcu a sapien semper non lacinia eros pretium.

Proin ut hendrerit arcu. Maecenas ullamcorper tristique magna vel mattis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam tincidunt euismod viverra. In sit amet neque turpis. Suspendisse ac sapien mi, id blandit purus. Ut tortor turpis, rutrum ac tempor at, accumsan sit amet erat. Etiam ultricies eleifend dolor, eget tempus justo tristique vitae. In hac habitasse platea dictumst. Aliquam eu enim neque.

Morbi massa lorem, viverra non dictum at, malesuada vel nibh. Nam fermentum lobortis varius. Sed a nulla lacus, quis posuere risus. Nunc id urna libero, quis rutrum mi. In gravida felis urna. Praesent nec dolor ac urna tempor fermentum. Curabitur rutrum arcu et lorem volutpat viverra.

初试Travis-CI

| Comments

Travis CI是一个基于云的持续集成项目, 目前已经支持大部分主流语言了,比如:C,PHP,Ruby,Python, Nodejs等等。和Jenkins类似, Travis CI也是开源的,不过Travis和Github集成非常紧密,官方的集成测试托管只支持Github项目, 不过你也可以搭建一套自己的方案。 这里有一篇比较详实的对Travis-CI的介绍, 同时InfoQ上也有一篇关于Travic_CI的报道

如果你有开源项目,那么Travis绝对值得一试,目前托管在Github上的大部分知名项目都使用了Travis来做集成测试。 比如Ruby语言的:Rails, Rack, Sinatra, RSpec, Cumber, Node.js, PHP的:Symfony2, Doctrine2, Zend Framework 2。

PHP语言也使用了Travis做集成测试,不过目前由于PHP的扩展众多, 很多的测试用例本身也不够健壮,PHP的测试经常会失败。

使用Travis-CI的项目可以在说明文件中增加目前版本的构建状态。Travis为每个项目提供一个图片地址,比如PHP的: https://secure.travis-ci.org/php/php-src.png?branch=master, php-src,之所以是构建失败,是因为前面提到的原因。

如果构建成功,图片将显示:php-leveldb, 这是我最近在写的一个扩展:php-leveldb的构建状态http://travis-ci.org/reeze/php-leveldb

下面简单介绍一下,如果你在编写一个PHP扩展,该怎么样使用Travis-CI来做持续集成。 当然,你的代码需要在Github上进行托管。

Travis-CI配置文件

要使用Travis,首先需要在你的代码根目录下包含一个叫做.travis.yml的文件,这是一个配置文件, 为yaml格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
language: php
php:
  - 5.2
  - 5.3
  - 5.4

env:
  - REPORT_EXIT_STATUS=1 NO_INTERACTION=1

before_script: sh travis/prepare.sh

script: sh travis/run-test.sh

notifications:
  email:
    - [email protected]

配置简单明了,选择需要的语言,同时可以设置需要测试的语言版本,因为php-leveldb支持5.2 ~ 5.4, 所以这里设置了3个需要测试的版本。

env 设置为执行测试时需要设置的环境变量,因为php 执行make test测试时如果测试失败会提示 用户是否将测试结果发送给PHP官方。因为测试是自动进行的,如果不设置NO_INTERACTION=1会导致 测试失败后一直等待用户输入而hang住直到测试超时。

before_script 可以用来进行一些准备工作,例如php-leveldb扩展需要先安装leveldb才能编译。

travis/prepare.sh文件做的工作也就是从leveldb官方下载代码并编译, 最后编译扩展。

1
2
3
4
5
6
7
#!/bin/sh

wget http://leveldb.googlecode.com/files/leveldb-1.5.0.tar.gz
tar zxvf leveldb-1.5.0.tar.gz
cd leveldb-1.5.0 && make
cd ..
phpize && ./configure --with-leveldb=$PWD/leveldb-1.5.0 && make

travis可以执行任何脚本。因为travis在执行测试之前会建立一个虚拟机用于测试。

script属性就是测试的入口,可以是任何的sh命令。测试结果到底是成功还是失败会依据这个命令的 返回值,如果返回非0结果,则表示测试失败,失败的时候就会给下面notifications设置的邮箱发送邮件。

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh

make test

# make test didn't return status code correctly
# use this to find whether the make test failed
cat tests/\*.diff

if [ $? -eq 0 ]; then
  exit 1;
fi

从上面的注释里也提到,因为PHP生成的测试不能正常的返回错误码,这样的结果是即使测试失败了, travis也会忽略,所以采用了一个折中的方式来测试。因为测试失败会生产diff文件, 报告测试失败的具体原因,cat的好处是能把错误详细信息报告出来,也能方便调试。

UPDATE 我已经提交了一个补丁用于让make test命令可以在测试失败的时候返回错误码了。 目前在master分支上,目前看在5.5版本以上可以使用。

每次提交代码到Github上时,Travis将自动对新提交的版本进行测试。

除了PHP,其实你可以在Travis上测试绝大部分的软件,因为travis提供的是一个完整的环境, 而你可以编写任何的脚本来进行你的测试工作。

PHP中的NOP及为什么有这个opcode

| Comments

什么是NOP

NOP 是一个特殊的opcode,表示空操作,在很多地方存在,汇编中的NOP含义也一样, 机器指令中的空操作通常用来将内存地址进行对齐,以提高CPU访问内存的效率, GCC等编译器也会将特定的语句进行优化而产生空操作。

PHP中的空操作opcode NOP: ZEND_NOP

PHP基于Zend虚拟机,其他基于虚拟机的语言中大都会有类似NOP的指令, PHP文档有对此的简单说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
/*
 * no operation
 * opcode number: 0
 */
function A(){};
?>

/* VLD 的输出结果 */
line  #   op  fetch   ext return  operands
6 0   NOP              
7 1   RETURN              1
Function name: A

Compiled variables: none

line  #   op  fetch   ext return  operands
6 0   RETURN              null

上面的VLD结果可以看出,函数A()的声明编译后变成了NOP操作。

Zend虚拟机是高级抽象,不要考虑内存对齐等的问题,为什么还需要空操作这样的opcode呢?

原因简单讲就是:编译过程优化的结果。有的内容由于可以在编译时就可确定(称为提早绑定Early Binding), 那么一部分opcode可以在编译时替换成空操作。

我们来看看这段代码的编译结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if(true) {
  class Foo {}
}

new Bar();
class Bar {}

filename:       /Users/reeze/Opensource/php-test/php-src-5.4/test.php
function name:  (null)
number of ops:  8
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  > > JMPZ                                                     true, ->3
   3     1  >   ZEND_DECLARE_CLASS                               $0      '%00foo%2FUsers%2Freeze%2FOpensource%2Fphp-test%2Fphp-src-5.4%2Ftest.php0x106cd601f', 'foo'
   4     2    > JMP                                                      ->3
   6     3  >   ZEND_FETCH_CLASS                              4  :1      'Bar'
       4      NEW                                                      :1
       5      DO_FCALL_BY_NAME                              0
   7     6      NOP
   9     7    > RETURN

和前面官方函数定义代码一样,上面VLD输出的第7行看到类Bar的opcode变成了NOP,不过请留意第3行, 这一行类Foo定义的opcode是ZEND_DECLARE_CLASS,也就是类的声明。

为什么同样是类的声明,第一个类声明的OPCODE和第二个的不一样呢?

上例中的代码,在Bar类声明之前是可以执行的new Bar,但是此时Bar类的声明并没有执行到, 那为什么可以访问到Bar类呢,这是因为Bar类的声明在编译时就已经完成了, 因为Bar类已经在编译时声明好了,所以在真正执行的时候就不需要再次执行声明类的操作了, 所以它所对应的opcode被替换成NOP了。

而Foo这个类由于处在条件判断块之中,编译期无法确定Foo类是否一定会被执行,所以还是需要在 执行时来声明这个类,所以opcode没有改变。

对于使用了opcode缓存的代码来说,把函数和类的声明移到了编译时,也就减少了执行时的opcode执行, 这能加快代码的执行。

优化

你可能会想,既然类及函数的声明可以优化掉,为什么不能直接丢弃这个opcode呢? 一能减少opcode占用的内容,比如很多的框架中有大量的类定义,也能减少执行时间, 因为空操作并不是0成本的,执行NOP的时候还是需要消耗CPU的。

先看看opcode编译过程的一个重要函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
zend_op *get_next_op(zend_op_array *op_array TSRMLS_DC)
{
  zend_uint next_op_num = op_array->last++;
  zend_op *next_op;
  if (next_op_num >= CG(context).opcodes_size) {
      if (op_array->fn_flags & ZEND_ACC_INTERACTIVE) {
          /* we messed up */
          zend_printf("Ran out of opcode space!\n"
                      "You should probably consider writing this huge script into a file!\n");
          zend_bailout();
      }
      CG(context).opcodes_size *= 4;
      op_array_alloc_ops(op_array, CG(context).opcodes_size);
  }

  next_op = &(op_array->opcodes[next_op_num]);

  init_op(next_op TSRMLS_CC);

  return next_op;
}

这个函数每次会返回一个zend_op(也就是opcode一个最小单位),opcode的存储空间申请和哈希表类似, 通过预先申请空间的方式,如果空间不足则适当扩容。在编译时,opcode是以文件为单位的,而通常 在一个文件中函数或类声明的个数是不会太多的。而在编译时opcode数组已经是预先申请好的,所以 及时优化掉这个opcode,而实际在编译时的内存占用也不会有任何的优化。

目前只有少数几处使用了ZEND_NOP这个opcode。读者可以参考Zend/zend_compile.c: zend_do_early_binding(), 这个函数进行就是在确定在编译时能确定的函数以及类声明,在完成函数或类的声明后将当前编译的opcode设置为ZEND_NOP。 因为后续在执行是并需要再次对该函数或类进行声明了。

NOTE 当然也并不是所有的全局类或者方法都会进行提早绑定,具体可以参考前面提到的Zend_compile.c文件的实现

在这个函数中其实可以将生成的ZEND_NOP优化掉的,比如eAccelarator扩展中就对opcode进行了优化,将ZEND_NOP从 opcode_array数组中移除了,因为使用了opcode cache扩展优化只进行一次,而执行对多次执行, 这样的优化是值得的。目前Zend引擎并没有进行任何的优化,首先从代码上来看,类和函数声明的数量和其他指令的数量 之间差很多个等级,所以至少这个地方优化的收益是有限的,为了保证Zend引擎的简洁它没有进行优化。

目前APC扩展已经基本确定为将要进入PHP默认opcode缓存的官方扩展了,那么这些优化都可以在扩展中进行, 保证Zend引擎的简单易维护更为重要。

迁移博客到Octopress

| Comments

很久以前就不想继续使用Wordpress了,不太习惯在线写东西,开始返璞归真,比较喜欢 纯文本的内容创作方式,TIPI就使用的是markdown格式, 同时在终端也会让我更有写作的欲望。

翻了一下以前的博客,原来一共加起来也不足20篇。以此作为起点今后多继续更新博客吧。

TIPI的issue里还有很多的待处理工作,计划在7月份全部写完。这个始终是高优先级的。 最近给PHP修复了一些bug,也有一些feature被接受了。在这过程中对PHP的实现又有了一些更加深入 的理解。也有冲动想要写到TIPI里。总之,TIPI会一直更新。

复制SSH会话,避免多次密码输入

| Comments

就当是笔记吧. 首先,这不是配置ssh密钥简历信任关系免密码登陆的方法。 主要解决的问题是在终端下多次登陆统一主机需要多次输入用户密码的问题。

我们公司的内网环境比较特殊, 为了安全性做了各种认证, 联入网络需要准入一下,  准入需要使用密码+随即密码的方式认证, 是挺安全的, 可以对于我们来说其实很痛苦, 每次都要输入一下密码,因为包含了随机密码, 这就无法使用脚本来方便的自动准入.  我们连入公司的远程开发机需要通过一台特殊的服务器来将我们的登陆转发,  也就是登陆到中转机,然后通过中转机在ssh.同样登陆中转机也是需要这个随机密码的. 吐槽完毕.

工作中经常需要在多台服务之间ssh登陆, screen 是一个不错的选择,  不过有时候还是需要打开另一个窗口再次登陆, 这时我又得再次输入那个随机密码,  如果你使用windows并且使用SecureCRT那你可以不用继续往下看了, SecureCRT可以简单的复制回话, 这个功能很贴心. 如果使用Linux&Mac OS那就继续往下看.

在/etc/ssh_config 文件中加入

Host *
ControlMaster auto
ControlPath ~/.ssh/master-%r@%h:%p

下次登陆同一站点的时候就会自动复用已有的回话. 可以只输入一次密码开N个窗口了.