Fantasy and Fiction


  • Home

  • Archives

设计模式之装饰器/面向切面编程在Python和C中的体现

Posted on 2015-09-20 | Comments:

最近学习到python中的装饰器有点意思,函数定义前面声明一句话,即可改变函数的部分行为。不同的声明,可以从不同方面改变函数的行为,就像一栋房子重新装修了下,就有了不同的风格。
小小的提炼却是大大的进步。
比如

1
2
3
4
5
6
7
8
9
10
11
12
def hello(fn):
def wrapper():
print "hello, %s" % fn.__name__
fn()
print "goodby, %s" % fn.__name__
return wrapper

@hello
def foo():
print "i am foo"

foo()

运行后

1
2
3
4
$ python hello.py
hello, foo
i am foo
goodby, foo

追究其本质,首先是因为在python中一切皆对象,函数也不例外,所以有了动态修改函数指向对象的机会,那么经过修饰器修饰后,foo其实已不是原来的foo,而是被包装过的:foo = hello(foo),再次调用时,新的foo指向了包装后的新函数,从而包括了原来的foo功能和包装器的新功能。

装饰器作为一种设计模式,在插入日志,性能测试,事务处理方面有着广泛应用。

作为一个C起家的程序员,看完这些突然意识到,这样的模式其实在C实践中也有很多应用,比如上面插入日志的例子,在C中就是:

1
2
3
4
void foo(int x)
{
print("in foo: %d\n", x);
}

修饰下,插入日志。

1
2
3
4
5
6
7
8
9
10
11
void _foo(int x)
{
print("in foo: %d\n", x);
}

#define foo(x) \
do { \
printf("hello\n"); \
_foo(x); \
printf("goodbye\n"); \
} while (0)

更进一步,C语言中有种特殊的调试技巧:某个函数被广泛调用,我们想追踪这个函数在动态执行时,被哪些函数调用,以及传入的参数是什么名称。
最简单粗暴的方法就是找出所有调用的地方,前面插入跟踪代码,打印调用函数和参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define ARG_1	50
#define ARG_2 100

void foo1()
{
printf("caller %s, argument ARG_1\n", __func__);
foo(ARG_1);
}

void foo2()
{
printf("caller %s, argument ARG_2\n", __func__);
foo(ARG_2);
}

显然这样做既费时痛苦又无扩展性,如果想跟踪其它函数,还得再这样改一遍。
改进的做法就是用宏定义同名覆盖函数,然后加上调试信息。

1
2
3
4
5
6
7
8
9
10
void _foo(int x)
{
print("in foo: %d\n", x);
}

#define foo(x) \
do { \
printf("caller %s, argument %s\n", __func__, ##x); \
_foo(x); \
} while (0)

只需如此修改,其它所有调用foo的地方无需修改即可达到前述跟踪目的。这里面蕴含的思想其实跟修饰器是类似的:通过修改原函数的指向,在原函数的基础上添加新功能。

当然这里有三点区别:

  1. C实现的修饰是编译时修改,而python是运行时动态修改;
  2. C函数的修饰器就是宏定义,而且需要修改原函数原型的名称,以防命名冲突;Python的修饰器是另一个函数,修饰后原函数被覆盖。
  3. C函数被修饰后,并没有生成新的函数,而是在原来的函数调用处添加了一些语句,而python简单彻底,重新生成新的函数对象。

参考文章:
coolshell的Python修饰器的函数式编程
Python装饰器与面向切面编程

树莓派上配置动态域名解析Oray DDNS

Posted on 2015-09-12 | Comments:

raspberry pi玩到后来不可避免会玩上DDNS,即动态域名解析,众所周知拨号上网拿到的公网IP每次可能不一样,为了从公网访问拨号猫后面的内网PC,需要外部提供一个查询机制,即DDNS,在这个外部查询系统注册一个域名之后,内部再装一个客户端,里应外合,把拨号拿到的公网IP通知给公网上的查询系统。这样公网用户每次要访问内网的PC,首先到这个确定地址的公网查询注册的域名,然后转换成对应的IP,从而达到访问内网PC的目的。
关于如何在Raspberry Pi设置花生壳,官网已经有介绍。
需要注意的是运行oraynewph需要root权限。

Update@2015-09-13
花生壳的免费域名实在太不好用,第一次成功更新A记录后,不管后面拨号再怎么重换地址,花生壳系统的A记录和路由的WAN地址始终不一致,还不能手动改。考虑到使用的电信网络,应该不存在ISP NAT的情况,折腾数天不妥,怒弃之。。。
死活不更新
另外考虑到花生壳的免费域名,可能会被无聊人士盯上乱扫描,万一自家的路由器,树莓派等等有什么漏洞,城池难保。折衷的办法就是自己麻烦一点,让rapsberry pi主动email报告IP。后面完善了回来补充。鉴于ISP NAT的问题,WAN的IP也不是公网IP,这个方案也被否决了。
Update@2015-09-14
换了路由自带的花生壳客户端,同样的不一致问题,只能归咎于免费域名的故意限制。。。尝试其他DDNS服务。。。
Update@2015-09-14
3322也一样,ip138也显示这个地址,看来真的是有ISP NAT。。。
Update@2015-09-15
对于ISP NAT,花生壳官网介绍有解决方案,只要你升到专业服务:(
说实话这个解决方案只是重新选择了下接入线路,背后原理莫名其妙,貌似再没有其它家有类似的解决方案。
初步搜索了下,在有ISP NAT的情况下,如果自己用,VPN不失为一个靠谱的解决方案。

设置nginx运行Hexo生产的静态网站

Posted on 2015-09-12 | Comments:

hexo基于nodejs实现,本质上都是V8引擎解释js语句执行,效率必然比不上本地代码。在raspberry pi资源这么有限的环境下,hexo server不是个好主意,那么以速度著称的nginx就是个必然选择。

1
2
3
4
sudo apt-get install nginx
sudo /etc/init.d/nginx start
sudo /usr/sbin/update-rc.d -f nginx defaults
sudo vi /etc/nginx/sites-enabled/default

找到server的配置,修改root目录

1
2
3
4
5
6
7
server {
...
server_name localhost;
index index.html index.htm;
root /home/pi/hexo/public;
...
}

修改完后别忘了让nginx重新读取配置

1
sudo /etc/init.d/nginx reload

为了能根据blog源代码的改变及时更新public,可以让hexo在后台进行监视

1
hexo generate --watch

在树莓派上安装hexo

Posted on 2015-09-12 | Comments:

之前烧录的Raspberry image已经自带了很多工具,其中包括git,但是hexo还不在里面。用apt-get install nodejs装了一个,版本非常老0.6.xx。想要更新到最新?没问题,官网直接为树莓派的老旧CPU编译好了。

1
2
3
4
5
wget http://nodejs.org/dist/latest/node-v4.0.0-linux-armv6l.tar.gz
tar xf node-v4.0.0-linux-armv6l.tar.gz
mv node-v4.0.0-linux-armv6l/ node
mv node /usr/local/
chown -R pi.pi /usr/local/node

如果后面有更新版本,上面的版本号注意替换掉。
设置环境变量

1
2
3
4
echo "PATH=$PATH:/usr/local/node/bin" >> ~/.profile
echo "export PATH" >> ~/.profile
source ~/.profile
node -v #检查路径是否设置正确

安装hexo

1
2
3
4
5
npm install -g hexo
mkdir -p /home/hexo
cd /home/hexo
hexo init #添加一个博客站点
npm install

clone blog hexo源文件到本地

1
git clone -b blog_code https://github.com/pansila/pansila.github.io.git

执行后会将git上blog_code分支的文件放到pansila.github.io目录,这个目录放置了hexo源代码(网页静态文件放在了master branch),因为之前在hexo目录建立hexo环境,所以要让hexo在git的文件上跑起来,需要将pansila.github.io目录下的所有东西复制到hexo目录

1
2
cp pansila.github.io .. -R
rm pansila.github.io -R

试了下速度,rapberry pi上跑hexo性能不如乐观。。。
同样的文件raspberry pi上,load files要24s,generate files要23s
PC上load files只要1s,generate files 580ms

参考文章:树莓派安装node.js来跑Hexo静态博客

New Toy - SanDisk Ultra 16G Class10 UHS-I

Posted on 2015-09-12 | Comments:

自从iPhone开启自带Flash Rom风潮以来,外置TF卡在手机上越来越不受待见,这次为了把吃灰多年的Raspberry Pi拿出来晒晒太阳,决心新入手一块TF卡。
吐槽下京东可能除了物流价格上真心没啥优势。。。
先来一张大头照
美照
然后照例验证正品,没问题。
测试读写速度
ATTO Disk BenchMark
最大速度,读45MB/s,写17MB/s左右。比官网号称48MB/s稍显乏力。。。
写入Raspberry Pi image
Win32 Disk Imager
写入速度差不多在15~16MB/s。

附上对比测试,金士顿未知Class卡
Kinston unknown TF
速度测试
ATTO Disk BenchMark
看起来应该也是Class 10级别的卡。
Raspberry Pi板载速度测试敬请期待。。。
接上文接着测试Raspberry Pi上,方法简单粗暴,就是dd拷贝测试:
写

1
2
dd if=/dev/zero of=~/test.tmp bs=500K count=1024
524288000 bytes (524 MB) copied, 36.7958 s, 14.2 MB/s

读

1
2
dd if=~/test.tmp of=/dev/null bs=500K count=1024
524288000 bytes (524 MB) copied, 28.128 s, 18.6 MB/s

这个数据与官方同款测试基本一致(详见下方链接),猜测受限于raspberry pi的SDIO总线速度,读写速度均有一定程度下降。

树莓派SD卡参考速度

cache exercises - Ex. 1 memory read optimization

Posted on 2015-09-10 | Comments:

Cache对于现代处理器提升性能有多重要就不多说了,最近看到这方面的练习,正好来实践下。
原文来自伯乐在线请猛击这里
第一个例子解释cache一次从memory读取的大小,这是可能与cache line不一致的,具体多少可以通过统计得出。
原文使用了C#例子,正好最近学习python,这里来模拟下。

1. 基于list的测试

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
buf_len = 64*1024*1024
buffer = [0] * buf_len

# update @2015-09-20
#def calc_at_every(step):
# i = 0
# while i < buf_len:
# buffer[i] *= 3
# i += step

def calc_at_every(step):
for i in xrange(0, buf_len, step):
buffer[i] *= 3

if __name__ == '__main__':
from timeit import Timer
results = []
for i in range(1, 33):
#print "test starts for step %d" % i
t = Timer("calc_at_every(%d)" % i, "from __main__ import calc_at_every")
#print t.timeit(1)
results.append(t.timeit(1))

import matplotlib.pyplot as plt
plt.plot(range(1, 33), results)
plt.ylabel('runtime per step size')
plt.xlabel('set size')
plt.show()

最直接的模仿就是list,可惜结果差3个数量级,看下图:
list test
步长为1时,遍历一遍需要12秒之多,例子中是80ms,大家感受下,cache肯定没有发挥作用。事实上随着step加倍,运行时间减半,完美的指数函数,完全忽视cache。。。
事实上python中list跟C/C++中的array并不一样,是一个包装起来的行为类似array的对象,其占用的size远远超过元素本身之和。
>>> sys.getsizeof([])
64
这里其实已经做过优化,并没有使用for ... in ...这样的pythonic遍历方式,因为产生一个索引list会占用同样多的memory,而且也有memory read的cache问题。难道用python做性能测试就是个杯具。。。
update @2015-09-20
xrange正是为这样的迭代所优化,它不会实际产生一个遍历list,而是按需生成数据,因此内存效率和性能都大有改善。PS:range在python3中升级成为generator
list test by xrange
可以看到1步遍历提高了约33%的性能,遍历一遍需要大约8秒(缩短4秒),虽然离cache的结果还差得远,但就遍历来说是个很好的优化。

2. 基于array的测试

既然list不够快,那换能够直接接触memory的array。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from array import array

buf_len = 64*1024*1024
buf = [0] * buf_len
buffer = array('i', buf)

def calc_at_every(step):
for i in xrange(0, buf_len, step):
buffer[i] *= 3

if __name__ == '__main__':
from timeit import Timer
results = []
for i in range(1, 33):
#print "test starts for step %d" % i
t = Timer("calc_at_every(%d)" % i, "from __main__ import calc_at_every")
#print t.timeit(1)
results.append(t.timeit(1))

import matplotlib.pyplot as plt
plt.plot(range(1, 33), results)
plt.ylabel('runtime per step size')
plt.xlabel('set size')
plt.show()

结果更差。。。
Array test by xrange

3. 基于numpy.array的测试

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
from array import array
import numpy as np

buf_len = 64*1024*1024
buf = [0] * buf_len
buffer = np.array(buf)

def calc_at_every(step):
for i in xrange(0, buf_len, step):
buffer[i] *= 3

if __name__ == '__main__':
from timeit import Timer
results = []
for i in range(1, 33):
#print "test starts for step %d" % i
t = Timer("calc_at_every(%d)" % i, "from __main__ import calc_at_every")
#print t.timeit(1)
results.append(t.timeit(1))

import matplotlib.pyplot as plt
plt.plot(range(1, 33), results)
plt.ylabel('runtime per step size')
plt.xlabel('set size')
plt.show()

惨不忍睹。。。

numpy.array test

to be continued

调整MAC OS X系统所在分区大小

Posted on 2015-09-05 | Comments:

开始安装Yosemite时,划分的分区过小,安装Xcode和IOS SDK时磁盘已经不够。作为爱偷懒的程序员,自然是能不重新安装最好不重装。

第一个想到的办法是Windows时代常用的Ghost。首先新划分一个合适大小的分区,格式不格式化无所谓,设置分区为激活状态。这里推荐分区助手,无损调节分区大小。
然后USB启动到PE,Ghost搬移partition,全程无压力。完成后启动新的Yosemite分区,发现分区大小也复制了过来。此法不通。

很快发现,Yosemite自带有Disk Utility,里面有Restore功能,专业系统搬移!Apple简直是太有预见性!
所以只要把原来的分区内容restore到一个更大的分区,就等于调节了分区大小。
不过要搬移的disk在Yosemite启动后就不能操作了,所以要从Yosemite的安装盘启动,里面也有这个工具。而且速度比Ghost还要快的多,3分钟搞定。

被GRUB2破坏MBR后Yosemite的恢复

Posted on 2015-09-05 | Comments:

之前已经成功安装了Yosemite到Thinkpad T420上,然而不幸的是,楼主手贱在玩Ubuntu 14.04时把grub2安装到了所有硬盘上。
不得不吐槽Linux系统桌面软件不如Windows成熟,这么重要的操作,Ubuntu自带的这个grub2配置GUI程序,在应用修改时居然没有一声提示,所有tab下的设置都统统设了下去。欲哭无泪。

由于之前已经成功安装Yosemite,而Grub2理论上只会破坏MBR以及MBR到VBR之间的数据,所以只要恢复这部分数据就可以了。
那么原来的这部分数据是什么呢,是Yosemite自己的bootloader,由于楼主之前用Multibeast安装了Chimera bootloader,所以首先要找到这个bootloader的boot0数据,即MBR数据。

有两种办法可以拿到这个数据:

  1. 找到相应版本的Chimera pkg,这个文件可以用解压缩软件打开,在路径Chimera 4.1.0.pkg\chimeraV41.pkg\Payload\Payload~\.\usr\standalone\i386\
    下可以看到有四个文件:boot,boot0,boot0md,boot1h,boot是存放于HFS文件系统下的Chimera主体程序,boot0存放于MBR,boot1h存放于VBR,这几个文件的关系有兴趣的可以看GRUB的维基介绍,原理一样。

  2. 另一个地方就在Yosemite系统里面,/usr/standalone/i386,里面也有这几个文件。

找到这几个文件后,就可以动手实施恢复。以下高能,模仿慎重
首先要独立于要操作的硬盘所在的系统进行操作,比如可以用USB盘启动PE,或者Yosemite的安装盘,或者其它硬盘上的系统。否则会提示无法操作成功,目标硬盘被占用。
楼主使用其他硬盘上的Ubuntu操作,使用最简单的dd指令即可恢复。
sudo dd if=/media/username/bakup/boot0 of=/dev/sdc bs=440 count=1
里面的路径注意替换成自己的,440字节是为了避免把分区表覆盖,同时还要保留分区表前面6个字节的磁盘签名。关于MBR可以猛戳MBR维基。
最后,注意要把活动分区设为Yosemite所在的分区,这个boot0,似乎只认活动分区,不认HFS分区。

fbinst/fbinstTool no menu问题

Posted on 2015-08-30 | Comments:

安利下,fbinst是个万能启动盘制作工具,能引导多种类型系统,网管必备。。。
使用fbinstTool制作启动盘时,碰到无法启动引导程序问题,提示”no menu”信息。

错误提示

google搜索没有结果,只好尝试自己排查问题。首先fbinst是个开源程序,因此最简单办法就是看代码为何出现如此错误。
搜索no menu,可以看到相关代码在fbmbr.S

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	movb	$FBM_TYPE_MENU, %cl
call find_first_item

1:
jc 1f
subb $1, %bl
jc boot_item
call find_next_item
jmp 1b

1:
movw $ABS(err_no_menu), %si
jmp fail

...

err_no_menu:
.ascii "no menu\0"

可以看到是在查找FBM_TYPE_MENU这个类型的数据时失败所致,继续回溯调用路径,可以看到这部分代码在parse_menu label下,而调用parse_menu的地方只有一个

1
2
3
4
5
6
7
8
9
10
11
12
movw	$ABS(menu_file), %si
pushw %si
call check_file
popw %si
jc 1f
call load_file
jmp parse_menu

...

menu_file:
.ascii FB_MENU_FILE "\0"

FB_MENU_FILE就是要查找的目标,看看它是什么,搜索可以发现其定义在fbinst.h,#define FB_MENU_FILE "fb.cfg"
所以问题是这个文件找不到,这个文件其实是有的,它就是fbinst菜单

fbinst菜单
原来默认启动项指向了一个不存在的条目,这才想起来因为之前配置过其它引导条目,后又删除忘记恢复default所致。。。
修改后,正常启动。
正常启动

Install clover on USB stick

Posted on 2015-08-29 | Comments:

clover支持UEFI和legacy两种BIOS启动,并且可以随意搭配GPT分区和MBR分区安装的系统,但是需要调教参数。
首先有一些概念要区别。
We can boot from a UEFI system and install Windows in UEFI\GPT mode, or boot in BIOS\CSM mode and install Windows in MBR mode.

123

Pansila

21 posts
© 2018 Pansila
Powered by Hexo
|
Theme — NexT.Gemini v6.0.6