俊杰's profileJohnny HomePhotosBlogListsMore ![]() | Help |
|
December 25 实现Exception机制 现代的编程语言都有Exception机制,相比返回值,Exception机制用起来实在太方便了。但是就像我这种人的性格,对使用方便的东西都会有一种警惕:安全不,性能消耗大不...以前因为性能的避讳我总是尽量不使用try...catch...。最近研究了一下Exception机制的开销,发现它的性能开销不如之前想象的那么严重。
我试着实现了一下在C++上的Exception机制,这里简单介绍一下(下面都是伪码):
try{
...
}
catch(type t)
{
...
}
可以编译成下面的语句:
push type;
push _catch_code;
call _util_try ;
....
_catch_code:
....
util_try是try操作的一个功能代码,它的代码如下:
util_try(void* addr,TYPE T)
{
stack.pushAddr(addr, T);
}
其中stack是Exception需要的一个运行栈,它存放的是处理对应Exception的代码入口和需要清理的局部变量。
结构如下:
处理代码地址3
---------------------------
清理变量地址3
---------------------------
处理代码地址2
---------------------------
清理变量地址2
---------------------------
清理变量地址1
---------------------------
处理代码地址1
........ 其中存放了抛出异常时需要清理的变量地址和异常的处理代码,供throw Exception时使用
抛出一个异常时的处理:
throw_exception( exp )
{
while(!stack.empty())
{
value = stack.pop();
if( value.type() == variable )
{
variable.clean();
continue;
}
else
{
if( value.exceptionType() == exp.type())
asm { "call value.codeAddr()"};
}
}
}
就是比较栈中的异常处理代码的类型与当前异常值的类型是否相配,是则表示该异常被捕捉,处理异常处理代码。并清除在异常处理前未析构的局部变量。
另外在下面情况时还要做一些附加处理;
构造一个栈内对象时,将其地址存入栈。
离开一个函数时,析构其所有局部变量的同时,也将其在栈中取出。
这样,一个异常处理系统基本实现了,虽然还缺少C++中的exception declaration等功能,也算基本完备。
至此再分析一下异常处理的性能开销:
构造栈对象时,附加栈操作。
离开函数时,附加栈操作。
由此可见,异常处理运行正常时,性能开销并不是很大。即使抛出一个异常,运行时间也是可以接受的。
如果是Java或C#,由于不需要清理局部栈对象,开销更小。
这个分析打消了我对Exception机制的性能怀疑。Exception机制也许算不上免费的午餐,但凭心而论价格还是相当合理的。
最后总结一句:开发领域,搞清楚一件事不能靠直觉或经验,下结论前研究一下。
(注:本文提到的异常处理的实现方式不是标准的处理方式。属自创,如有不妥,欢迎指正。) 也来谈谈Linux的线程 前几天和一个复旦的同行讨论Linux的线程。最后意见不一。
我的观点是Linux内核不支持线程,Linux只是在用户态下实现了符合Posix标准的线程。但他却不同意,坚持Linux有自己的线程,而且早就有了的。结果谁也不能说服谁。
为此,我专门查了一下相关的资料。最后明白:其实我们都错了。。。
Linux的线程不是像Windows那样的全核实现,也不是用户台下用Timer玩的一个trick。它是半核半用户,利用Linux的进程来模拟线程,外加系统库德外围管理。
先从Linux的几个系统调用说起:fork() vfork() clone()
fork就是最早的创建子进程的unix系统API,功能不用多介绍。Linux为fork加上了copy on write 特性,就是可写页面在第一次写入的时候才会被复制,实现的方法牵扯I386内存管理,就不介绍了。vfork()是为了弥补exec()的缺陷而设计的,它生成的子进程与父进程完全共享用户空间(包括内存的页表)。所以vfork生成的子进程无法与父进程同时存在的,父进程会一直睡眠到子进程exit()或exec()。至于clone,则可以算是fork()和vfork()的一个超集。它有可传递的参数,实现多种需求(是否共享用户内存空间,是否更换用户栈...)。
Linux的线程,也就是直观的pthread_create()等线程库(符合Posix标准)使用了进程来模拟线程。为了减少进程的开销,pthread_create()创建线程时,先开辟一块内存空间(作为线程的栈),然后调用clone()来创建一个与父进程共享用户空间的进程,将新的子进程的栈指针指向新开辟的空间(clone()有这种参数选项)。这样,新的进程与父进程共享内存空间,但有不同的运行栈。加上新进程会接受内核的统一调度,完全达到了线程的需求。
至于Linux线程库的核外管理,这里就不作介绍了。
显然Linux对线程十分照顾,并没有利用Timer来戏弄开发人员的感情,也为其在核内作出了很大的支持。至于用进程模拟的线程的“血统”问题,就不必深究了。进程和线程并没有明确的规范,只是一个概念。Windows的线程也许更直接,但Linux的线程在性能上并不落后。
争论还是有点用的... |
|
|