我们知道,在MicroStation CE下目前支持三种编程语言(MVBA、C# 和C++)做二次开发。那么这三种语言生成的程序最终运行效率如何呢?本文针对该问题做了一个实际测试,供大家在选择编程语言时做参考。
硬件环境:HP ZBOOK-15 ( i7-8850H CPU、32G内存、1.5T 固态硬盘)
软件环境:Windows 10 企业版 、MicroStation CONNECT Edition Update 16.2
我们用了两个不同的程序来测试运行时间的长短。一个是在默认图层上连续创建2000个圆柱体;另一个是在每个单独的图层上创建2000个圆柱体。
先看MVBA源代码:
Sub CreateCones() startTime = Timer Dim myCone As ConeElement Dim basePt As Point3d, topPt As Point3d For i = 1 To 2000 basePt = Point3dFromXYZ(i, 0, 0) topPt = Point3dFromXYZ(i, 0, 1) Set myCone = CreateConeElement2(Nothing, 1, basePt, topPt) ActiveModelReference.AddElement myCone Next Debug.Print Format(Timer - startTime, "0.000s") End Sub Sub CreateConesWith2000Lvls() startTime = Timer Dim myLvl As Level Dim myCone As ConeElement Dim basePt As Point3d, topPt As Point3d For i = 1 To 2000 Set myLvl = ActiveDesignFile.AddNewLevel("LevelTest" & i) basePt = Point3dFromXYZ(i, 0, 0) topPt = Point3dFromXYZ(i, 0, 1) Set myCone = CreateConeElement2(Nothing, 1, basePt, topPt) myCone.Level = myLvl ActiveModelReference.AddElement myCone Next Debug.Print Format(Timer - startTime, "0.000s") End Sub
CreateCones完成仅用时0.053秒,可以说是瞬间完成。但出人预料的是,CreateConesWith2000Lvls却耗时98.992秒,差不多是CreateCones的1868倍,大大出乎我们的预料。推测有可能是VBA代码在后台频繁写层表导致的。
再来看C/C++代码,我们知道,在MSV8i时代,我们大都用MDL C函数来创建元素,这种方式在MSCE下仍然适用但不推荐。在MSCE下我们推荐使用新的MDL C++类的方法编程。故以下源代码分了四种情况在计时。情况1是用MDL C++在默认图层创建2000个圆柱体;情况2是用MDL C++在单独的2000个图层上每层创建一个圆柱体;情况3是用MDL C在默认图层创建2000个圆柱体;情况4是用MDL C在单独的2000个图层上每层创建一个圆柱体。它们消耗的时间分别为:0.014秒、0.123秒、0.020秒、0.144秒。总体看,用C++和用C编程时间相差很小,令人惊奇的是,C++代码居然比C还要快,这个有点超出我们的想象。深入分析源代码发现,Bentley最底层是都用C++写了,为了能使以前的C代码继续运行,反而在C函数中调用了C++封装的类。这一点解释了为何MS SDK中的C函数反而比C++慢的原因。但CreateConesWith2000Lvls的运行时间仅仅是CreateCones的7~8倍,远远低于VBA的1868倍。
void timeTest(WCharCP unparsed) { long myCase = wcstol(unparsed, NULL, 10); double startTime = BeTimeUtilities::QuerySecondsCounter(); switch (myCase) { case 1: // --- MDL(C++) CreateCones { DPoint3d basePt, topPt; for (int i = 0; i < 2000; i++) { basePt = DPoint3d::From(i * 1000, 0, 0); topPt = basePt; topPt.z = 1000; DgnConeDetail dgnCone(basePt, topPt, 1000, 1000, true); ISolidPrimitivePtr pSolid = ISolidPrimitive::CreateDgnCone(dgnCone); EditElementHandle eeh; DraftingElementSchema::ToElement(eeh, *pSolid, nullptr, *ACTIVEMODEL); eeh.AddToModel(); } } break; case 2: // --- MDL(C++) CreateCones with 2000 levels { DPoint3d basePt, topPt; FileLevelCacheP pLvlCache = ACTIVEMODEL->GetFileLevelCacheP(); ElementPropertiesSetterPtr propSetter = ElementPropertiesSetter::Create(); for (int i = 0; i < 2000; i++) { basePt = DPoint3d::From(i * 1000, 0, 0); topPt = basePt; topPt.z = 1000; DgnConeDetail dgnCone(basePt, topPt, 1000, 1000, true); ISolidPrimitivePtr pSolid = ISolidPrimitive::CreateDgnCone(dgnCone); EditElementHandle eeh; DraftingElementSchema::ToElement(eeh, *pSolid, nullptr, *ACTIVEMODEL); WPrintfString lvlName(L"Level %d", i + 1); EditLevelHandle lvlHandle = pLvlCache->CreateLevel(lvlName.GetWCharCP(), LEVEL_NULL_CODE, LEVEL_NULL_ID); propSetter->SetLevel(lvlHandle->GetLevelId()); propSetter->Apply(eeh); eeh.AddToModel(); } } break; case 3: // --- MDL(C) CreateCones { DPoint3d basePt, topPt; basePt.y = basePt.z = topPt.y = 0; topPt.z = 1000; for (int i = 0; i < 2000; i++) { basePt.x = topPt.x = i * 1000; mdlCone_create(dgnBuf, NULL, 1000, 1000, &basePt, &topPt, NULL); mdlElement_add(dgnBuf); } } break; default: // --- MDL(C) CreateCones with 2000 levels { DPoint3d basePt, topPt; basePt.y = basePt.z = topPt.y = 0; topPt.z = 1000; LevelId lvlId; for (int i = 0; i < 2000; i++) { basePt.x = topPt.x = i * 1000; mdlCone_create(dgnBuf, NULL, 1000, 1000, &basePt, &topPt, NULL); WPrintfString lvlName(L"Level %d", i + 1); mdlLevel_create(&lvlId, ACTIVEMODEL, lvlName.GetWCharCP(), LEVEL_NULL_CODE); mdlElement_setProperties(dgnBuf, &lvlId, NULL, NULL, NULL, NULL, NULL, NULL, NULL); mdlElement_add(dgnBuf); } } break; } double endTime = BeTimeUtilities::QuerySecondsCounter(); WPrintfString wStr(L"Case %d time = %.3f seconds", myCase, endTime - startTime); mdlDialog_dmsgsPrint(wStr); }
最后来测试一下C# 程序的执行效率。C# 编程有两套完全不同的对象模型:一套是基于VBA的,我们叫做Addin Interop(互操作),它的编程思路和VBA完全类似,仅仅是换了一种语言的写法而已;另外一套是MSCE中才能用的.NET编程,它是对MDL C++类的封装,所以,编程思路和MDL C++类似。针对两种对象模型和CreateCones、CreateConesWith2000Lvls两种情况也是四种情况。最终的测试结果为:情况1(Addin Interop,CreateCones)耗时0.070秒;情况2(Addin Interop,CreateConesWith2000Lvls)耗时102.413秒;情况3(Addin .NET,CreateCones)耗时0.021秒;情况4(Addin .NET,CreateConesWith2000Lvls)耗时0.136秒。
public static void TimeTestForCreate(string unparsed) { DateTime beforDT = DateTime.Now; switch (unparsed) { case "1": // ---- Interop Create Cone in default level { BIM.Application app = BMI.Utilities.ComApp; BIM.Point3d basePt, topPt; BIM.ConeElement myCone; for(int i = 0; i< 2000; i++) { basePt = app.Point3dFromXYZ(i, 0, 0); topPt = app.Point3dFromXYZ(i, 0, 1); myCone = app.CreateConeElement2(null, 1, basePt, topPt); app.ActiveModelReference.AddElement(myCone); } } break; case "2": // ---- Interop Create Cone in 2000 levels { BIM.Application app = BMI.Utilities.ComApp; BIM.Point3d basePt, topPt; BIM.ConeElement myCone; BIM.Level myLvl; for (int i = 0; i < 2000; i++) { basePt = app.Point3dFromXYZ(i, 0, 0); topPt = app.Point3dFromXYZ(i, 0, 1); myCone = app.CreateConeElement2(null, 1, basePt, topPt); myLvl = app.ActiveDesignFile.AddNewLevel("LevelTest" + i); myCone.let_Level(myLvl); app.ActiveModelReference.AddElement(myCone); } } break; case "3": // ---- .NET Create Cone in default level { DgnModel dgnModel = Session.Instance.GetActiveDgnModel(); DPoint3d basePt, topPt; DVector3d vec0 = new DVector3d(1, 0, 0), vec90 = new DVector3d(0, 1, 0); for (int i = 0; i < 2000; i++) { basePt = new DPoint3d(i * 1000, 0, 0); topPt = basePt; topPt.Z = 1000; DgnConeDetail dgnCone = new DgnConeDetail(basePt, topPt, vec0, vec90, 1000, 1000, true); SolidPrimitive solid = SolidPrimitive.CreateDgnCone(dgnCone); Element elem = DraftingElementSchema.ToElement(dgnModel, solid, null); elem.AddToModel(); } } break; default: // ---- .NET Create Cone in 2000 levels { DgnModel dgnModel = Session.Instance.GetActiveDgnModel(); DPoint3d basePt, topPt; DVector3d vec0 = new DVector3d(1, 0, 0), vec90 = new DVector3d(0, 1, 0); FileLevelCache lvlCache = dgnModel.GetFileLevelCache(); ElementPropertiesSetter setter = new ElementPropertiesSetter(); for (int i = 0; i < 2000; i++) { basePt = new DPoint3d(i * 1000, 0, 0); topPt = basePt; topPt.Z = 1000; DgnConeDetail dgnCone = new DgnConeDetail(basePt, topPt, vec0, vec90, 1000, 1000, true); SolidPrimitive solid = SolidPrimitive.CreateDgnCone(dgnCone); Element elem = DraftingElementSchema.ToElement(dgnModel, solid, null); EditLevelHandle lvlHandle = lvlCache.CreateLevel("Level" + i); setter.SetLevel(lvlHandle.LevelId); setter.Apply(elem); elem.AddToModel(); } } break; } DateTime afterDT = DateTime.Now; TimeSpan ts = afterDT.Subtract(beforDT); CommonClass.ShowMessage("Case " + unparsed + ": "+ ts.TotalSeconds + "second"); } }
测试结果:
编程方法 | MVBA | Addin-Interop | Addin-.NET | MDL-C | MDL-C++ |
CreateCone耗时(秒) | 0.053 | 0.070 | 0.021 | 0.020 | 0.014 |
CreateConeWith2000Lvls耗时(秒) | 98.992 | 102.413 | 0.136 | 0.144 | 0.123 |
倍数 | 1867.8 | 1463.0 | 6.5 | 7.2 | 8.8 |
MSV8i中是否支持 | √ | √ | ✘ | √ | ✘ |
MSCE中是否支持 | √ | √ | √ | √ | √ |
可以看出,在以上五种编程方法中,用C# 调用VBA互操作这种是最慢的,而采用C++面向对象编程是最快的。不过,Addin .NET和MDL C++这两种是我们强烈推荐的。
其实单从CreateCones函数的耗时来看,最慢的Addin-Interop方法(即通过C# 调用VBA这套COM接口)和最快的MDL-C++相比也仅仅相差5倍,而且都是在百分秒级的,基本上可以忽略不计。但CreateConeWith2000Lvls的耗时就相差太大了,经过深入分析底层代码,发现在MVBA的AddNewLevel封装中除了调用了C的mdlLevel_create函数外,它还做了大量同步和刷新的一些操作,正是这些额外操作拖慢了MVBA的执行速度,这个不是语言的问题,而是函数封装的问题。遇到此类MVBA问题,要想提高程序运行效率只能是改换后三种编程方式或者自己用COM封装一个mdlLevel_create来供VBA使用了。