Managed C# versus Unmanaged C++

This is Howard C-Shell, coming to youlive from your typical Windows computer just outside of Disney World. Here, Iwill find out once and for all how much slower managed C# is then unmanagedC++. In this bout of speed, unmanaged C++ is the clear favorite.

This article is going to run someperformance tests against C# and C++. I think it has become generally acceptedthat Visual C++ is the best, if not, one of the best performance compilers onthe Win32 platform. Many developers have approached the dotNet community withquestions as to the performance of unmanaged C++ on Win32 versus managed C# indotNet.

I think I should begin by explaining mydevelopment environment. The environmental details are that I?ve got a DellInspiron 3800 G700GT notebook that is slightly over one year old. I haveWindows 2000 with SP2, Visual Studio.NET and the dotNet platform.

All the tests are performed withcommand-line executables that were compiled in release mode and run from thecommand prompt, not the Visual Studio IDE. These are the results right out ofthe box. No optimizations. Optimizations to the C++ or the C# compilers mightproduce different results and this might be a subject for a further article.

I plan on running four different tests.Some of them are classic performance test (Sieves) and others are solelybecause I want to test the performance of a specific item in the dotNetframework.

<!–[if !supportLists]–>?       <!–[endif]–>Hello World Test

<!–[if !supportLists]–>?       <!–[endif]–>Sieve of Eratosthenes

<!–[if !supportLists]–>?       <!–[endif]–>Database Access Test

<!–[if !supportLists]–>?       <!–[endif]–>XML Test

An important fact is that I?m trying tomake the code in both environments as similar as possible. Please, if you finda discrepancy that favors one language over the other, then please send me somekind words and I?ll republish the results.

Hello World

The Hello World test programs measure theamount of time that it takes to load a program and its run-time environment. Inthe case of C++, that?s the C run-time library and pretty lightweight. In C#,the dotNet framework must be loaded, which arguable is not as lightweight.

The code for the C++ Hello World programis presented in Listing 1.

Listing 1:helloworld.cpp

#include <iostream>

int main(int argc, char *argv[])

{

?std::cout << "Hello World" << std::endl;

?return 0;

};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The code for the C# Hello World programis presented in Listing 2.

Listing 2:helloworld2.cs

using System;

namespace HelloWorld

{

class Class1

?{

???static void Main(string[] args)

???{

?????Console.WriteLine("Hello World");

???}

?}

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The results of this test will indicate tous the load time for each environment. A quick load time is often desirablewhen you have processes that load, perform a very few amount of small tasks andexit. Perl scripts typically had large load times that made them undesirable inperformance oriented CGI-base websites. In these cases, C++ was often chosenover similar Perl programs.

In longer-lived programs, the load timecan be irrelevant compared to the run-time performance.

n lang="
EN-US">The results of running the test 10 timesare presented in Table 1.

Table 1:Hello World Test Results

Test Run

C++ (milliseconds)

C# (milliseconds)

1

40

1221

2

20

121

3

10

130

4

10

100

5

10

110

6

10

130

7

10

120

8

10

140

9

10

150

10

20

140

Average

15

235

These results are not accurate to themilliseconds as the GetTickCount function is not capable of such accuracy.Rather the results are accurate to about the centisecond (hundredth of asecond).

The results reveal a couple of points. First,cold starting a dotNet application produces a more expensive startup thanrunning the same application a second time. Second, the startup cost onsubsequent runs is about one tenth of a second longer in dotNet. In mostapplications, a one-tenth of a second startup cost is irrelevant.

Sieve of Eratosthenes

The Sieve of Eratosthenes test programsmeasure basic integer arithmetic and comparison logic. The algorithm wasdevised long before computers and thus is a great proving ground for evaluatinghuman algorithms in various environments.

The C++ code for the Sieve of Eratostenesprogram is presented in Listing 3.

Listing 3:sieve.cpp

#include <windows.h>

#include <iostream>

#include <algorithm>

#include <vector>

#include <cstdlib>

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

using namespace std;

int main(int argc, char *argv[])

{

?if (argc != 2)

?{

???std::cerr << "Usage:\tsieve [iterations]\n";

???return 1;

?};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?size_t NUM = atoi(argv[1]);

?DWORD dw = ::GetTickCount();

?vector<char> primes(8192 + 1);

?vector<char>::iterator pbegin = primes.begin();<

/span>

?vector<char>::iterator begin = pbegin + 2;

?vector<char>::iterator end = primes.end();

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?while (NUM–)

?{

???fill(begin, end, 1);

???for (vector<char>::iterator i = begin;

?????? ?????? i < end; ++i)

???{

?????if (*i)

?????{

???????const size_t p = i – pbegin;

???????for (vector<char>::iterator k = i + p;

????????? k < end; k += p)

???????{

????????? *k = 0;

???????}

?????}

???}

?}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?DWORD dw2 = ::GetTickCount();

?std::cout << "Milliseconds = " << dw2-dw

???<< std::endl;

?return 0;

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The C# code for the Sieve of Eratostenesprogram is presented in Listing 4.

Listing 4:sieve.cs

using System;

namespace Sieve

{

?class Class1

?{

???static void Main(string[] args)

???{

?????if (args.Length != 1)

?????{

???????Console.WriteLine("Usage:\tsieve "

????????? "[iterations]");

???????return;

?????}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????int NUM = int.Parse(args[0]);

?????long dt = DateTime.Now.Ticks;?????

?????int[] primes = new int[8192+1];

?????int pbegin = 0;

?????int begin = 2;

?????int end = 8193;

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????while (NUM– != 0)

?????{

???????for (int i = 0; i < end; i++)

???????{

????????? primes[i] = 1;

???????}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

???????for (int i = begin; i < end; ++i)

???????{

????????? if (primes[i] != 0)

????????? {

??????????? int p = i – pbegin;

??????????? for (int k = i + p; k < end; k += p)

??????????? {

????????????? primes[k] = 0;

??????????? };

????????? }

???????};

?????};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????long dt2 = DateTime.Now.Ticks;

?????System.Console.WriteLine("Milliseconds = {0}",

???????(dt2-dt)/10000);

???}

?}

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

It?s not enough to say that oneenvironment is faster than another. In each test, I?m indicating which languageconstructs will most impact the results of the test. When you are choosing alanguage based on the performance, you should be looking at exactly what typeof performance you need. In this case, the Sieve of Eratosthenes programs testthe loop constructs and both comparison and manipulation of the integer basetype.

The results of running the test 10 timeswith 10000 iterations per test are presented in Table 2.

Table 2:Sieve Test Results

Test Run

C++ (milliseconds)

C# (milliseconds)

1

1342

2724

2

1342

2714

3

1342

2724

4

1342

2724

5

1342

2734

6

1342

2724

7

1362

2734

8

1352

2734

9

1362

2724

10

1352

2724

Average

1348

2726

The results are pretty conclusive.Integer calculations are twice as fast in C++ as C#. That?s something toconsider. A logic intensive server might have the same issues and might be moresuitable as unmanaged C++ then as managed C#.

There is one difference between the C++and C# code. The C# code uses a native array, whereas the C++ code uses thevector template class. I rewrote the C++ code to use the native array thinkingthat it would be faster. It wasn?t. The native C++ array clocked in the 1900sof milliseconds.

Database Access

In this section, I?ll be writing some C++and C# code that tests both data access and manipulation. All the tests will beagainst one table with the following data definition.

CREATE TABLE testtable

(

?col1 INTEGER,

?col2 VARCHAR(50),

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?PRIMARY KEY (col1)

)

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The test will be divided into three. Thefirst and third tests will focus on data manipulation queries, while the secondtest will focus on data access queries. The results for data manipulation anddata access will be presented separately.

The C++ code for data access andmanipulation is presented in Listing 5.

Listing 5:db.cpp

#import "msado15.dll" \

no_namespace rename("EOF", "EndOfFile")

#include <iostream>

#include <string>

#include <sstream>

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

int main(int argc, char* argv[])

{

?if (argc != 2)

?{

???std::cerr << "Usage:\tdb [rows]\n";

???return 1;

?};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?::CoInitialize(NULL);

?int NUM = atoi(argv[1]);

?DWORD dw = ::GetTickCount();

?_ConnectionPtr conptr(__uuidof(Connection));

?conptr->Open(L"Provider=Microsoft.Jet.OLEDB.4.0;"

???"Data Source=c:\db.mdb;",

???L"",

???L"",

???adOpenUnspecified);

?for (int i=0;i<NUM;i++)

?{

???VARIANT RecordsEffected;

???RecordsEffected.vt = VT_INT;

???std::wstringstream ss;

???ss << L"INSERT INTO testtable (col1, col2) "

?????<< "VALUES ("

????

?<< i+1 << L", ?" << i+1 << L"?)";

???_bstr_t sql = ss.str().c_str();

???conptr->Execute(sql, &RecordsEffected, adCmdText);

?};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?DWORD dw2 = ::GetTickCount();

?std::cout << "Milliseconds = " << dw2-dw

???<< std::endl;?

?dw = ::GetTickCount();

?for (int j=0;j<100;j++)

?{

???_RecordsetPtr rsptr(__uuidof(Recordset));

???rsptr->Open(L"SELECT col1, col2 FROM testtable",

???conptr.GetInterfacePtr(),?

???adOpenForwardOnly, adLockOptimistic, adCmdText);

???while (rsptr->EndOfFile)

???{

?????_variant_t v1 = rsptr->GetCollect("col1");

?????_variant_t v2 = rsptr->GetCollect("col2");

?????rsptr->MoveNext();

???};

??? rsptr->Close();

?};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?dw2 = ::GetTickCount();

?std::cout << "Milliseconds = " << dw2-dw

???<< std::endl;?

?dw = ::GetTickCount();

?for (int i=0;i<NUM;i++)

?{

???std::wstringstream ss;

???VARIANT RecordsEffected;

???RecordsEffected.vt = VT_INT;

???ss << L"DELETE FROM testtable WHERE col1 = "

?????<< i+1;

???_bstr_t sql = ss.str().c_str();

???conptr->Execute(sql, &RecordsEffected, adCmdText);

?};

?conptr->Close();

?dw2 = ::GetTickCount();

?std::cout << "Milliseconds = " << dw2-dw

???<< std::endl;?

?::CoUninitialize();

?return 0;

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The C# code for data access andmanipulation is presented in Listing 6.

Listing 6:db2.cs

using System;

using System.Data;

using System.Data.OleDb;

using System.Text;

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

namespace Db

{

?class Class1

?{

???static void Main(string[] args)

???{

?????if (args.Length != 1)

?????{

???????Console.WriteLine("Usage:\tdb2 [rows]");

???????return;

?????}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????int NUM = int.Parse(args[0]);

?????long dt = DateTime.Now.Ticks;? ????

?????OleDbConnection connection;

?????connection = new OleDbConnection(

???????"Provider=Microsoft.Jet.OLEDB.4.0;"

???????"Data Source=c:\db.mdb;");

?????connection.Open();

?????for (int i=0;i<NUM;i++)

?????{

???????StringBuilder ss = new StringBuilder();

???????ss.Append("INSERT INTO testtable (col1, col2)"

????????? " VALUES (");

???????ss.Append(i+1);

???????ss.Append(", ?");

???????ss.Append(i+1);

???????ss.Append("?)");

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

???????OleDbCommand command = new OleDbCommand(

????????? ss.ToString(), connection);

???????command.ExecuteNonQuery();

?????};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????long dt2 = DateTime.Now.Ticks;

?????System.Console.WriteLine("Milliseconds = {0}",

???????(dt2-dt)/10000);

?????dt = DateTime.Now.Ticks;

?????for (int j=0;j<100;j++)

?????{

???????DataSet dataset = newDataSet();

???????OleDbCommand command = new OleDbCommand(

?????????"SELECT col1, col2 FROM testtable",

????????? connection);

???????OleDbDataReader reader =

"smallblack">????????? command.ExecuteReader();

???????while (reader.Read() == true)

???????{

????????? int v1 = reader.GetInt32(0);

????????? string v2 = reader.GetString(1);

???????};

???????reader.Close();

?????};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????dt2 = DateTime.Now.Ticks;

?????System.Console.WriteLine("Milliseconds = {0}",

???????(dt2-dt)/10000);

?????dt = DateTime.Now.Ticks;

?????for (int i=0;i<NUM;i++)

?????{

???????StringBuilder ss = new StringBuilder();

???????ss.Append("DELETE FROM testtable "

????????? "WHERE col1 = ");

???????ss.Append(i+1);

???????OleDbCommand command = new OleDbCommand(

????????? ss.ToString(), connection);

???????command.ExecuteNonQuery();

?????};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????connection.Close();

?????dt2 = DateTime.Now.Ticks;

?????System.Console.WriteLine("Milliseconds = {0}",

???????(dt2-dt)/10000);

???}

?}

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The results of running the test 10 timeswith 100 rows per test are presented in Table 3.

Table 3 :D atabase Test Results

Test Run

C++ (milliseconds)

C# (milliseconds)

1

1612/441/450

4086/630/560

2

391/410/441

490/630/520

3

370/421/440

480/510/440

4

371/420/451

470/510/450

5

370/421/461

460/500/450

6

371/420/461

470/500/460

7

n>

370/411/471

470/500/460

8

381/410/451

460/510/470

9

370/421/450

470/510/470

10

391/410/461

460/510/470

Average

499/419/454

832/531/475

The results were quite surprising to me. Consideringthe benefits you get with dotNET, I don't think it's much to expect a 25%decrease in performance. I think dotNET is a winner here.

XML

The latest craze in computing is XML.Many will be interested in the C# XML parsing performance versus the sameperformance on Visual C++.

The C++ code for xml access and manipulationis presented in Listing 7.

Listing 7 :x ml.cpp

#import <msxml3.dll> named_guids

#include <iostream>

int main(int argc, char* argv[])

{

?if (argc != 2)

?{

???std::cerr << "Usage:\txml [filename]\n";

???return 1;

?};

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?::CoInitialize(NULL);

?DWORD dw = ::GetTickCount();

?for (int i=0;i<100;i++)

?{

???MSXML2::IXMLDOMDocumentPtr DomDocument(

?????MSXML2::CLSID_DOMDocument) ;

???_bstr_t filename = argv[1];

???DomDocument->async = false;

???DomDocument->load(filename);

?}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?DWORD dw2 = ::GetTickCount();

?std::cout << "Milliseconds = " << dw2-dw

???<< std::endl;

?::CoUninitialize();

?return 0;

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The C# code for xml access and manipulationis presented in Listing 8.

Listing 8 :x ml.cs

using System;

using System.Xml;

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

namespace xml2

{

?class Class1

?{

???static void Main(string[] args)

???{?

class="smallblack">?????if (args.Length != 1)

?????{

???????Console.WriteLine("Usage:\txml [filename]");

???????return;

?????}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????long dt = DateTime.Now.Ticks;?????

?????for (int i=0;i<100;i++)

?????{

???????XmlDocument doc = new XmlDocument();

???????doc.Load(args[0]);

?????}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

?????long dt2 = DateTime.Now.Ticks;

?????System.Console.WriteLine("Milliseconds = {0}",(dt2-dt)/10000);

???}

?}

}

<!–[if !supportEmptyParas]–> <!–[endif]–><o:p></o:p>

The results of running the test 10 timesare presented in Table 4.

Table 4:XML Test Results

Test Run

C++ (milliseconds)

C# (milliseconds)

1

241

1111

2

170

841

3

161

841

4

170

861

5

160

861

6

171

851

7

170

841

8

160

831

9

160

841

10

170

851

Average

203

873

These results were again very surprisingto me. It?s hard to believe that the dotNET XML classes are four to five timesslower than equivalent ActiveX classes. It may be that under the hood, thedotNET classes are doing something different that will save me time somewhereelse. Otherwise, I hope Microsoft will spend some time optimizing their dotNETXML classes.

Conclusion

Something that must be remembered is thatthe dotNET framework is newer than all the technologies that I measured itagainst today. As such, there should be a lot of room for optimizations withinthe framework. What also must be said is that I've only started performancetesting dotNET. I could only fit four tests int

o a brief article and there areobviously many other components that might be faster or slower with dotNET.

About the Author

Randy Charles Morin is the ChiefArchitect of 1X Inc [www.1xinc.com] from Toronto, Ontario, Canada and liveswith his wife and two kids in Brampton, Ontario. He is the author of the www.kbcafe.com website, many articles and many books.

Twitter Digg Delicious Stumbleupon Technorati Facebook Email

No comments yet... Be the first to leave a reply!