新一代指令碼語言引擎Cx -- 應用之AutoCAD二次開發 (1)
新一代指令碼語言引擎Cx -- 應用之AutoCAD二次開發 (1)
“十年磨一劍”,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#和JAVA,但是C/C++中有很多很值得人們珍惜和學習的東西!這些年來,AutoCAD每年升級一次,原有的ARX應用程式,就面臨升級/版本相容問題,這既會嚴重影響人們對CAD升級的慾望,也影響人們對ARX應用程式慾望。
Cx以ARX方式結合到AutoCAD中。Cx程式的書寫格式、運作方式、資料結構等都非常類似C/C++程式,支援包括指標、回撥函式、位域、MFC、Win32 API等在內的C/c++大部分資料結構和功能,也可以進行記憶體直接處理的非常底層的操作。
與C/C++不同,Cx的記憶體處理基本上是自動的。與.NET的記憶體託管、JAVA的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。