俊杰's profileJohnny HomePhotosBlogListsMore Tools Help

Blog


    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的线程在性能上并不落后。
         争论还是有点用的...
    December 22

    重开技术贴宣言

    本blog最近将放上一些本人总结的技术文章。敬请大家留意。