zoukankan      html  css  js  c++  java
  • Python 对象模型 -- (转)

    面向对象的纯粹性

    在很久很久以前,C++还被称为面向对象语言(现在一般称为多范式通用语言),人们就对C++的面向对象的纯粹性提出了质疑,主要有以下几点:

    1. 并非所有的对象都是对象(很拗口?),比如指针本身不是对象,函数不是对象,基本数据类型不是对象。
    2. C++对于面向对象中“消息传递”的设计采用的是方法调用的形式,这种方式不能完整的表达“消息传递”的语义。

    对于第一点,我们最直观的感受就是,我们无法写如下的代码:

    1 int a = 10;
    2 string b = a.toString();

    我们能做的只是:

    1 int a = 10;
    2 char buffer[MAX_STRING];
    3 itoa(a, buffer, MAX_STRING];
    4 string b(buffer);

    所以这里a是一个值,而非一个对象,它只是数据,而没有与之相关的操作。
    我们看看Lua和Python的设计,就可以更加明了的理解值类型的设计的差异个中差异。

    Lua和Python的差异

    在Python中,所有的对象都继承自PyObject类(Python本身是使用C语言编写的,Python在C语言中模拟了一套类似C++的OO机制,支持继承、多态等面向对象特性),在所有需要操作变量的地方,都统一使用PyObject*来访问,例如,在函数调用时,为函数分配堆栈空间的时候,代码就类似这个样子:

    1 PyObject** stack = (PyObject**)malloc(sizeof(PyObject*)*maxStack);

    所以,本质上来说,所有的对象都是在堆上分配的,我们访问的都是对象的指针。
    再来看看Lua的设计,在Lua中,使用一个结构体来保存对象:

    01 typedef struct {
    02   int t;
    03   Value v;
    04 } TObject;
    05 typedef union {
    06   GCObject* gc;
    07   void* p;
    08   lua_Number n;
    09   int b;
    10 } Value;

    所以,保存一个变量,在32bit的机器上,Lua使用12个字节。对于值类型(lua_Number)之类的变量,并不会从堆中分配,而是直接在栈上分配(事实上也是从堆上分配,但是是一次性分配的),例如在函数调用的时候,要分配寄存器的空间的时候,相当于这样的代码:

    1 TObject* locals = (TObject*)malloc(sizeof(TObject)*localCount);

    这样看起来似乎都是一条malloc调用,那么性能差异在哪里呢?

    分配空间时的性能差异

    假设我们在两个虚拟机里面都需要分配一个4个数值类型的栈上变量或者寄存器的空间,那么在Python中,等效代码是:

    1 PyObject** stack = (PyObject**)malloc(sizeof(PyObject*)*4);
    2 for (int i = 0; i < 4; i++)
    3   stack[i] = (PyObject*)malloc(sizeof(PyIntObject));

    这里的代码只包含分配空间,不包括初始化变量的部分。对应在lua虚拟机中的相对代码就是:

    1 TObject* locals = (TObject*)malloc(sizeof(TObject)*4);

    可以看到,Lua的模式在处理数值变量的时候,将会少4次内存分配操作,这对于每秒执行数以万级的函数调用的时候,对性能的影响非常明显。

    执行运算时的性能差异

    现在我们再看看执行数值运算的时候性能会有怎样的差异,我们还是以上一篇文章中说到的,1+2的数学运算为例,将相应的bytecode翻译成实际执行的C代码来做:
    Python ByteCode

    1 push 1
    2 push 2
    3 add

    对应的C代码

    01 // 为堆栈分配内存
    02 PyObject** stack = (PyObject**)malloc(sizeof(PyObject*)*2); // 一次内存分配
    03  
    04 // push 1
    05 STACK_ADJ(1);
    06 STACK_TOP = PyInt_FromLong(1);  // 一次内存分配
    07  
    08 // push 2
    09 STACK_ADJ(1);
    10 STACK_TOP = PyInt_FromLong(2);  // 一次内存分配
    11  
    12 // add
    13 PyObject* result = PyNumber_Add(STACK_SECOND, STACK_TOP); // 一次内存分类
    14 STACK_ADJ(-1);
    15 STACK_TOP = result;

    我们可以看到,这样一次简单的计算,进行了4次内存分配操作(其实还有2次内存释放操作)。
    我们再来看看Lua的ByteCode:

    1 loadk 0 1
    2 loadk 1 2
    3 add 2 0 1

    对应的C代码是:

    01 TObject* locals = (TObject*)malloc(sizeof(TObject)*3); // 一次内存分配
    02  
    03 // loadk 0 1
    04 locals[0] = lua_Number(1); // 没有内存分配
    05  
    06 // loadk 1 2
    07 locals[1] = lua_Number(2);
    08  
    09 // add 2 0 1
    10 locals[2] = lua_Number(locals[0].v.n + locals[1].v.n);

    只有1次内存分配操作,没有内存释放操作,这样,速度的差异就非常明显了。

    文章来源:http://zoomq.qiniudn.com/ZQScrapBook/ZqFLOSS/data/20111002195204/

  • 相关阅读:
    利用ASP.NET 2.0创建自定义Web控件
    Sublime Text 2 使用心得
    Sublime Text 2 使用心得
    django分页组件pagination
    蛙蛙推荐:如何编写高质量的python程序
    django单元测试编写
    python数据库操作
    django分页组件pagination
    python数据库操作
    蛙蛙推荐:如何编写高质量的python程序
  • 原文地址:https://www.cnblogs.com/fendou-999/p/3518953.html
Copyright © 2011-2022 走看看