Eleven's blog


  • 首页

  • 归档

  • 关于

  • 遗愿

关于值与引用

发表于 2016-03-12 | 分类于 java

自从到了新公司,就开始了苦逼的java学习。新公司忙成🐶,每天基本上都是10点左右才离开公司。

今天感冒请假在家,想到自己已经很长时间没有学习了。不学习就不会成长,毕竟大杭州90%公司的核心开发语言都是Java,于是在知乎上找各位大牛的Java学习路线,发现了之前有个一直不适很清楚的概念,Java中的值传递和引用传递。

  • 关于值类型和引用类型。值类型是java中所有的基本类型,而引用类型是指对象类型。(Integer和Long这种包装器也是引用类型,只是他们在某些情况下编译器中会转化成它包装的基本类型)。用来区分引用类型和值类型只有一个标准,它的内存分配是在堆上还是在栈上。
  • mutate和change,mutate表示堆上内存的改变,change表示变量名重新指到一个新的内存地址,原本指向的内容不变。
  • 值传递与引用传递。函数调用参数传递本身都是值的传递,值类型传递一份本身的拷贝,引用类型传递一份引用的地址拷贝。由于函数调用会创建新的栈帧,所以形参的作用域只是函数内部,对形参变量名的赋值不会对函数之外产生影响,所以我们永远也无法在函数内改变函数外的实参的引用地址。所以我们只能对实参进行mutate操作,不能进行change操作。

不扯淡,睡觉了。知乎原文地址

明天回来看闭包。 first class function

Mysql Explain命令整理

发表于 2015-07-19 | 分类于 Mysql

Mysql命令整理

要理解Mysql内部查询机制首先要学会使用Explain命令。

mysql> explain select * from orders where serial_id = '0010001505180005fm';
+----+-------------+--------+------+---------------+-----------+---------+-------+------+-------------+
| id | select_type | table  | type | possible_keys | key       | key_len | ref   | rows | Extra       |
+----+-------------+--------+------+---------------+-----------+---------+-------+------+-------------+
|  1 | SIMPLE      | orders | ref  | serial_id     | serial_id | 122     | const |    1 | Using where |
+----+-------------+--------+------+---------------+-----------+---------+-------+------+-------------+
1 row in set (0.02 sec)

id 表示查找的顺序,如果有union还会为union专门分配一次查找。

select_type 表示查找类型

  • simple 不包含子查询和union的查询类型
  • subquery 包含在select列表中的子查询中的select。(也就是说不在from子句中)。
  • derived 与上面的相反,包含在from子句中的select语句。
  • union 在union中的第二个和随后的select被标记为union。
  • union result 用来从union的匿名临时表检索结果的select。

subquery还可以被标记为dependent(子查询依赖与外部查询)和uncacheable(使用了rand()函数无法缓存)。

table 表示被查找的表。mysql是按照左侧深度优先来查找数据的。如果from子句中有子查询或有union时,会产生派生表。

type 表示关联类型。

  • all 表示全表扫描
  • index 类似于全表扫描,只不过扫描的时候时按照索引的顺序进行的。主要的优点是避免了排序,最大的缺点是每次检索都是随机检索。
  • range 表示范围查找。mysq会把in和or也归类为范围查找,不过in和or会比普通的范围查找性能快很多。
  • ref 索引访问。只有使用非唯一索引或者唯一性索引的非唯一性前缀的时候才会发生。
  • eq_ref 使用这种索引的时候,mysql知道最多只返回一条符合条件的记录。
  • const, system 常量查找

possible_keys 表示可以使用的索引。

key 表示实际使用的索引。

key_len 索引里使用的字节数。

ref 表示之前的表在key列记录的索引中查找所用的列或常量。

rows 表示查找所需要读取的行数。

extra 额外信息

  • using index 表示使用了覆盖索引
  • using where 表示mysql服务器将在存储引擎检索后再进行过滤。
  • using temporary 表示使用了临时表
  • using filesort 表示使用了文件排序
  • range checked for each record (index map: N) 不知道是什么意思。

与coroutine的意外邂逅

发表于 2015-04-09 | 分类于 Python

看tornado源码的时候,意外看到了coroutine这个东西,觉得十分有意思,去问了端神和July大神后发现原来Go和Erlang的高并发都是用coroutine来实现的,Lua中coroutine的概念也是十分常用。后面去百度了一下,发现当前并发模型最火的也就是nodejs的callback模型和coroutine模型(据说node的callback模型的性能比coroutine更强大,怪不得死月他们都学node去了)。

Python原生不支持crountine,不过python原生的生成器倒是挺像coroutine。但是由于生成器无法指定将执行权限交给谁,所以对于异步编程来说意义不是很大。幸好有了gevent这个python的coroutine框架。

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
from gevent import monkey
import gevent

monkey.patch_socket()

def f(n):
for i in range(n):
print gevent.getcurrent(), i

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)

g1.join()
g2.join()
g3.join()

# 输出结果是这样的:
<Greenlet at 0x1068cf0f0: f(5)> 0
<Greenlet at 0x1068cf0f0: f(5)> 1
<Greenlet at 0x1068cf0f0: f(5)> 2
<Greenlet at 0x1068cf0f0: f(5)> 3
<Greenlet at 0x1068cf0f0: f(5)> 4
<Greenlet at 0x1068cf230: f(5)> 0
<Greenlet at 0x1068cf230: f(5)> 1
<Greenlet at 0x1068cf230: f(5)> 2
<Greenlet at 0x1068cf230: f(5)> 3
<Greenlet at 0x1068cf230: f(5)> 4
<Greenlet at 0x1068cf2d0: f(5)> 0
<Greenlet at 0x1068cf2d0: f(5)> 1
<Greenlet at 0x1068cf2d0: f(5)> 2
<Greenlet at 0x1068cf2d0: f(5)> 3
<Greenlet at 0x1068cf2d0: f(5)> 4
说明它其实是按顺序执行的。
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
from gevent import monkey
import gevent

monkey.patch_socket()

def f(n):
for i in range(n):
print gevent.getcurrent(), i
gevent.sleep(0)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)

g1.join()
g2.join()
g3.join()

# 输出结果如下
<Greenlet at 0x10c4d20f0: f(5)> 0
<Greenlet at 0x10c4d2230: f(5)> 0
<Greenlet at 0x10c4d22d0: f(5)> 0
<Greenlet at 0x10c4d20f0: f(5)> 1
<Greenlet at 0x10c4d2230: f(5)> 1
<Greenlet at 0x10c4d22d0: f(5)> 1
<Greenlet at 0x10c4d20f0: f(5)> 2
<Greenlet at 0x10c4d2230: f(5)> 2
<Greenlet at 0x10c4d22d0: f(5)> 2
<Greenlet at 0x10c4d20f0: f(5)> 3
<Greenlet at 0x10c4d2230: f(5)> 3
<Greenlet at 0x10c4d22d0: f(5)> 3
<Greenlet at 0x10c4d20f0: f(5)> 4
<Greenlet at 0x10c4d2230: f(5)> 4
<Greenlet at 0x10c4d22d0: f(5)> 4
# 这说明它是交替执行的。

其实是这样的,coroutine并不是并行,它不会创建多个进程或线程,而是始终运行在一个线程里面。但是当每个coroutine执行遇到IO的时候,它会交出执行权限gevent会将执行权限交给其他没有在IO等待状态的coroutine,如果全部都在等待状态,gevent会不断轮询,类似与epoll。也正式由于coroutine始终执行在一条线程中所以它使用不了多核的资源。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import gevent, random

products = []

def consume(count):
while count:
for index in xrange(0, len(products)):
products.pop(0)
print products
count = count -1
gevent.sleep(0)

def product(count):
while count:
while len(products) < 5:
p = random.randint(0, 9)
products.append(p)
print products
count = count -1
gevent.sleep(0)

print products
gevent.joinall([
gevent.spawn(product, 3),
gevent.spawn(consume, 3),
])

# 输出如下:
[]
[6]
[6, 2]
[6, 2, 6]
[6, 2, 6, 7]
[6, 2, 6, 7, 8]
[2, 6, 7, 8]
[6, 7, 8]
[7, 8]
[8]
[]
[8]
[8, 5]
[8, 5, 3]
[8, 5, 3, 1]
[8, 5, 3, 1, 4]
[5, 3, 1, 4]
[3, 1, 4]
[1, 4]
[4]
[]
[5]
[5, 4]
[5, 4, 8]
[5, 4, 8, 8]
[5, 4, 8, 8, 4]
[4, 8, 8, 4]
[8, 8, 4]
[8, 4]
[4]
[]
这与多线程并行的程序相比,无论执行多少次输出结果都是固定的。
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
from gevent import monkey
import gevent
import urllib2

monkey.patch_all()


def f(url):
print("GET: %s" % url)
resp = urllib2.urlopen(url)
data = resp.read()
print("%d bytes received from %s." % (len(data), url))

gevent.joinall([
gevent.spawn(f, "http://www.python.org/"),
gevent.spawn(f, "http://eleven.name/"),
gevent.spawn(f, "http://github.com/"),
])

# 输出结果
GET: http://www.python.org/
GET: http://eleven.name/
GET: http://github.com/
28866 bytes received from http://eleven.name/.
47108 bytes received from http://www.python.org/.
17424 bytes received from http://github.com/.

看了Qcon上豆瓣清风大神的分享,顿时觉得有点明白github是怎么玩的了,以后大家多多给我做code review吧。

  • http://blog.jobbole.com/77240/ 这篇文章是讲coroutine的实现的。简单地说就是不断地将每个coroutine地栈帧从堆移到栈然后又从栈移到堆,从而获得coroutine的切换。
  • http://xlambda.com/gevent-tutorial/ 中文教程
  • http://www.gevent.org/ 官方教程

InnoDB中多版本并发控制的实现

发表于 2015-03-21 | 分类于 Mysql

MySQL的大多数事务型存储引擎的都不是简单的行级锁,基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。

InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的。一个保存了行的创建时间,一个保存了行的过期时间(删除时间)。当然存储的并不是世纪的时间值,而是系统版本号。
每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

下面看一下在REPEATABLE READ隔离级别下,MVCC具体是如何操作的。

Select

InnoDB会根据一下两个条件检查每行记录:

  • a.InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在,要么是事务自身插入或者修改过的。
  • b.行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

Insert

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

Delete

InnoDB为删除的每一行保存当前系统版本号作为行删除标识。

Update

InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。

大道至简。这个设计真是碉堡了。帅。

DBA也是一条不归路呀。最近又重新开始看《高性能MySQL》,发现还远远不够,还是要去卡看InnoDB的相关资料。

PS:附上一个git教程,感觉蛮好的,励志成为Git骨灰级玩家。http://blog.csdn.net/fanwenbo/article/details/8670523

代码大全第一部分:打好基础

发表于 2015-02-19 | 分类于 代码大全

昨天和今天看了《代码大全》的第一部分,打好基础。第一部分讲了一个软件构建前的一些东西。之前一直没有做过很大的项目,现在代码写多了也就慢慢地有感觉了,知道了架构是干什么用的。

第一部分没有很详细地教你如何做架构,而是讲了一个软件构建地整个过程,从定义问题确定需求到架构设计,到详细设计,到编码,到单元测试,到继承,到集成测试和系统测试。这中间有很多时间上是有重叠地。之前我写程序都没有做架构设计,所以写地时候很多地方是都不清晰的,而且有时候修改起来也比较麻烦,更重要的是修改过了,对系统的掌控力也就差了。

《人月神话》中将软件开发比作是写作,但本书的作者将这种隐喻推翻,认为软件开发更像是造房子。前期良好的架构是节省成本的最佳方法,因为移动一片造好的墙和在设计的时候修改墙的设计的成本差好多。隐喻是一种很好的认事物的方法。

架构师是一个牛逼的职业,要开发一个软件首先要明确需求,对需求要有一个良好的问题定义。然后是确定软件的类型。问题定义只是定义了“问题式什么”,而不能以技术的角度涉及任何解决方案相关的内容。书中列举了3中常见的软件类型,每种类型的软件都有适合自己的开发方法。另外还介绍了两种开发方法的优缺点(迭代式开发方法和序列式开发方法,虽然我不是很理解)。

确定需求和架构的时间一般占工程时间的20~30%,另外书中列举了一系列标准:确定一个良好的架构的标准,和确定一份良好的需求的标准。以后可以当作自己写软件的标准。

另外书上还讲了一些有意思的东西,其实在软件工程发展的早起,程序员也式很惨的。因为那个时候各种不稳定,编译器会出bug,操作系统会出bug,而且版本更新十分频繁,向前兼容性也做得很差,文档少。但是作者提到了一个牛逼的地方就是:深入一门语言编程而不是只用一门语言编程,编程的思想可以跨越语言的限制,就比如作者举的那个用vb写窗体程序的列子。

架构的目的最重要的一点就是降低复杂度,在第二部分中作者提到了,软件工程其实是一个管理复杂度的过程。所以架构师的职责也包括了确定编码风格,因为一个统一的编码风格可以让程序员把更多的注意力转移到编写业务上。

里面有几句有意思的话:

P7 把主要精力集中于构建活动,可以大大提高程序员的生产率。

健康的生态环境中,海鸥吃新鲜的鲑鱼,鲑鱼吃新鲜的青鱼,青鱼吃新鲜的水蝽。这是一条健康的食物链。 如果环境被污染了,水蝽在污染的水域游泳,那么海鸥,食物链的最后一环吃下的不仅仅是是不健康的鲑鱼体内的垃圾,还有青鱼,水蝽体内的污染物。软件开发中,架构师吃掉需求,设计师吃掉架构,程序员,软件食物链的最后一环,消化掉设计。如果一开始就被污染了,我们就不要指望程序员快乐了。整个软件都会具有放射性,周身都是缺陷,绝对导致程序员脾气暴躁、营养失调。在我们规模不大的团队里,一个人身兼数职,伤害更大。所以,项目一开始就决定了它能否成功。

P24 避免使用错误的方法制造正确的产品

往往我们在软件开发中会很强调测试。的确、测试是质量的保证。但是测试只保证有质量的代码,却不保证有质量的设计。

P42 需求的 checklist

是否详细定义了系统的全部输入,包括来源、精度、取值范围、出现频率。是否详细定义了系统全部输出,包括目的,精度,取值范围、出现频率,格式?是否定义了机器内存和剩余磁盘空间的最小值?是否详细定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件的接口的变更能力?

P46 数据设计

我曾经很迷惑项目文档到底要写什么?这里列举的一些东西解开了我一些疑惑。如果你选择使用一个顺序访问的列表表示一组数据,就应该说明为什么顺序访问比随机访问更好。(往往随机访问更为高效)在构建期间,这些信息让你能洞察架构师的思想。在维护阶段,这种洞察力是无价之宝。 后面 P58 有个更为有趣的例子:Beth 想做丈夫 Adbul 家祖传的炖肉。Adbul 说,先撒上胡椒和盐,然后去头去尾,最后放在锅里盖上盖子炖就好了。Beth 就问了,“为什么要去头去尾呢?” Abdul 回答说,我不知道,我一直这么做,这要问我妈。他打电话回家一问,母亲也说不知道,她一直这么做,这个问题要问奶奶。母亲就打了个电话给奶奶,奶奶回答说,“我不知道你为什么要去头去尾,我这么做是因为我的锅太小了装不下”:D 架构应该描述所有主要决策的动机。

P51 过度工程

这个问题把握好并不容易。一方面,我们希望系统健壮,如果组成系统的各个部分只在最低限度满足健壮性要求,那么整体通常是达不到要求的。软件健壮性不取决于最薄弱的一环,而是等于所有薄弱环节的乘积。构架应该指出每个部分,程序员为了谨慎而宁可做过度工程,还是做出简单的能工作的东西就够了。有些东西是不应该过分花精力的,这个错误我们也犯过,尤其一些一开始就知道以后很可能要重构的部分,大量的精力花在里面很浪费。

P62 选择编程语言

我曾经也觉得 C++ 是万能的,这种想法很多 C++ 程序员也有。但是无可否认,每种语言的表达力是不同的。书在这页有一张表,如果 C 的表达能力是 1 的话,C++ 和 Java 就是 2.5 。而 perl 和 python 却有 6 。这就是我们选择游戏逻辑脚本编写的原因之一。另外对语言的熟悉程度是很影响程序员的效率的,所以我们不能独立的看语言本身的表达能力。P63 有个例子,用一群 Fortran 背景的程序员去用 C++ 编写一个新系统,结果他们编写出的是伪装成 C++ 的 Fortran 代码。他们扭曲 C++ 来模拟 Fortran 的不良特性并且忽略了 C++ 丰富的面向对象能力。我们这里有个现成的例子,一个 C++ 程序员用 C++/C 的方式写 Lua ,结果可想而知。到现在我还在叮嘱他,一定要理解,再理解 Lua 。lua 不是 C 。

P68 Programming into a Language

注意,这里是 into 而不是 in 。书这里用了一个 vb 的例子来说明,恰好我也有个例子。我们现在用 C++ 构建系统,C++ 里有个相当麻烦的东西,就是单件的生存期问题。一个 singleton 到底什么时候创建出来,什么是否析构,相信很多 C++ 程序员在构建大系统的时候都头痛过。据我所知,我们公司别的项目的同事到现在还在头痛这个问题。这次我做了一个约定,禁止任何模块的代码构造静态对象,也就是说,任何在 main 函数前自动的对象构造过程和 main 函数之后的自动析构过程都是不允许的。然后我们有一整套管理单件的方法供使用,这个问题被很好的解决了。我们再也没有为某个单件什么时候构造出来的,或是为什么他提前析构了的问题烦恼过。

P78 管理复杂度的重要性

我们做软件,就是在和问题的复杂度做斗争。有三个问题需要注意:用复杂的方法解决简单的问题;用简单但错误的方法解决复杂的问题;用不恰当的复杂方法解决复杂的问题。

P80 high fan-in 和 high fan-out

高内聚,低耦合很容易被重视。但是高扇入低扇出有时候会被忽略。这里是说,我们应该尽量的大量的使用某个低层次上给定的类(high fan-in) 而每个类都应该尽量少使用其他的类(控制在7个之下)。(貌似这也是我一直在思考的问题。之前一直以为是面向对象中类的爆炸)

另外附上云风大神的笔记:http://blog.codingnow.com/cloud/CodeComplete

网络编程的一些基础

发表于 2015-02-10 | 分类于 网络
/* IP地址结构, 历史原因使得ip被写在这么一个结构体里面 */
struct in_addr {
       unsigned int s_addr;    /* 这是大端顺序 */
}

#include 

/* 返回按照网络字节顺序的值 */
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);

/* 返回按照主机字节顺序的值 */
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);

/* 因特网程序使用inet_aton合inet_ntoa的函数来实现ip地址和点分十进制串之间的转换 */
#include

/* 若成功返回1, 失败返回0 application to network*/
int inet_aton(const char *cp, struct in_addr *inp);

/* 返回指向点分十进制字符串的指针 network to application */
char *inet_ntoa(struct in_addr in);


// hostinfo.c是DNS条目的栗子
/* DNS主机条目结构 */
struct hostent {
       char *h_name;        /* 域名 */
       char **h_aliases;    /* 别名 */
       int h_addrtype;        /* 地址类型一般为AF_INET(因特网) */
       int h_length;        /* 地址长度,以字节为单位 */
       char **h_addr_list;    /* 一个以Null结尾的in_addr结构数组 */
};

#include 
/* 根据域名查找DNS主机条目 若成功返回非NULL指针,若出错则返回NULL指针,同时设置h_errno。 */
struct hostent *gethostbyname(const char *name);

/* 根据ip地址查找DNS主机条目 若成功返回非NULL指针,若出错则返回NULL指针,同时设置h_errno。 */
struct hostent *gethostbyaddr(const char *addr, int len, 0)

/* 套接字地址结构 */
/* 普通套接字结构 */
struct sockaddr {
       unsigned short sa_family;    /* 协议族 */
       char sa_data[14];        /* 地址数据 */
}

/* 英特网套接字结构 */
struct sockaddr_in {
       unsigned short sin_family;    /* AF_INET */
       unsigned short sin_port;        /* 端口,大端顺序 */
       struct in_addr sin_addr;        /* IP地址,以大端顺序 */
       unsigned char sin_zero[8];    /* sizeof(struct sockaddr) */
}

#include 
#include 

/* 创建套接字 成功返回非负描述符,若出错则返回-1 */
int socket(int domain, int type, int protocol);
/* PS: clientfd = socket(AF_INET, SOCK_STREAM, 0) */

/* 套接字链接 成功返回非负描述符,若出错则返回-1 */
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

/* 地址绑定函数 */
int bind(int sockfd, struct sockaddr *my)addr, int addrlen);

/* 侦听方法 */
int listen(int sockfd, int backlog);

/* 接收链接 */
int accept(int listenfd, struct sockaddr *addr, int *addrlen);

#include 
/* 获取环境变量。我用bash试了一下,可以取到Bash中的环境变量。但是这个变量具体是进程中的还是系统的,还是cgi变量什么的,或者只能是bash变量什么的还不是很清楚*/
char* getenv(const char *name);

#include "csapp.h"
/* 以下函数是《深入理解计算机系统》中的函数 成功返回描述符,Unix出错返回-1,DNS出错返回-2 */
int open_clientfd(char *hostname, int port);

int open_listenfd(int port);

你好,2015

发表于 2015-01-01 | 分类于 日常

元旦了,从13年12月开始写Python已经有一年多了。2015年还是并着绝对清晰的原则去学习。

2015年有一个愿望,再去参加一次黑客马拉松。

2015年的主要学习内容还是Python,对Python虚拟机现在还没有达到绝对清晰的地步。

2015年要补基础,《计算机组成与设计 硬件/软件接口》,《深入理解计算机系统》,《编译原理》,《C专家高级编程》。

2015年努力改掉晚睡的坏习惯。

2015年做事情更加仔细,牛逼的人都很仔细,无论是春哥还是乌龙,还是饺子。

2015年学一门函数式语言,接触点分布式的东西。

2015年接触接触Nginx,一直觉得很牛逼的东西。

2015年多往github上提交东西,以后写代码有Octopus陪我了。

2015年至少再看两遍《Python源码剖析》

2015年想去学英语,多提高提高自己的英语水平。

2015年多运动。

2015年,加油。

关于单元测试的一些东西

发表于 2014-12-23 | 分类于 基础

刚进公司的时候第一次听说自动化测试和自动化运帷的概念。后来项目大了之后,平凡上下线,最耗时的事情就是测试了。之前就听说过TDD(Test-Dtriven Development),但是那个时候也只是听听,知道那么回事。近段时间交易项目平凡上下线,改动较大,由于测试不完全,导致了不少问题。于是我决定在项目中加入单元测试。

Python中标准库自带的单元测试模块(unittest也叫pyunit)。

pyunit中又四个重要的概念

test fixture : 每个固件执行完之后都会调用清理函数tearDown,在执行之前都会执行初始化函数setUp。

test case : 测试用例,就是一个最小单元的测试。注意:这里的测试用例的概念和unittest.TestCase的概念不一样,这里的test case是unittest.TestCase中以test开头的函数,TestCase之所叫TestCase只不过是因为很多初始化和清理的函数相同,可以写到一起。

test suite : 这是一些test case的集合。

test runner : 测试执行。

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

def setUpModule():
print 'setUpModule() executed'

def tearDownModule():
print 'tearDownModule() executed'

class ElevenTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print 'setUpClass() executed'

@classmethod
def tearDown(cls):
print 'tearDownClass() executed'

def setUp(self):
print 'setUp() executed'

def tearDwon(self):
print 'tearDown() executed'

def test_test1(self):
self.assertEqual(1, 2)

def test_test2(self):
self.assertEqual(1, 2)

这里的ElevenTest类继承自TestCase,但是这个类不是一个测试用例,它里面又两个测试方法,这里的测试方法才是测试用例。注意,只有方法名以test开头的才会被当成是测试方法,不然不会记录统计。

这里的每一个测试方法都是一个text fixture,也就是说没执行一个测试方法都会调用setUp和tearDown方法。ElevenTest也是一个text fixture,当ElevenTest执行完之后,会执行setUpClass和tearDownClass。同时这整个文件(module)也是一个text fixture,当这个module执行完之后会调用setUpModule和tearDownModule方法。

当然test suite可以包含很多unittest.TestCase,同时它也能包含其他TestSuite。

unittest模块只是争对单元测试开发的,用起来很方便,但是对于集成测试没有什么十分大的作用。之前我的做法是事先将所有的环境建好,所有的测试用力全部放在一套单独的数据库中,后来想想既然模块中提供了setUp和tearDown方法,就在初始化的时候就把测试的环境建好了,然后在tearDown将环境恢复。创建环境的大部分操作都是有相同部分的。所以可以单独给自己写一个测试基类。另外环境相同的测试方法可以放在同一个TestCase中。

unittest: https://docs.python.org/2/library/unittest.html#

以机器指令的角度看过程调用

发表于 2014-12-18 | 分类于 计算机组成原理

最近真是闲得蛋疼啊,看完了第二遍《Python源码剖析》之后开始看计算机组成原理。

回顾一下,自从接触了Python,扯了一大堆东西。从windows转移到Linux(现在用的是OS X—苹果系统)。从VS2012转移到vim,再到现在得emacs。从Sql Server到Mysql。从web开发,桌面应用开发,到各种服务和api的开发。

从米帮的基础框架sputnik开始扯,到看tornado源码,然后学HTTP协议和Linux IO多路复用模型,然后到C语言基础,然后到各种算法和数据结构,然后到编译原理,然后到现在的计算机组成原理。好大的一个栈。不知道什么时候能够全部出栈,不知道接下去是不是还要继续把状态机,图灵机什么的也压入栈。不得不感慨一句:跳出.net,外面又是一个十分广阔的世界。

好吧,不扯远了。

1
2
3
4
5
6
7
int leaf_example(int g, int h, int i, int j)
{
int f;

f = (g + h) - (i + j);
return f;
}
leaf_example:
# 保存现场
addi    $sp, $sp, -12        # 调整栈指针
sw    $t1, 8 ($sp)        # 保存$t1寄存器的内容到当前栈帧的栈底
sw    $t0, 4 ($sp)        # 保存$t0寄存器的内容到$t1的上面
sw    $s0, 0 ($sp)        # 保存$s0寄存器的内容到栈顶

# 函数体执行
add     $t0, $a0, $a1        # 计算g + h并将结果保存到$t0寄存器
add     $t1, $a2, $a3        # 计算i + j并将结果保存到$t1寄存器
sub    $s0, $t0, $t1        # 计算( $t0 - $t1 )并将结果保存再$s0
add     $v0, $s0, $zero        # 记录返回值

# 恢复现场
lw    $s0, 0 ($sp)        # 将栈顶数据加载到$s0寄存器中
lw    $t0, 4 ($sp)        # 将接下来的数据加载到$t0寄存器中
lw    $t1, 8 ($sp)        # 将栈底的数据加载到$t1寄存器中
addi    $sp, $sp, 12        # 将栈指针向上移动3个单位

# 控制转移
jr    $ra                # 将控制权转移给调用者

总得来说一个函数调用过程需要进行这几个步骤。首先是当前CPU现场得保存,然后执行函数体,最后恢复现场将控制权转移给调用者。

因为CPU中寄存器的数量有限,所以在寄存器数量不够的情况下,需要将寄存器中的数据转移到内存中(PS:我还知道转移的指令是数据传输指令:存字和取字指令)。

当然这里过程调用的现场保存主要还是为了方便恢复现场,只有恢复了现场,在被调用的过程执行完之后,当前过程才能继续正确执行。

被转移出去的数据是以栈的形式存放在内存中的,$sp就是这个栈的栈顶指针。

另外这几个寄存器也是有特定含义的,$sp之前说过了,是当前运行时栈的栈指针,$t0~$t9这10个寄存器是临时寄存器,用来存放临时变量,$s0~$s7保留寄存器,一旦使用由被调用者保存和恢复。$v0~$v1用来存放过程调用中的返回值,$a0~$a3用来存放过程调用中的参数信息。$ra是是用来存放返回地址的,还有一个抽象的东西,程序计数器,存放CPU要执行的下一条指令,其实程序计数器并不是真实存在的一个寄存器,每个体系的好像都不一样,在x86体系中是CS:IP这两个寄存器的组合。

计算机中补码到底解决了什么问题

发表于 2014-12-14 | 分类于 基础

大学的时候计算机组成原理曾经说过原码,反码,补码这三种整数的表示类型。那个时候2B一样,只知道通过这些可以把正数,负数,0的算术运算统一掉,具体怎么统一那个时候一直很疑惑。知道几个星期前我上网查了才明白。

首先CPU中的运算器只能执行加法运算,不能执行减法运算,但是减法运算可以转化成加上一个负数的加法运算。

第二个条件是计算机是有字长的,也就是说会有溢出的产生。(溢出是什么,你们自己百度吧,大概意思就是数据太大寄存器无法表示,就丢弃了一些数据)

补码的诞生主要是为了解决将减法转化成加法的问题。

考虑时钟上时间的计算,假设现在时针指向数字3,若问“6小时前时针指向的数字是几”,则可以:

  1. 将时针逆时针拨动6格。
  2. 将时针顺时针拨动12 - 6 = 6格。

这两种方式的结果是一样的。这里称12是“模。

故有 3时 - 6个小时 = 3时 + (12 - 6个小时),这里可以看到将减法转换成加法的过程,即“加上模减去绝对值的差”。

而我们知道,计算机是有字长的,通过这个思想我们就可以把计算机中的减法转化成加上一个“正整数”的方式来做了。

计算机中算术运算的模就是2的计算机字长次方。也就是最小能产生溢出的正整数。

所以x的补码的计算 x的补码 = 2^n - x(n是计算机的字长)
同时x + !x + 1 = 2^n(!x表示对x进行取反操作)
所以x = !x + 1(这就是这个计算公式的由来)

参考网址:http://www.douban.com/note/223507364/ (大神真是活跃于各个社区)

http://www.zhihu.com/question/20159860

12345
© 2019 Mr.Eleven
由 Hexo 强力驱动
主题 - 由 Mr.Eleven 修改自 NexT.Muse