Plugin languages comparison

jeroen.van.halderen
Advocate
Advocate

Plugin languages comparison

jeroen.van.halderen
Advocate
Advocate

Dear fellow developers,

 

While I was learning some programming techniques I heard somewhere that C++ is the fastest language out there, so I decided to do a test to see which of the three languages you can use to write a Revit plugin is the fastest. During my test it occurred to me that, because the Revit API is managed, the C++ implementation you can use to write a Revit plugin is considerably slower than native C++.

My test was to create 1000 modelcurves in Revit, the result are as follows:

With visual C++ it took 4 seconds, 733 milliseconds and 9619 ticks,

With C# it took 7 seconds,  989 milliseconds and 4320 ticks,

With VB.NET it took 5 seconds, 16 milliseconds and 3316 ticks.

 

The code for each of the three plugins is as follows:

Visual C++:

-Header:

#pragma once

using namespace System;

namespace HelloRevitCpp {
	[Autodesk::Revit::Attributes::TransactionAttribute(
		Autodesk::Revit::Attributes::TransactionMode::Manual)]

	[Autodesk::Revit::Attributes::RegenerationAttribute(
		Autodesk::Revit::Attributes::RegenerationOption::Manual)]
	public ref class Command
		: Autodesk::Revit::UI::IExternalCommand
	{
	public:
		virtual Autodesk::Revit::UI::Result Execute(
			Autodesk::Revit::UI::ExternalCommandData^ commandData,
			System::String^% message,
			Autodesk::Revit::DB::ElementSet^ elements);
	};
}

 -Main code:

#include "HelloRevitCpp.h"


using namespace Autodesk::Revit::DB;
using namespace Autodesk::Revit::UI;
using namespace HelloRevitCpp;

Result Command::Execute(ExternalCommandData^ commandData, String^% message, ElementSet^ elements) {
	Diagnostics::Stopwatch^ watch = gcnew Diagnostics::Stopwatch();
	watch->Start();
	Document^ doc = commandData->Application->ActiveUIDocument->Document;
	Plane^ plane = Plane::CreateByNormalAndOrigin(gcnew XYZ(0, 0, 1), gcnew XYZ());
	Transaction trans(doc);
	trans.Start("Test");
	SketchPlane^ sketchPlane = SketchPlane::Create(doc, plane);
	for (int i{ 0 }; i < 1000; ++i) {
		doc->Create->NewModelCurve(Line::CreateBound(gcnew XYZ(), gcnew XYZ(i + 1, 0, 0)), sketchPlane);
	}
	trans.Commit();
	watch->Stop();
	TaskDialog testDialog("Test");
	testDialog.Show("Test", "It took " + watch->Elapsed.Seconds + " seconds, " + watch->Elapsed.Milliseconds + " milliseconds and " + watch->Elapsed.Ticks + " ticks to create 1000 modelcurve");
	return Result::Succeeded;
}

 

C#:

using System.Diagnostics;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace HelloRevitCS
{
    [Transaction(TransactionMode.Manual)]
    public class Command : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            Stopwatch watch = new Stopwatch();
            watch.Start();
            Document doc = commandData.Application.ActiveUIDocument.Document;
            Plane plane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, new XYZ());
            Transaction trans = new Transaction(doc);
            trans.Start("Test");
            SketchPlane sketchPlane = SketchPlane.Create(doc, plane);
            for (int i = 0; i < 1000; ++i)
            {
                doc.Create.NewModelCurve(Line.CreateBound(new XYZ(), new XYZ(i + 1, 0, 0)), sketchPlane);
            }
            trans.Commit();
            watch.Stop();
            TaskDialog.Show("Test", "It took " + watch.Elapsed.Seconds + " seconds, " + watch.Elapsed.Milliseconds + " milliseconds and " + watch.Elapsed.Ticks + " ticks to create 1000 modelcurves");
            return Result.Succeeded;
        }
    }
}

 

VB.NET:

Imports Autodesk.Revit.Attributes
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.DB

<Transaction(TransactionMode.Manual)>
Public Class Command
    Implements IExternalCommand
    Public Function Execute(commandData As ExternalCommandData, ByRef message As String, elements As ElementSet) As Result Implements IExternalCommand.Execute
        Dim watch As Stopwatch = New Stopwatch
        watch.Start()
        Dim doc As Document = commandData.Application.ActiveUIDocument.Document
        Dim plane As Plane = plane.CreateByNormalAndOrigin(XYZ.BasisZ, New XYZ())

        Dim trans As Transaction = New Transaction(doc)
        trans.Start("Test")
        Dim sketchPlane As SketchPlane = SketchPlane.Create(doc, plane)
        For i = 1 To 1000
            doc.Create.NewModelCurve(Line.CreateBound(New XYZ(), New XYZ() + XYZ.BasisY.Multiply(i)), sketchPlane)
        Next
        trans.Commit()
        watch.Stop()
        TaskDialog.Show("Runtimetest", "It took " & watch.Elapsed.Seconds & " seconds, " & watch.Elapsed.Milliseconds & " milliseconds and " & watch.Elapsed.Ticks & " ticks to create 1000 modelcurves")
        Return Result.Succeeded
    End Function
End Class

 

Reply
999 Views
9 Replies
Replies (9)

sahin.ikbal
Advocate
Advocate

Interesting comparison 🙂

0 Likes

RPTHOMAS108
Mentor
Mentor

I always knew VB.Net was 2 seconds quicker than C#!

 

I've never been convinced that such comparisons are truly accurate i.e. was the machine restarted between tests and in the exact same state.  There is an amount or graphics caching that goes on I've sometimes noticed the same test conducted has differing results, best to take an average of multiple tests. Especially considering the state of other OS processes and services are constantly changing.

 

Note that your VB.Net code is not exactly the same as the other two. Your line is heading in the Y direction and you are using XYZ.Multiply rather than the constructor.

 

Code Revit API in C++?...Never in a million years. Using a managed language is the way to go. You can use managed C++ CLR compliant, so that statement in itself is a bit ambiguous. I guess these choices are based on personal experience. Noticed some RevitAPI code in F# once.

 

0 Likes

jeroen.van.halderen
Advocate
Advocate

I know it would have been better to take an average of multiple tests because the state of my machine and maybe even the state of Revit changed between tests, but I didn't have time to do multiple tests when I wrote this post. The results of my little comparison shouldn't be taken as the truth, if you want to know in which programming language your tool runs fastest I would advise to just test it for yourself.

 

Thanks for pointing out that my VB.NET code isn't quite the same as in the other two languages, I hadn't noticed that yet!

0 Likes

jeroen.van.halderen
Advocate
Advocate

Dear fellow developers,

 

It took me quite long, but I have found some time to correct the mistake in my VB.NET code pointed out by @RPTHOMAS108 and run multiple tests to get a better comparison between the three programming languages.

 

First of all, here's the corrected VB.NET code:

Imports Autodesk.Revit.Attributes
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.DB

<Transaction(TransactionMode.Manual)>
Public Class Command
    Implements IExternalCommand
    Public Function Execute(commandData As ExternalCommandData, ByRef message As String, elements As ElementSet) As Result Implements IExternalCommand.Execute
        Dim watch As Stopwatch = New Stopwatch
        watch.Start()
        Dim doc As Document = commandData.Application.ActiveUIDocument.Document
        Dim plane As Plane = plane.CreateByNormalAndOrigin(XYZ.BasisZ, New XYZ())

        Dim trans As Transaction = New Transaction(doc)
        trans.Start("Test")
        Dim sketchPlane As SketchPlane = SketchPlane.Create(doc, plane)
        For i = 1 To 1000
            doc.Create.NewModelCurve(Line.CreateBound(New XYZ(), New XYZ(0, i, 0)), sketchPlane)
        Next
        trans.Commit()
        watch.Stop()
        TaskDialog.Show("Runtimetest", "It took " & watch.Elapsed.Seconds & " seconds, " & watch.Elapsed.Milliseconds & " milliseconds and " & watch.Elapsed.Ticks & " ticks to create 1000 modelcurves")
        Return Result.Succeeded
    End Function
End Class

Secondly, to get an acceptable average testresult I ran each of the three plugins that I made ten times, the results are as follows:

jeroenvanhalderen_0-1635862186204.png

During these tests I had a few programs running in the background, but not more than what I would consider to be normal, so the result should be fairly comparable to what the average Revit user would get.

I hope this helps you in your journey that is learning how to program.

RPTHOMAS108
Mentor
Mentor

Thanks for the update, just two questions really:

 

What order did you conduct the tests in i.e. did you intermingle tests for each programming language or run each batch one after the other e.g. did tests for VB.Net come at end after other languages?

What was the state of Revit between each test i.e. did you restart Revit at all or restart the system in general?

 

Given the path of travel of .Net I'd probably be choosing C# if starting out today.

jeroen.van.halderen
Advocate
Advocate

I ran each sequence of 10 runs for each plugin one after the other, without restarting Revit or my computer in between. I only restarted Revit once after the first run of the VB.NET plugin because I closed Revit by mistake, however it didn't make a really big difference in execution time, so I think it's okay.

0 Likes

Sean_Page
Collaborator
Collaborator

This appear to be more what I'd expect to see especially between VB and C# as they should basically run identical in IL.

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
0 Likes

RPTHOMAS108
Mentor
Mentor

Yes in the end it all ends up as MSIL. Perhaps in a slightly different form due to language but not to a significant extent regarding performance.

 

Below are the results I gathered. Using your code I compiled into three assemblies and invoked via an F# assembly. Each assembly was invoked in the order VB/CS/CPP 11 times. One set of results was discarded (debug hitting break mode due to ContextSwitchDeadlock).

 

namespace RevitAddin

open System
open Autodesk.Revit.UI
open Autodesk.Revit.DB
open System.Reflection
open System.Collections.Generic
open System.Diagnostics
open System.Linq
open VB_Test
open RevitTestCS
open RevitTestCPP

[<Autodesk.Revit.Attributes.RegenerationAttribute(Autodesk.Revit.Attributes.RegenerationOption.Manual)>]
[<Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)>]
type EntryPoint1() =
  interface IExternalCommand with
    member x.Execute(commandData:Autodesk.Revit.UI.ExternalCommandData, message:byref<String>, elements:Autodesk.Revit.DB.ElementSet) =

        let IntUIDoc = commandData.Application.ActiveUIDocument 
        let IntDoc = commandData.Application.ActiveUIDocument.Document

        let vb = new VB_Test.Command()
        let cs = new RevitTestCS.Command()
        let cpp = new RevitTestCPP.Command()

        for i = 0 to 10 do
          vb.Execute(commandData, &message, elements) |> ignore
          cs.Execute(commandData, &message, elements) |> ignore
          cpp.Execute(commandData, &message, elements) |> ignore
              
        Result.Succeeded

 

 

 Test_VB    Test_CS    Test_CPP  
SecondsMillisecondsTicks SecondsMillisecondsTicks SecondsMillisecondsTicks
359035909869 353935398977 359135916002
357035708886 356335632770 355035503094
379237927712 362036206010 351535157249
349534956705 354435446136 352435249609
350935098848 353835384087 348934898128
354835482160 356735675425 352935294007
351135115181 377837784373 355835589702
360636065778 360036006141 358035802883
353735378800 352535251008 354235423529
353035308956 362636269563 355135512552
Average          
3568.835695289.5 359035905449 3542.935434675.5

 

0 Likes

RPTHOMAS108
Mentor
Mentor

Out of curiosity also tested F# and is slightly slower than the others. Tried with and without transaction using structure but slower result was the same.

 

Also found on this occasion C++ to be slower, bucking the trend (don't know why that was).

 

I suppose in the end people will choose a language based on the features it offers.

FS   VB   CS  
SecondsMillisecondsTicks SecondsMillisecondsTicks SecondsMillisecondsTicks
461046104674 356735675102 354135412883
424242423981 358835889337 355735570359
430543057801 355235525057 377437741724
433743375458 358435847504 355635563868
431643165970 362236229785 357235727086
428942898363 357435746935 418241821380
428142814815 358235825139 357435744402
425842583895 357235727252 362436248141
433643365424 357235727866 357835784266
428942898260 358835886823 358635869409
Average          
4326.343268864.1 3580.135808080 3.1554.436548351.8

 

CPP  
SecondsMillisecondsTicks
356835687214
357035703568
355035507877
361136118407
357135716139
356235622363
380438046229
359335937931
374537451302
358535852185
   
3615.936164321.5

 

 

namespace FS_Test

open System
open Autodesk.Revit.UI
open Autodesk.Revit.DB
open System.Reflection
open System.Collections.Generic
open System.Diagnostics
open System.Linq

//[<Autodesk.Revit.Attributes.RegenerationAttribute(Autodesk.Revit.Attributes.RegenerationOption.Manual)>]
//[<Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)>]
type EntryPoint1() =
  //interface IExternalCommand with
    member public x.Execute(commandData:Autodesk.Revit.UI.ExternalCommandData, message:byref<String>, elements:Autodesk.Revit.DB.ElementSet) =
        let watch:Stopwatch = new Stopwatch()
        watch.Start()

        let IntUIDoc = commandData.Application.ActiveUIDocument 
        let IntDoc = commandData.Application.ActiveUIDocument.Document
        
        let plane:Plane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero)
        let Lst = new List<ElementId>()

        let TS:TransactionStatus  =
                   use Tx = new Transaction(IntDoc,"Transaction Name")
                   if(Tx.Start() = TransactionStatus.Started) then
                        for i = 1 to 1000 do
                            let sketchPlane:SketchPlane = SketchPlane.Create(IntDoc, plane)                  
                            let MC:ModelCurve = IntDoc.Create.NewModelCurve
                                                        (Line.CreateBound
                                                            (XYZ.Zero,XYZ.Zero + 
                                                                XYZ.BasisY.Multiply((float i))),sketchPlane)
                            Lst.Add(MC.Id)

                        Tx.Commit()
                   else
                       TransactionStatus.Uninitialized
                      
        
        
        let TS:TransactionStatus  =
                   use Tx = new Transaction(IntDoc,"Transaction Name")
                   if(Tx.Start() = TransactionStatus.Started) then
                        IntDoc.Delete(Lst) |> ignore
                        Tx.Commit()
                   else
                       TransactionStatus.Uninitialized    

        watch.Stop()
        Debug.WriteLine("Test_FS",watch.Elapsed.Seconds.ToString() + ":" + 
                            watch.Elapsed.Milliseconds.ToString() + ":" + 
                                watch.Elapsed.Ticks.ToString())


        Result.Succeeded