您的位置:首页 > 其它

新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (1)

2011-08-15 23:07 477 查看

新一代脚本语言引擎Cx -- 应用之AutoCAD二次开发 (1)

tongshou@gmail.com



“十年磨一剑”,Cx经历十年的研究、沉淀、设计。。。



01. 拥有ARX的强大功能,具备LISP的灵活、简便、动态等特性

02. 非常方便与其他程序的沟通和调用DLL功能

03. 支持反应器

04. 高度动态

05. 尽力静态

06. 简洁的数据操作

07. 支持DCL, MFC

08. 支持ForEach, map

09. 良好容错、除错能力

10. 支持(自)覆盖 / 热替换

11. 支持正则表达式

12. 支持多线程编程、并行计算



(2):

13. 实时可控的动态数据处理方式,高级指针、GC

14. 内建大量数据类型及处理方法

15. 支持超高精度整数和超高精度实数

16. 类定义中的“简化型”多重继承、运算符重载、仿函数

17. 模块化(#Package)编程

18. 参考(Reference)/映射(Refect)型 变量和数据

19. 传址参数、署名参数(缺省参数)的函数定义

20. 参数包(Valist)、闭包(Closure)、子函数

21. 窗口函数的消息处理、事件反应、虚拟调用

22. 方便编写键盘重设置(键盘宏)、成倍提高电脑操作速度

23. 支持小型2D-CAM系统,方便编写CNC加工的应用程序

24. 装载、命令行操作

25. 其他说明



(3): (待续)





一种新的强大的脚本语言引擎,Cx语言, 拥有C/C++语言的强大功能,又具备普通脚本语言的灵活、便利、简洁、动态、非编译等特性,已成功用在AutoCAD、ZWCAD和办公室软件WPS等,成为其强大、灵活的二次开发程序语言。本文介绍用在AutoCAD里的Cx。



01. 拥有ARX的强大功能,具备LISP的灵活、简便、动态等特性

对于AutoCAD,其ObjectARX(以下简称ARX)的编译型C/C++程序,功能可以非常强大,但是,作为编译型的C/+C++程序语言,门槛高、编程繁琐、编译扰人、共享不易、版本敏感等,让不少人转到C#和J***A,但是C/C++中有很多很值得人们珍惜和学习的东西!这些年来,AutoCAD每年升级一次,原有的ARX应用程序,就面临升级/版本兼容问题,这既会严重影响人们对CAD升级的欲望,也影响人们对ARX应用程序欲望。

Cx以ARX方式结合到AutoCAD中。Cx程序的书写格式、运作方式、数据结构等都非常类似C/C++程序,支持包括指针、回调函数、位域、MFC、Win32 API等在内的C/c++大部分数据结构和功能,也可以进行内存直接处理的非常底层的操作。

与C/C++不同,Cx的内存处理基本上是自动的。与.NET的内存托管、J***A的GC不同,Cx的内存处理是实时可控的。Cx里可以存在多个变量/结点同时拥有同一动态数据,却不会引发死循环而造成内存永久不能释放的问题。

文件句柄、资源句柄、CAD实体对象句柄等,如果Cx程序没有显示Close或释放,Cx会在拥有句柄的变量/结点的作用域失效时、或数值要被覆盖时,自动关闭或释放这些句柄。象句柄的打开与关闭、多线程中数据同步锁的取得与释放等需要成对的操作,Cx会严格控制,一旦程序异常中断,Cx会自动进行善后处理,没有及时关闭或释放的,会将其关闭或释放,这对于脚本语言来说非常重要。如果CAD实体没有及时close(),对CAD来说基本上是致命的崩溃、甚至烟消云散。

作为脚本语言,Cx同时又具备普通脚本语言的各种高级特性,与AutoLISP一样,很少受CAD平台的版本影响,编程、装载、调用、功能共享等,都非常方便。在功能、表达能力、运行速度等方面,CX显著超越AutoLISP。

在数据类型方面,Cx既支持C/C++方式的强制类型数据,也支持普通脚本语言的泛型数据。

先看看下面一个例子,这个例子取自网络牛人“雨中飞燕” 提供的求1000阶乘算法的C代码,通过改写函数头,long前加一个点,就成为Cx语言程序了:

#define Nx  1000

public function [C:C00]()
{
    .long s[Nx]={1,1}, n=Nx, t=2, a=1, b=0;
     for (; a <= *s || (++t <= 1000 ? (b=0, a=1) : 0);  (*s == a++ && b) ? (*s)++ : 0)
          s[a] = (b += s[a] * t) % 10000, b /= 10000;

     for (printf("%d", s[*s]); --*s > 0; ) printf("%04d", s[*s]);
}


相信绝大多数的脚本语言无法书写表达如此精练的程序(不是可读性^_^)。这个例子足以显示Cx语言良好的表达能力、与C的亲近、相似程度。

Cx定义函数,以关键字 def 或 function 标识,函数名称可以用任何字符,当然包括使用中文,当使用"不规范字符"的名称时,用[], ‘’,或""界定,上面定义的这个函数,用[ ]界定,名称是 C:C00,如果函数定义用 关键字public前置修饰,那么,定义的这个函数,会在CAD的命令队列中注册一个命令C00,这样,操作者在CAD命令行中输入 C00时,就会启动相应的函数。这点,非常类似AutoLISP函数的定义。

ARX中的大部分类定义都可以映射到Cx中,在Cx中使用这些类,宛如在C/C++中使用,因此,熟悉C/C++,尤其熟悉ARX程序的人,对Cx程序应该不会陌生。

Cx操作CAD可以使用多种方式编程: ARX方式, ADS / LISP方式, ActiveX / VBA方式,C动态编译方式等,其中以ARX方式编程运行效率最高,如果不是操作CAD,而是纯粹的数值计算,C的动态编译方式的运行效率最高,它基本上等效C的静态编译程序。

下面以同时缩放5万个圆实体半径为测试实例,展示CX不同的编程方式。电脑配置:XP, CPU: Q6600 @2.4G HZ(4 核),RAM: 2G。这也是本文所有测试使用的电脑配置。预先画好的5万个CIRCLE实体,启动相应命令时,选取该5万CIRCLE,再输入缩放值,比如 2.0。运行结果如下表:

---------------------------------------------------------------

程序语言 耗时(毫秒) 比较

---------------------------------------------------------------

Cx (Arx 普通方式) 297 1

Cx (Arx 优化方式) 219 0.74

Cx (Ads 方式 ) 3250 10.94

Cx (ActiveX 方式) 828 2.79

Cx (C的动态编译方式) 2766 9.31

AutoLISP 4109 13.84

ARX (C/C++ 编译型) 94 0.32

ADS (C 编译型) 2687 9.05

---------------------------------------------------------------

无论是ADS的静态编译型的C程序,或动态编译型的C程序,还是AutoLISP程序,对CAD实体的操作,都是通过动态生成resbuf数据链间接操作,因此相当耗时,C程序的高效性在这里无法表现出来, 如果只是数值运算,编译型程序会展现很好的运行效率,象上面第一个程序C:C00的求1000阶乘中的循环部分,经过测试,得到如下结果:

-------------------------------------------------------------------------------------

程序语言 耗时(毫秒)

--------------------------------------------------------------------
Cx (普通方式) 375
Cx (TrySpeed优化方式) 172
Cx (动态编译方式) <10
C (静态编译) <10
---------------------------------------------------------
可以看出,这里的动态编译的程序,其运行速度与传统静态编译型的C程序没有什么差别,显著超越非编译型的脚本语言。
ActiveX方式操作CAD,没有牵涉数据链,因此运行效率较高,但是ActiveX接口本身存在一定的效率损耗。



Cx程序展示出高度的灵活性和适应性,能够以各种方式混合编程,既可以根据实际运行效率的需要,也可以根据编程者自身的喜好、掌握的技能、水平状况编写相应的程序。无论ARX编程者,还是VBA编程者,或AutoLISP编程者,在Cx里都可以找到自己熟悉的编程方式。

优化后的Cx程序,其操作CAD的运行速度达到ARX C/C++编译型程序的一半,而其程序码的编写比C/C++程序要来得简洁。AutoLISP程序是所有程序中 运行最慢的。



各种程序码如下:

1)ARX 方式, 耗时 297 ms (毫秒) 或 219 毫秒:

public function [C:C01]()
{
    ss = ssget( buildlist(0, "CIRCLE"));
    if (!ss) return;
    sc = getreal("\nScale<2.0> =");
    if (!sc) sc = 2.0;

    t1 = GetCurrentTime();
    for (en in ss) {
        acdbOpenObject(pEnt, en, AcDb::kForWrite);
        pEnt->setRadius( pEnt->radius() * sc);
    }
    printf("Time = %d\n", GetCurrentTime() - t1);
}

该程序处理5万个CIRCLE运行耗时297毫秒。

用Cx提供的AcDbObjectOpenArray类,把选择集中的所有实体一次性open(),再逐一操作实体,最后一次性close()所有实体。这样可以进一步提高Cx程序的运行效率,仅仅耗时219毫秒。



public function [C:C02]()
{
    ss = ssget( buildlist(0, "CIRCLE"));
    if (!ss) return;
    sc = getreal("\nScale<2.0> =");
    if (!sc) sc = 2.0;

    t1 = GetCurrentTime();
    A = new AcDbObjectOpenArray(ss.len);
    A.appendSS(ss, AcDb::kForWrite);

    for(pEnt in A) {
        pEnt->setRadius( pEnt->radius() * sc);
    }
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
}



2)ADS 方式, 耗时 3250 ms (毫秒):

public function [C:C03]()
{
    ss = ssget( buildlist(0, "CIRCLE"));
    if (!ss) return;
    sc = getreal("\nScale<2.0> =");
    if (!sc) sc = 2.0;

    t1 = GetCurrentTime();
    for(en in ss) {
        eg = entget(en);
        eg.SeekType(40)->Value *= sc;
        entmod(eg);
    }
    printf("Time = %d\n", GetCurrentTime() - t1);
}



3)ActiveX 方式, 耗时 828 ms (毫秒):

public function [C:C04]()
{
    ss = ssget( buildlist(0, "CIRCLE"));
    if (!ss) return;

    sc = getreal("\nScale<2.0> =");
    if (!sc) sc = 2.0;

    t1 = GetCurrentTime();
    for(en in ss) {
        pDisp = en.Dispatch();
        pDisp.Radius = pDisp.Radius * sc;
    }
    printf("Time = %d\n", GetCurrentTime() - t1);
}



4)C的动态编译方式, 耗时 2766 ms (毫秒):

public function [C:C05]()
{
    cc = new CCode();
    cc.__add_include_path( CCodeMainPath() + "ADS\\");
    cc.__add_ADS();

    cc.__compile( {%%
        #include <windows.h>
        #include "ads.h"
        #include "adscodes.h"

        void ScaleCircles()
        {
            struct resbuf  *filter=NULL, *eg, *p;
            ads_name  ss, en;
            double    sc;
            long  slen, i;
            int   t1, res;

            filter = ads_buildlist(RTDXF0, "CIRCLE", 0);
            res = ads_ssget(NULL, NULL, NULL, filter, ss);
            ads_relrb(filter);
            if (RTNORM != res) return;

            res = ads_getreal("\nScale<2.0> = ", &sc);
            if (RTNORM != res) sc = 2.0;

            t1 = GetCurrentTime();

            ads_sslength(ss, &slen);
            for (i=0; i<slen; i++) {
                ads_ssname(ss, i, en);
                eg = ads_entget(en);
                p = eg;
                while(p) {
                    if (p->restype == 40) {
                        p->resval.rreal *= sc;
                        break;
                    }
                    p = p->rbnext;
                }
                ads_entmod(eg);
                ads_relrb(eg);
            }
            ads_ssfree(ss);

            ads_printf("T1=%d\n", GetCurrentTime() - t1);
        }
    %%} );

    cc.__declare {
        void ScaleCircles();
    };

    cc.ScaleCircles();
}



5)AutoLISP程序,耗时4109 ms (毫秒):

(defun C:L01 (/ sc i en eg a1)
   (setq filter (list (cons 0 "CIRCLE")))
   (setq ss (ssget filter))
   (if ss (progn
      (setq sc (getreal "Scale <2.0> = "))
      (if (not sc) (setq sc 2.0))
      (cascript "t1 = GetCurrentTime(); ")
      (setq sslen (sslength ss) i 0)
      (while (< i sslen)
         (setq en (ssname ss i)
               i  (1+ i)
               eg (entget en)
               a1 (assoc 40 eg)
               eg (subst (cons 40 (* (cdr a1) sc)) a1 eg)
          )
          (entmod eg)
      )
      (cascript " printf(_'Time = %d (ms)\n', GetCurrentTime() - t1);")
   ))
   (princ)
)



6)C/C++ (ARX)编译程序,耗时 94 ms (毫秒):

void Arx_ScaleCircles()
{
    Acad::ErrorStatus es;
    struct resbuf  *filter=NULL;
    ads_name   ss, en;
    AcDbObjectId  objId;
    AcDbCircle  *pEnt;
    cat_real   sc;
    int   res, t1;
    long  slen, k;
 

    filter = ads_buildlist(RTDXF0, "CIRCLE", 0);
    res = ads_ssget(NULL, NULL, NULL, filter, ss);
    ads_relrb(filter);
    if (RTNORM != res) return; 

    res = ads_getreal("\nScale <2.0> = ", &sc);
    if (RTNORM != res) sc = 2.0;
 
    t1 = GetCurrentTime();
    ads_sslength(ss, &slen);

    for (k=0; k<slen; k++) {
        res = ads_ssname(ss, k, en);
        if (res == RTNORM) {
            es = acdbGetObjectId(objId, en);
            if (es == Acad::eOk) {
                es = acdbOpenAcDbEntity((AcDbEntity* &)pEnt, objId, AcDb::kForWrite);
                if (es == Acad::eOk) {
                    pEnt->setRadius( sc * pEnt->radius());
                    pEnt->close();
                }
            }
        }
    }

    ads_printf("Time = %d\n", GetCurrentTime() - t1);
}



7)C (ADS)编译程序,耗时 2687 ms (毫秒):

该程序与“4)C的动态编译方式” {%% %%}中的程序完全一样。



再测试,以生成5万条直线为例子,得到的结果与上面的情况基本一样:

------------------------------------------------------

程序语言 耗时(毫秒) 比较

------------------------------------------------------

Cx (ARX方式) 297 1

AutoLISP 1360 4.58

ARX C/C++ 编译型程序 109 0.37

ADS 编译型程序 1032 3.47

------------------------------------------------------

限于篇幅,这里仅仅给出Cx 的ARX方式的程序:

public function [C:C06]() //定义一个CAD命令 C06
{
    .AcGePoint3d  p1(0,0,0), p2(0, 100, 0);

    acdbCurDwg().getBlockTable(pTab, AcDb::kForRead);
    pRec = NULL;
    pTab.getAt("*Model_Space", pRec, AcDb::kForWrite);
    pTab = NULL;
 
    t1 = GetCurrentTime();
    repeat( x in 50000) {
        p2.x = x;
        pEnt = new AcDbLine(p1, p2);
        pRec.appendAcDbEntity(pEnt);  
    }
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
}



02. 非常方便与其他程序的沟通和调用DLL功能

Cx支持包括指针、回调函数在内的绝大部分C/C++数据类型,因此Cx调用普通DLL中的函数、数据非常简便,例如:

myDll = new DLL(“myDll.dll”);

myDll.__declare{
    double func1( double a, double b);
    void   func2( int x);
    int    varNum;
};

R = myDll.func1(1.0, 2.0);

myDll.func2(123);

++myDll.varNum;



被LISP函数(vl_acad_defun) 注册过的LISP函数,可以被Cx函数调用,比如有LISP函数:

(defun lispFunc(x y)
    (+ (* 10 x) y)
)
 
(vl_acad_defun ‘lispFunc)

这样就可以在Cx程序里直接调用(以操作符 “!”标识调用的是LISP函数):

Val = lispFunc!(2, 3);



以public 标识的Cx函数,会在LISP空间注册相应的函数,让LISP程序直接调用。因为Cx程序往往是定义在各自的命名空间,因此,LISP里注册Cx函数时,会在函数名前加空间名的前缀,比如:

//Cx 程序:

#package  pak_1

public function cxFunc(x, y)
{
    return 10 *x + y;
}

#package  _end_



;LISP 程序:

(defun test()
     (setq val (Pak_1^cxFunc  2 3))
)

但是,如果函数名是前缀C:,则不做追加前缀处理,而是与LISP语言一样,直接注册成CAD命令型函数。

Cx支持操作ActiveX接口,既可以通过该接口操作CAD,比如对上面对CIRCLE实体进行半径缩放的例子(C:04),也可以通过该接口,启动、连接诸如Excel.Application,或电脑系统中大量的存在的ActiveX构件,比如:

public function [C:C07]()
{
    for (x in GetObject( "WinMgmts:").InstancesOf( "Win32_DiskDrive")){
        MsgBox(x.Model, L"Information of Win32_DiskDrive");
    }
}

显然,通过ActiveX接口,让CAD与Excel等软件交换数据是很方便的。



下面给一个例子,展示如何用Cx的回调函数为参数调用 Win32 API 的函数,具体是枚举AutoCAD 里 acadbtn.dll中所有图标BITMAP/ICON资源的名字,并且屏幕显示前十个名字。在声明Win32 API函数时,回调函数的参数以 void * 类型声明。 Cx支持类的成员函数构建回调函数,这样的回调函数类似C#中的delegate,能够调用对象里封装的数据成员。与C/C++不同,Cx里取得函数myFunc的地址是通过&.myFunc()格式。

//////////////////////////////////////////////////////////////////////

#define RT_BITMAP    2
#define RT_ICON      3

//////////////////////////////////////////////////////////////////////
#package  myDemo

//////////////////////////////////////////////////////////////////////
public function [C:C08] ()
{
    Enum = new CEnumResource("acadbtn.dll");
    if (!Enum.Start()) return;

    prinn("\nBitmap Names List:\n");
    for (x in Enum.m_arrBitmap, 10) prinn(x);
 
    prinn("\nIcon Names List:\n");
    for (x in Enum.m_arrIcon, 10) prinn(x);
}

//////////////////////////////////////////////////////////////////////
public class  CEnumResource
{
    def CEnumResource( sourceFile);
    def Start();
    def EnumResTypeProc (HMODULE hInst, LPSTR pszType, LPARAM lParam);
    def EnumResNameProc (HMODULE hInst, LPSTR lpszType, LPSTR lpszName,
                         LPARAM lParam); 

    DLL           m_source;
    DLL           m_kernel32;
    CStringArray  m_arrBitmap;
    CStringArray  m_arrIcon;
    __Callback    m_TypeProc;
    __Callback    m_NameProc;  
};

//////////////////////////////////////////////////////////////////////
function  CEnumResource::CEnumResource ( sourceFile)
{
    m_source = new DLL(sourceFile);
    if (!m_source) return;

    m_kernel32 = new DLL("kernel32.dll");
    m_kernel32.__declare {
        BOOL EnumResourceTypesA (HMODULE hInst, void *TypeProc,
                                 LONG lParam);
        BOOL EnumResourceNamesA (HMODULE hInst, LPCTSTR pszType,
                                 void *NameProc, LONG lParam); 
    };

    // 构建回调函数
    m_TypeProc = new __Callback( &.EnumResTypeProc());
    m_NameProc = new __Callback( &.EnumResNameProc());
}
 
//////////////////////////////////////////////////////////////////////
function CEnumResource::Start()
{
    if (!m_kernel32) return FALSE;
    m_kernel32.EnumResourceTypesA( m_source.__GetInstance(),
        m_TypeProc, 0);
    return TRUE;
}
//////////////////////////////////////////////////////////////////////
function CEnumResource::EnumResTypeProc (HMODULE hInst, LPSTR pszType,
     LPARAM lParam)
{
    m_kernel32.EnumResourceNamesA( hInst, pszType, m_NameProc, lParam); 
    return TRUE;
}

//////////////////////////////////////////////////////////////////////
function CEnumResource::EnumResNameProc (HMODULE hInst, LPSTR lpszType,
    LPSTR lpszName, LPARAM lParam)
{
    switch ((int) lpszType) {
    case RT_BITMAP:
        m_arrBitmap.add(lpszName);
        break;
 
    case RT_ICON:
        m_arrIcon.add(lpszName);
        break;
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////

#package _end_
//////////////////////////////////////////////////////////////////////


输入命令C08启动上面定义的函数 C:C08,得到如下输出结果:

Bitmap Names List:

ICON_16_2DOPTIM

ICON_16_3DCLIP

ICON_16_3DCLIPBK

ICON_16_3DCLIPFR

ICON_16_3DCORBIT

ICON_16_3DDISTANCE

ICON_16_3DFACE

ICON_16_3DMESH

ICON_16_3DORBIT

ICON_16_3DPAN



Icon Names List:



上面这段程序中有几个函数、方法定义中,对参数的类型做了强制限制,这是回调函数的需求。





03. 支持反应器

下面展示的例子是建立AcEditorRector型反应器,当该反应器打开(ON)时,一旦完成 OFFSET,COPY,MIRROR,ARRAY或 –ARRAY命令后,该反应器会自动把刚刚生成的所有实体的层、颜色和线型的属性改成DWG当前值:

///////////////////////////////////////////////////////////////////
#package  myDemo
 
class  MyEdReactor;
 
///////////////////////////////////////////////////////////////////
public function [C:C09]()
{
    static  var myEd=NULL;
    initget("ON OFF");
    kw = getkword ("\nEnter an option [ON/OFF] <" +
                   (myEd ? "OFF" : "ON") + ">: ");
    if (!kw) {
        kw = myEd ? "OFF" : "ON";
    }

    if (kw == "ON") {
        if (myEd) return;
        myED = new MyEdReactor;
        prinn(" acting myEdReactor...");
    } else {
        myED = NULL;
        prinn(" myEdReactor delected.");
    }
}

///////////////////////////////////////////////////////////////////
class  MyEdReactor : AcEditorReactor
{
    var  m_en;

    def  commandWillStart (cmd) { m_en = entlast(); }
    def  commandEnded     (cmd);
};

///////////////////////////////////////////////////////////////////
function  MyEdReactor::commandEnded (cmd)
{
    ss = sslast(m_en);
    if (!ss) return;

    switch(cmd) {
    case "OFFSET":
    case "COPY":
    case "MIRROR":
    case "ARRAY":
    case "-ARRAY":
        ss.SetLayer( GetVar( "CLAYER"));
        ss.SetColor( GetVar( "CECOLOR"));
        ss.SetLtype( GetVar( "CELTYPE"));
        break;
    }
}

 
///////////////////////////////////////////////////////////////////
#package _end_

///////////////////////////////////////////////////////////////////


04. 高度动态

Cx程序可以非常动态,象前面说的C程序的动态编译,后面将要说明的程序覆盖/“热替换”,都是很好用的动态特性。此外,函数名、变量名、类型名等标识符(名称)可以为任何字符组成,甚至可以计算。一般以前置符$作为特殊标识开始,比如:

$[姓名] = “张三”;
$”年龄” = 23;
++${“AB # C & @” + 123};

在可以明确是标识符的地方,比如函数名定义处,前置符$可以省略。



Cx程序码可以动态构建,方式有多种:Lambda函数、普通函数体、字符串等,都可以动态插入运行。对于函数体、Lambda定义体的插入运行,与单独运行这些函数、Lambda定义体不同,比如定义体中的return语句,仅仅起到结束插入体程序运行的作用,不会对当前正在运行的函数产生影响。类似C语言中运行一个{。。。}局部结构的程序,不同的是Cx有带参数,这一点又象宏带入。一旦插入的程序段运行结束,期间产生的局部变量会马上释放。例如,有字符串:

S = “ x = a + b * c”;

可以动态执行:

S.eval();

等效执行字符串里表达的程序。只是这里的运行解释过程会比较消耗时间,如果对字符串里的表达式要求高效运行,可以先进行格式化处理:

F = Formula(S);

再运行:

F.eval();



F();

运行效率一般可以提高一个数量级。

Cx的函数定义,支持可变参数,比如,定义一个任意参数个数的加法函数:

function myAdd(A, …)
{
    no = ThisParamNo();
    for(k=1; k<no; k++) {
        A += *ThisParam(k);
    }
    return A;
}
其中内建函数ThisParamNo()取得当前函数myAdd的参数个数,内建函数ThisParam()则取得当前函数的参数地址/指针,这样,通过表达式:
B = myAdd(1,2,3,4);
变量B得到的值就是:10



05. 尽力静态

Cx支持泛型变量,也支持强制类型声明的变量。明确变量类型,有利于Cx程序在运行过程静态处理,比如对高强度循环过程,通过语句:trySpeed {…} 有可能显著提高程序运行速度:

public function [C:C10]()
{
    res = 0i64; 

    t1 = GetCurrentTime();
    for (k=0; k<10000000; k++) {
        res += k;
    }
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
}
public function [C:C11]()
{
    .int      k;
    .__int64  res=0;

    t1 = GetCurrentTime();
    trySpeed {
        for (k=0; k<10000000; k++) {
            res += k;
        }
    };
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
}

上面定义两个函数,其中C:C10采用泛型方式,而C:C11采用强制类型声明,并且引入程序结构trySpeed{…},运行结果显示,C:C10耗时2234 毫秒,C:C11耗时仅仅719毫秒。可以算出C:C11的运行速度是C:C10的3倍。

Cx语言会对一些语句进行静态优化,比如switch-case语句,因为进行静态优化处理过,其case 匹配过程非常快速,即使存在数以千计的case 语句,也可以在瞬间匹配到位。如果使用传统的if –else if … 语句方式,当case 比较多时,会明显影响运行效率。



06. 简洁的数据操作

Cx中的大部分数据结构,与C/C++中的数据结构是完全一致的,当与C/C++编译成型的Win32 API的DLL模块进行沟通时,数据不需要进行任何转换,比如,Cx语言生成的字符串数组:

mArray  = new TSTR[1024];

在内部数据结构分布上与C中的如下数组是完全一样的:

LPTSTR * cArray = new LPTSTR[1024];


因此,当有C的库函数需要这种类型的参数时,可以把mArray直接赋值参数,Cx语言内部不需要做任何数据转换,如:

myDll = new DLL(“abc.dll”);
myDll.__declare {
    void  xFunc(int a, LPTSTR[] b);
};
myDll.xFunc(123, mArray);

在Cx语言里对mArray数组元素的赋值,要比C里简洁、自在得多,不需要考虑内存泄漏问题,也不会发生内存泄漏,有关内存的释放,Cx语言会自动完成,程序员的编程仅仅是进行简单的赋值操作:

mArray[8] = “ABC”;
mArray[8] =  aNewStr;

不要担心mArray[8]处原来是否存在字符串,如果存在,也用不着编程去释放这个字符串,Cx 语言会自动、马上释放。

如果把某数组元素赋予变量v,实际上是让v指向该元素拥有的字符串:

v = mArray[8];

当mArray[8]拥有的字符串更新时,原来的字符串自然失效,变量v的值会自动变为NULL。

调用Win32 API函数时,往往会要求各种struct型参数,有相当部分在Cx内部有预定义,如果没有,可以用Cx程序直接书写,Cx支持位域、位对齐等,效果与C/C++中定义的完全相同。

有C/C++编程经验的人,应该可以从中看出Cx语言程序的简便方式对编程效率、品质的意义。

AutoLISP语言操作CAD,往往通过resbuf 数据链,因为LISP语言的传值特性,对数据链的任何修改,都会引发重构整条数据链,造成明显性能损耗。Cx支持指针,可以修改resbuf 数据链中任意数据结点而不必重构数据链,而且操作非常简单,比如,变量L1中有如下resbuf数据链:

(0 1 2 3 4 5 6)

如果要修改第三个数(从0位算起),比如乘8:

L1[3].value *= 8;

L1中的数据链变为:

(0 1 2 24 4 5 6)

也可以把第三个结点的数据直接赋其他类型数据的值:

L1[3] = “Hello”;

L1中的数据链变为:

(0 1 2 “Hello” 4 5 6)

还可以方便操作resbuf数据结点亚结构中的数据,比如,变量L2中有如下resbuf数据链:

(0 1 2 (11 “X” “Y”) (22 “U” “V”) 3.14)

用如下表达式:

(L2+3)[2].Value += “-ABC”;
就可以把L2改成:
(0 1 2 (11 “X” “Y-ABC”) (22 “U” “V”) 3.14)

Cx仅仅是通过数据指针修改相应亚结点的数据,其他结点的数据完全没有改变。操作很方便吧。

大部分脚本语言,都采用传统的所谓的垃圾回收机制调控资源,这个回收机制虽然已经改善很多,但是仍然存在一些问题,一个是垃圾回收过程的不可预测性;其次是编写程序过程必须很小心,否则可能会出现相互引用而造成内存永远无法释放;象数据链、数组之类的复合型数据,相关引用很多,会影响数据处理效率。Cx语言完全没有这些问题。



07. 支持DCL, MFC

Cx除了支持AutoCAD提供的窗口控制语言DCL外,更强力支持Microsoft Visual C/C++的MFC编程。用MFC设计窗口,会比DCL强大、漂亮得多。由于Cx脚本特性,建立独立小单元的测试非常方便,学习、测试MFC特性是一件相当让人振奋的事。

Cx内部连接大量MFC类(class),可以直接使用这些类,无缝地继承这些类,虚拟函数、操作符重载、仿函数、枚举值,等等,都可以继承过来。对于窗口类,Cx处理消息要比C/C++语言简洁得多,不需要使用映射消息的数据结构和相应的宏。对于不同的窗口消息,Cx会自动调用相应名称的方法处理消息。Cx本身的类定义,支持“简化型”的多重继承,一个类里可以同时定义具有不同参数个数的构造函数,析构函数功能是健全的,在对象失效时,会及时启动。下面先给个简单的例子:

Mx = new AcGeMatrix3d();
Mx(1,1) = 123;
++Mx(1,1);

这里的操作,隐式调用了 AcGeMatrix3d仿函数。



Cx中定义的类,如果继承自MFC的CObject之类的类,可以拥有MFC类CRuntimeClass,进行相当底层的操作,比如进行动态产生窗口,象下面的例子,将主窗口分割成3个子窗口,利用CRuntimeClass为每个子窗口动态产生Cview型窗口:





//////////////////////////////////////////////////////////////////////
#package  myDemo
 
//////////////////////////////////////////////////////////////////////
#include "windows.ch"

#define MY_PHONE  88
#define MY_EXIT   99
 
//////////////////////////////////////////////////////////////////////
public function [C:C12]()
{
    myC = new CCreateContext;
    MyF = new CmyForm;
 
    res = MyF.Create( NULL, "myDemo", WS_OVERLAPPEDWINDOW,
                      CFrameWnd::rectDefault, AfxGetMainWnd(),
                      NULL, 0, myC);
    if (!res) return;
 
    MyF.ShowWindow(SW_NORMAL);
    MyF.UpdateWindow();
    MyF.RunModalLoop();
}

 
//////////////////////////////////////////////////////////////////////
class CmyForm : CFrameWnd
{

    def  OnCreateClient (lpcs, pContext);
    def  OnSize         (type, cx, cy);
    def  OnClose        ();
    def  OnNcDestroy    () { EndModalLoop(1); }

    CSplitterWnd  m_sV, m_sH;
    RECT  m_rec;
    int   m_bCreateSplitter=FALSE;
};

 
//////////////////////////////////////////////////////////////////////
function CmyForm::OnCreateClient(pcs, pcc)
{
    pcc.m_pCurrentFrame = this;

    siz    = new SIZE;
    rView1 = RUNTIME_CLASS(CmyView_1);
    rView2 = RUNTIME_CLASS(CmyView_2);
    rView3 = RUNTIME_CLASS(CmyView_3);

    m_sV.CreateStatic(this, 1, 2);
    m_sH.CreateStatic(m_sV, 2, 1, WS_CHILD | WS_VISIBLE,
                      m_sV.IdFromRowCol(0, 1));
    siz.cx = 700;
    siz.cy = 600;
    if (!m_sV.CreateView(0, 0, rView1, siz, pcc)) return 0;

    siz.cx = 200;
    siz.cy = 200;
    if (!m_sH.CreateView(0, 0, rView2, siz, pcc)) return 0;
    if (!m_sH.CreateView(1, 0, rView3, siz, pcc)) return 0;

    m_sV.SetRowInfo   (0, 100, 10);
    m_sV.SetActivePane(0, 0, NULL);
    m_bCreateSplitter = TRUE; 

    SetWindowPos(0, 60, 40, 900, 600, 0);
    ShowWindow(SW_NORMAL);
    return 1;
}

//////////////////////////////////////////////////////////////////////
function CmyForm::OnSize(type, cx, cy)
{
    if (!m_bCreateSplitter) return;
 
    GetClientRect(m_rec);

    Width  = m_rec.Right - m_rec.Left;
    Height = m_rec.Bottom - m_rec.Top;

    m_sV.SetColumnInfo(0, Width * 6/9, 0);
    m_sV.SetColumnInfo(1, Width * 2/9, 0);
 
    m_sH.SetRowInfo(0, Height /2, 0);
    m_sH.SetRowInfo(1, Height /2, 0);

    m_sV.RecalcLayout();
    m_sH.RecalcLayout();
}

 
//////////////////////////////////////////////////////////////////////
function CmyForm::OnClose()
{
   if (MessageBox("Are you sure to quit?", "Quit", MB_YESNO) != IDYES)
       return 1;
}

//////////////////////////////////////////////////////////////////////
class  CmyView_1 : CView
{
    CPicture  m_pic;
    RECT      m_rec;

    def OnCreate(cs)  {
        m_pic.Load("highway.jpg");
    }

    def OnDraw (pDC) {
        GetClientRect(m_rec);
        m_pic.Render(pDC, NULL, m_rec);
    }
};

//////////////////////////////////////////////////////////////////////
class  CmyView_2 : CView
{
    CPicture  m_pic;
    RECT      m_rec;
 
    def OnCreate(cs) {
        m_pic.Load("warShip.jpg");
    }

    def  OnDraw (pDC) {
        GetClientRect(m_rec);
        m_pic.Render(pDC, m_rec);
    }
};

 
//////////////////////////////////////////////////////////////////////
class  CmyView_3 : CView
{
    def OnCreate   (cs);
    def OnCommand  (msg);
    def OnDraw     (pDC);

    CPicture       m_picPhone1;
    CPicture       m_picPhone2;
    CPicture       m_picExtit;

    CBitmapButton  m_btnPhone;
    CBitmapButton  m_btnExit;
    HWND           m_hFrame;
};

 
//////////////////////////////////////////////////////////////////////
function CmyView_3::OnCreate( cs)
{
    rec = new RECT;

    m_hFrame = ((CCreateContext) cs.lpCreateParams).m_pCurrentFrame->m_hWnd;

    m_picPhone1.Load("phone_up.jpg");
    m_picPhone2.Load("phone_down.jpg");
    m_picExit. Load("exit.jpg");

    sty = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW;
    rec.left   = 30;
    rec.right  = rec.left+20;
    rec.top    = 50;
    rec.bottom = rec.top +20;

    m_btnPhone.AttachBitmaps(
        (HBITMAP) m_picPhone1.GetHandle(),
        (HBITMAP) m_picPhone2.GetHandle()
    );
    m_btnPhone.Create("", sty, rec, this, MY_PHONE);
    m_btnPhone.SizeToContent();

    sty = WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_OWNERDRAW;
    rec.left   = 150;
    rec.right  = rec.left+20;
    rec.top    = 50;
    rec.bottom = rec.top +20;

    m_btnExit.AttachBitmaps(
        (HBITMAP) m_picExit.GetHandle()
    );

    m_btnExit.Create("", sty, rec, this, MY_EXIT);
    m_btnExit.SizeToContent();
}

//////////////////////////////////////////////////////////////////////
function CmyView_3::OnCommand(msg)
{
    switch(msg.wParam) {
    case MY_PHONE:
        CreateDispatch("SAPI.SpVoice").Speak("Hello");
        return 1;
 
    case MY_EXIT:
        ::SendMessage(m_hFrame, WM_CLOSE, 0, 0);
        return 1;
    }
}

//////////////////////////////////////////////////////////////////////
function CmyView_3::OnDraw(pDC)
{
    s1 = "Hello";
    s2 = "Exit";
    pDC.TextOut(50,  120, s1, s1.len);
    pDC.TextOut(170, 120, s2, s2.len);
}

//////////////////////////////////////////////////////////////////////
#package _end_

//////////////////////////////////////////////////////////////////////



08. 支持ForEach, map

作为脚本语言既酷、又富有效率的程序结构:ForEach 和 map,Cx强力支持。对于ForEach结构,Cx的表达方式有:

1) for (x in express [, len]) {…}

2) for (p to express [, len]) {…}

3) repeat(x in num [,Zero [, Step]]) {…}



其中express, 可以是各种形式的数组结构、数据链、字符串、和CAD选择集,等等。这类表达式已经不断出现在上面例子程序中。它们不仅表达简洁,而且可以让Cx程序的运行更富有效率,For(x in experss) 中的 x, 不仅对应各个数据结点的值,很多时候是这些数据的参照(Reference),让这些结点数据直接进行存取,比如,有数组:

A = new int[1000];
for (x in A) x = $i;

这里的 $i为Cx的系统变量,自动记录了该 for 循环的序号。因此,现在A中各元素的值分别是:

0, 1, 2, 3, 4, …, 998,999。



for (p to express) {…}结构,用于操作复杂的数据结点,比如ADS的resbuf数据链,p是指向各个数据结点的高级指针。通过该指针,可以方便操作所指数据结点的各种值。比如,建立如下resbuf的LIST数据链:

L = new LIST(<1,"Sa">, <2, "Sb">, <3, "Sc">);

得到LIST数据链:

((0 “Sa”) (1 “Sb”) (2 “Sc”))

进行 to 的 forEach操作:

for (p to L) {
    p[1].value += "-x" + $i;  //$i为Cx的系统变量,自动记录了 for 循环的序号
}
LIST数据链L的内容变为:
((0 "Sa-x0") (1 "Sb-x1") (2 "Sc-x2"))



对于Map的结构,Cx的表达方式有:

1) map_in(L1, L2, …) {

functionPtr;

}

2) map_to(L1, L2, …) {

functionPtr;

}



其中L1, L2, …可以为各种形式的数组结构、数据链、字符串、和CAD选择集等, FunctionPtr为Lambda、函数指针、或闭包(下篇博文有专门论述)。比如有3个数组:

#define  MyLEN 1000
A = new double[MyLEN];
B = new double[MyLEN];
C = new double[MyLEN];

。。。初始化A和B各元素的值后,

map_in(A, B, C) {
    Lambda(x, y, z) {
        z = x + y;
    }
}



完全等效C/C++中的表达式:

for (int i=0; i < MyLEN; i++) {
    Z[i] = A[i] + B[i];
}



对于map_to,与 for(p to…) 的情况一样,用于操作复杂的数据结点。





09. 良好容错、除错能力

作为脚本语言,良好的容错性是很重要的,不像编译型程序语言的程序错误可以在编译过程发现和更正,脚本语言的很多错误只能在运行过程发现。因此,脚本语言应该有一定的纠错、“容错”能力。比如在前面讲到的程序在异常情况下中断,必须能够自动对要求成对操作进行善后处理。

Cx中的对象变量或对象结点,一般都具有双重性:指针和参照,因此,Cx对方法的调用,一般即可以用点操作符 “.”,也可以用指针操作 “->”。

Cx的标识符不区分大小写。函数名称、变量名称、类型名称等可以同名,因此不用担心变量名称的随意使用而可能导致同名函数、类型的失效。



采用下面程序结构,可以处理程序中可能的出错:

1)try {};

{}中出错时,跳出{},继续执行下去。

2)try {1} except {2};

{1}中出错时,跳到{2}中运行,如果没有出错,则跳过{2}。无论如何,程序都会在 {2}后继续运行下去。

3)try {1} finally{2};

无论{1}中是否出错,都会进入{2}中运行,然后程序都会在{2}后继续运行下去。




010. 支持(自)覆盖 / 热替换


Cx的函数定义,类定义,结构定义等,都可以进行(自)覆盖/ 热替换,程序运行过程可以随时随地装载新的程序,无论新的程序中的各种定义是否与旧的定义重叠,都没有关系,新、旧定义可以并存,旧的定义体仍然可以正常运作下去,新的定义也可以开始新的操作。即使在多线程复杂情况下也可以进行正常装载!

AutoLISP支持函数自覆盖特性,这是非常有用的特性,利用该特性,数以千计的功能、函数,可以在真正需要的时候才自动、随时随地装载,而不是在宿主程序启动时一次性装载而严重影响启动时间、也造成内存浪费。

用脚本语言进行大型软件的二次开发,说简单很简单,可能仅仅一行代码就可以完成一项任务,它的简洁、它的灵活性,是编译型语言不能匹敌的!但是二次开发、尤其脚本语言进行二次开发,说困难也困难,因为运行环境很恶劣,很难预料、不好控制,操作者的操作可能很随机,甚至是恶意的,如何去适应?二次程序开发者可能不止一个,素不相识的开发者的程序之间如何协调、共处而不冲突?二次开发的程序可能非常多,而可能调用的功能很随机,在不能把所有功能程序装载、甚至绝大部分功能程序都没有装载的情况下,操作者如何随心所欲、让宿主平台自动、简易调用需要的功能?程序重载、覆盖,是否会影响正在运行的程序?
等等,所有这些,让人眼花缭乱。

Cx语言的产生,最初的宗旨就是为了解决上面这些问题。很庆幸、也很意外的是,上面的这些问题,现在在Cx语言里已经不成问题了。



011. 支持正则表达式

例如下面是一个定义有效日期格式的正则表达式(考虑了润年影响,摘自网络文章):

exp = @"^(?:([0-9]{4}-(?:(?:0?[1,3-9]|1[0-2])-(?:29|30)|"
      @"((?:0?[13578]|1[02])-31)))|([0-9]{4}-(?:0?[1-9]|"
      @"1[0-2])-(?:0?[1-9]|1\d|2[0-8]))|"
      @"(((?:(\d\d(?:0[48]|[2468][048]|"
      @"[13579][26]))|(?:0[48]00|[2468][048]00|"
      @"[13579][26]00))-0?2-29)))$";
rx = new regex(exp);
rx.match("2000-2-29"); // 返回 1  (有效日期)
rx.match("2001-2-29"); // 返回 0  (无效日期)

正则表达式是一种很特殊的语言,看起来怪异,不容易书写和上手,但是很有用,可以在网上找到不少实例。



012. 支持多线程编程、并行计算

作为脚本语言,Cx没有象Python那样的“全局锁”(GIL),所有运行于各线程的Cx程序,都是真正的系统级线程程序,程序语句独立、安全。一般脚本语言的内部实现,需要牵涉太多公共数据读和写,在多线程状态下要安全实现数据共享,往往需要付出太多性能代价,Cx非常成功解决了这个问题,代价很小,很安全,甚至在极端情况下,比如10个线程同时在同步锁协调下为某个整数增值操作,同步协调造成的运行速度损耗不会超过3%!如果采用传统同步锁方法进行同步操作,运行速度损耗可能高达85%!

下面给个比较测试的例子。Cx提供一种同步锁:单写多读同步锁wrLock,允许同时很多读操作,而写操作具有排他性,一次只允许一个写操作。比较用的同步锁程序来自 Jeffrey Richter编写的类CSWMEG,也是单写、多读同步锁。为了便于比较,该类已经被Cx关联,可以直接在Cx里调用。这里先给出结果,再列出相应的程序。程序的结构以Cx支持的并行计算方式书写,对数据成员m_res共做 1千万次增1操作。其中类CmyPa_1采用 wrLock同步锁,而类CmyPa_2采用 CSWMRG同步锁。函数C:13和C:14都调用类CmyPa_1,分别使用单线程和10个多线程操作,函数C:15和C:16都调用类CmyPa_2,方别使用单线程和10个多线程操作,结果如下(消耗时间:ms
毫秒):



同步锁 单线程 多线程 比较(单线程/多线程)

-----------------------------------------------------------------------

wrLock (Cx) 5,344 5,484 97.45%

CSWMRG (By Jeffrey Richter) 5,750 39,015 14.74%

-----------------------------------------------------------------------



从中可以看出极大的差异!显示Cx语言提供的同步锁无比优越的性能!需要指出的是,两种同步锁下的程序运行过程,CPU占用率都不太高,在20~30%之间徘徊。



//////////////////////////////////////////////////////////////////////
#package myDemo

//////////////////////////////////////////////////////////////////////
public function [C:C13]() // 用wrLock, 单线程
{
    t1 = GetCurrentTime();
    P1 = new CmyPa_1(1);
    P1.start();
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
    prinn("result = " + P1.m_res);
}

//////////////////////////////////////////////////////////////////////
public function [C:C14]() //用WrLock, 10个线程
{
    t1 = GetCurrentTime();
    P1 = new CmyPa_1(10);
    P1.start();
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
    prinn("result = " + P1.m_res);
}
 

//////////////////////////////////////////////////////////////////////
public function [C:C15]() //用CSWMRG, 单线程
{
    t1 = GetCurrentTime();
    P2 = new CmyPa_2(1);
    P2.start();
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
    prinn("result = " + P2.m_res);
}
 

//////////////////////////////////////////////////////////////////////

public function [C:C16]() //用CSWMRG, 多线程
{
    t1 = GetCurrentTime();
    P2 = new CmyPa_2(10);
    P2.start();
    printf("Time = %d (ms)\n", GetCurrentTime() - t1);
    prinn("result = " + P2.m_res);
}

//////////////////////////////////////////////////////////////////////
class CmyPa_1 : CParallel  //用WrLock
{
    CmyPa_1( nThread) {
        CParallel::initial( nThread);
        m_nThread = nThread;
        m_lock = new wrLock();
        m_res  = 0;
    }
 
    ~CmyPa_1(){}
 
    def InvokeProcess() {
        my_lock = m_lock.__image();

        repeat(10000000 / m_nThread) {
            my_lock.EnterWrite();
            m_res++;
            my_lock.LeaveWrite();
        }
    }

    wrLock   m_lock;
    __int64  m_res;
    int      m_nThread;
};

 

//////////////////////////////////////////////////////////////////////
class CmyPa_2 : CParallel //用CSWMRG
{
    CmyPa_2( nThread) {
        CParallel::initial( nThread);
        m_nThread = nThread;
        m_lock = new CSWMRG();
        m_res  = 0;
    }

    ~CmyPa_2() {}

    def InvokeProcess() {
        my_lock = m_lock.__image();

        repeat(10000000 / m_nThread) {
            my_lock.WaitToWrite();
            m_res++;
            my_lock.Done();
        }
    }
 
    CSWMRG   m_lock;
    __int64  m_res;
    int      m_nThread;
};

 
//////////////////////////////////////////////////////////////////////
#package _end_

//////////////////////////////////////////////////////////////////////


多核PC已经普及几年了,能够利用多核优势进行编程的程序语言不多,脚本语言就更少了。为了有效使用多核,要求程序能够支持真正的多线程(Multihread)!

有一些脚本语言声称支持多线程编程,但是它们即使能够运行于多核,却达不到提高程序运算效率的目的,为什么?象现在很流行的脚本程序语言Python和Ruby,其多线程的运作是通过全局锁(GIL)调控,在GIL控制下,多线程的运行是畸形的,在同个时间段,只允许一个线程取得GIL,处于运行状态,其他线程是处于休眠状态!可想而知,在GIL控制下,这些多线程合计的运算效率,与一个普通单线程情况下的运算效率不会有多大差别!因此,人们一直期望能够移除这个GIL。Python到版本3.1、Ruby到版本1.9都没有能够移除掉GIL,倒是基于微软。NET平台的
IronPython和IronRuby没有采用GIL,这是一大进步。

脚本语言的实现,一般内部存在大量需要共享的数据,虽然在多线程环境里数据共享方法很早就有,也健全,但是就是存在明显的效率问题,线程一多,问题更大,可能会象死机一样,这是个令人非常头痛的问题。有人抱怨CPU提供的“同步”机制太弱,Intel等硬件厂商把无法在硬件上再提升速度的问题,转嫁给软件业。不过,这也反映出,软件的发展跟不上硬件的发展。

Cx程序语言的成功,在一定程度上是受益于多线程同步技术的探索取得突破性进展!为了适应多线程需要,Cx语言的架构就是基于多线程。它支持系统级多线程,能同时运行于CPU多核,不仅支持MFC的工作者(Worker)线程,而且支持用户界面(UI)线程。

上面的例子是围绕同步锁激烈竞争情况,下面给个没有同步竞争的多线程平行计算性能比较测试结果,与Python、IronPython 对比,测试过程是求从 1到5千万的和。分别测试在单线程和有10个线程并行计算的结果如下(消耗时间单位: ms 毫秒):

单线程 10个线程 (单线程/10线程)的比值

------------------------------------------------------------------

Cx 1875 547 3.43

Python v3.1 4747 4470 1.06

IronPython v2.0 6735 4030 1.67

------------------------------------------------------------------

限于篇幅,省略源程序。可以看出,Python 在多线程下并行计算得到的效率提升很少,几乎可以忽略不计。在多线程下IronPython的并行运算效率有些提升,而效率提升很多的是Cx语言!Cx语言的运算效率与核数量几乎成线性关系,核利用率高达85.75% (3.43/4)。从IronPython的结果看,感觉其内部运行机制中还是存在类似GIL的东西,只是效果比真正存在GIL时改进一些。

AutoCAD的操作不支持多线程,但是在AutoCAD里仍然可以正常运行多线程,只要这些多线程不牵涉操作CAD。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: