Collection

Programming C++ standalone code - thus .NET technology is not indispensable.

There are several options to create collections of data:

- MFC: using classes such as CArray, CTypedPtrArray, etc.

- STL: using classes from <array>, <list>, <map>, ...

- .NET framework: using classes from System::Collections

Generally speaking, which solution is the best performing in terms of speed and memory usage? Are they in the end the same thing, after they are compiled to executable code?

My apologies if this question is just a silly one.

[597 byte] By [giorgiosf] at [2007-12-24]
# 1

The STL classes will generally be the best. I'm not too familiar with the MFC containers, but they are not likely to provide any higher speed or storage efficiency than sane use of STL will. I'd say the STL ones will always either match of outperform those MFC.

The .NET classes will preform quite good, but are likely to consume more memory (.NET usually does use a fair share of that). I'm sure there are hard evidence benchmarks out there somewhere, but I'm not familiar with any (a couple of quick searches should get you going). To draw a parallell to Java, which like .NET runs in a VM, one shouldn't too bluntly dismiss them. Java has proven to be very efficient with regard to container mechanisms and algorithms. Obviously, .NET and Java isn't the same piece of technology, and shouldn't be treated as such, but it should eliminate the notion that VMs will make a language perform poorly. To look at it somewhat differently: a set of native classes with defective design, is likely to perform worse than a simple set of managed classes -- meaning that .NET is more forgiving with regard to how you design and implement the application, than what you will see with the native counterparts.

einaros at 2007-10-8 > top of Msdn Tech,Visual C++,Visual C++ General...
# 2

Your answer was very professional. However, since you declared that you are "not too familiar with the MFC containers", I decided to do a test of my own. Although this test is not definitive, it appears that managed code significantly outperforms both STL and MFC, both in terms of memory usage and of speed! I have to admit I am very surprised by this result.

I created the following lists (this type of collection is declared to be the best performing on all systems)

STL: list<MyClass*> STL_list
MFC: CPtrList MFC_list
Managed: List<MyRefClass^> CLR_list

I let each list grow by 100,000 elements (each element is a pointer to MyClass / handle to MyRefClass respectively), then a search through the list, finally I destroy all the elements and the list. Here are the results.

STLMFCManagedEnd
INITIAL
Paged=Private10,825,72811,038,72011,091,96813,856,768
WorkingSet9,117,6969,539,5849,625,60012,365,824
FULL LIST
Paged=Private14,913,53613,950,97613,791,232
WorkingSet13,336,57612,480,51212,259,328
delta Paged4,087,8082,912,2562,699,264
delta WorkingSet4,218,8802,940,9282,633,728
ELAPSED TIME0.10446880.06708640.0102848

I do not understand why apparently memory is not recovered after destroying CLR_list (see coloumn 'End'), however the results seem impressive expecially for the elapsed time.

Just for your information and for completeness.

Regards. Following is the complete source:

// TestList.cpp : Defines the entry point for the console application.

//

#include"stdafx.h"

#include"TestList.h"

#define RAND_ITEMS 100000

// The one and only application object

CWinApp theApp;

usingnamespace std;

usingnamespace System;

usingnamespace System::Diagnostics;

usingnamespace System::Collections::Generic;

class MyClass {

public:

int x,y;

MyClass(int xx,int yy) : x(xx),y(yy) {}

};

refclass MyRefClass {

public:

int x,y;

MyRefClass(int xx,int yy) : x(xx),y(yy) {}

//~MyRefClass() { Console::WriteLine("Destroying {0}",x); }

//!MyRefClass() { Console::WriteLine("Finalizing {0}",x); }

};

void DisplayMemory(String^ str)

{

Process^ proc = Process::GetCurrentProcess();

Console::WriteLine("Memory - {0}:",str);

Console::WriteLine("Paged=Private {0}",proc->PrivateMemorySize64);

Console::WriteLine("WorkingSet {0}",proc->WorkingSet64);

}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

int nRetCode = 0;

// initialize MFC and print and error on failure

if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))

{

// TODO: change error code to suit your needs

_tprintf(_T("Fatal Error: MFC initialization failed\n"));

nRetCode = 1;

}

else

{

Stopwatch sw;

int i,count,r[RAND_ITEMS];

MyClass* p;

// declare the lists

list<MyClass*> STL_list;

CPtrList MFC_list;

List<MyRefClass^> CLR_list;

// initialize r[]

for(i=0 ; i<RAND_ITEMS ; i++)

{

UINT u;

if(0!=rand_s(&u))

{

Console::WriteLine("The rand_s function failed");

return 2;

}

rIdea = (int)((double)u/(double)UINT_MAX*1000);

}

// Perfomance test

// STL

Console::WriteLine("Testing STL");

DisplayMemory("Initial");

sw.Start();

for(i=0 ; i<RAND_ITEMS ; i++) STL_list.push_back(new MyClass(i,rIdea));

DisplayMemory("Full List");

count = 0;

foreach(p in STL_list) if(p && p->y==500) count++;

Console::WriteLine("Count = {0}",count);

while(!STL_list.empty())

{

p = (MyClass*)STL_list.back();

if(p) delete p;

STL_list.pop_back();

}

sw.Stop();

Console::WriteLine("Elpsed time: {0}",sw.Elapsed);

sw.Reset();

// MFC

Console::WriteLine("\nTesting MFC");

DisplayMemory("Initial");

sw.Start();

for(i=0 ; i<RAND_ITEMS ; i++) MFC_list.AddTail(new MyClass(i,rIdea));

DisplayMemory("Full List");

count = 0;

POSITION pos = MFC_list.GetHeadPosition();

while(pos)

{

p = (MyClass*)MFC_list.GetNext(pos);

if(p && p->y==500) count++;

}

Console::WriteLine("Count = {0}",count);

while(!MFC_list.IsEmpty())

{

p = (MyClass*)MFC_list.GetTail();

if(p) delete p;

MFC_list.RemoveTail();

}

sw.Stop();

Console::WriteLine("Elpsed time: {0}",sw.Elapsed);

sw.Reset();

// Managed

Console::WriteLine("\nTesting CLR");

DisplayMemory("Initial");

sw.Start();

for(i=0 ; i<RAND_ITEMS ; i++) CLR_list.Add(gcnew MyRefClass(i,rIdea));

DisplayMemory("Full List");

count = 0;

foreach(MyRefClass^ p in CLR_list) if(p && p->y==500) count++;

Console::WriteLine("Count = {0}",count);

foreach(MyRefClass^ p in CLR_list) if(p) delete p;

CLR_list.Clear();

CLR_list.TrimExcess();

sw.Stop();

Console::WriteLine("Elpsed time: {0}",sw.Elapsed);

// End

DisplayMemory("End");

}

return nRetCode;

}

giorgiosf at 2007-10-8 > top of Msdn Tech,Visual C++,Visual C++ General...
# 3
This is probably related to memory allocation. Try profiling your application, and you should see it spending most of the time inside operator new for the native code. Using a custom allocator should speed things up considerably.
einaros at 2007-10-8 > top of Msdn Tech,Visual C++,Visual C++ General...