2008-11-28

Sharpening your tools

Let me share with you a couple of simple things that could make your life a bit easier if you use Microsoft Visual Studio for development on Open CASCADE.

1. Highlighting Open CASCADE class names in the editor

This will improve your code readability and what is more important, can serve as additional quick check whether you correctly spell a OCC class name before you get a compiler error ;-).






What you just need to do is to copy the file UserType.dat shipped with Open CASCADE (tools subdirectory) it to where devenv.exe is located. For instance, on my laptop it’s here - c:\Program Files\Microsoft Visual Studio 8\Common7\IDE\. If you already have some UserType.dat, just concatenate the contents.

2. More convenient display of Open CASCADE data types in debugger

Have you even been tired of clicking in a deep nested tree in the Watcher window to dig down from your surface just to check its reference count ? If yes, this can be for you.

As you might know, the debugger allows you to display complex types (e.g. classes) not only as default ‘{…}’. To do that you can describe display rules, modifying the autoexp.dat file.
I have found and extended one with a few frequently used OCC types. Go download it here and insert into autoexp.dat, located inside Visual Studio hierarchy. For instance, for VS2005 it is here - c:\Program Files\Microsoft Visual Studio 8\Common7\Packages\Debugger\Autoexp.dat.
Compare these two screenshots. The one below (with modified autoexp.dat) displays everything what one above does but in a more concise form without forcing you to expand the tree.









OCC team: feel free to include this file or its extended version into Open CASCADE release ;-).

There is yet another functionality – skipping stepping into a function in the Debugger, so that when you click F11, it bypasses some internal functions you don’t want to spend your time in. This is an undocumented technique described here. I did not try it but you may want to. If you are a success, please share your settings with us, this will likely be helpful for several people.

There are likely more than that. If anyone is willing to share his/her experience, please send me an email at roman.lygin@gmail.com and I can post it.

2008-11-26

Building from scratch. Part 2

(continued)

First, do you know where WOK name comes from ? It’s not just Workshop Organization Kit as named in the documentation. In Chinese cuisine wok is a deep convex pan in which chef cooks food (I’m sure you saw it, right ?) The name was given by a former Matra Datavision employee who was of Chinese origin and initiated it. WOK was used in MDTV in multi-site, multi-team, cross-platform environment who intensively worked on development of one product bundle (Euclid Quantum) that contained many components (including its foundation – CAS.CADE). The source base was huge, release cycles were asynchronous and this could not fit into one source control repository that would permanently built. Take into account state of the art hardware of that time (slow Sun workstations, networks, etc). So development environment had to reflect that and WOK really enabled that dispersed and hierarchical environment. You could take a piece of software and put into your local workbench or creates a tree of workbenches and develop.

Time passed, Euclid Quantum dissolved, CAS.CADE transformed into Open CASCADE, but WOK is still alive though not developed anymore. It’s pure legacy, and current investment into it would unlikely be justified. WOK still does its job enabling cross-platform and multi-team software development but it’s way too heavy for that. It’s like if you used a truck to drive to your office one mile away from your home.

In terms of building time WOK does not stand any competition with VS which builds in parallel threads. Building Data Exchange module in Release mode on quad-core Xeon processor took 13 minutes where WOK was crunching for 32.

My personal view on development environment is that you should borrow what is available outside. Don’t re-invent a wheel when others produce good ones you can freely use. There are options available out there, with wide adoption, with active evolution. Go and grab some. In our team in Intel we use SVN and SCONS-based build system (of course, with custom tweaks to setup a whole infrastructure around it – promotion process, unit and integration tests, build rotation, etc). I heard Google’s Chrome also uses SCONS. There are also cmake, qmake (Qt-tailored) and likely many other decent options.

Migration has a cost, but if you count long term benefits, you can change your mind. My humble opinion.

Building from scratch. Part 1

As I said in my previous post, we decided to integrate OCC into our team’s app test database at Intel. So I committed to enable that integration and proceeded to the task.

In order to get correct data when profiling an application (including using Intel Parallel Amplifier and Inspector), it must be compiled with /Zi option and linked with /DEBUG options to enable symbol information and store it in pdb. For performance tuning it should also be compiled with /O2 (maximize speed), and software vendors usually apply it. So, I needed all these three. This is not a default combination offered by Visual Studio (/Zi and /DEBUG come in Debug configuration with /Od and Release come with /O2 but without /Zi and /DEBUG). OCC ships *.vcproj files with those conventions. So I had to manually update all *.vcproj and for the Release config to set /Zi and /DEBUG. This was a first challenge, as OCC does not provide a single VS solution (*.sln) where I could select all projects and modify only once (fortunately Visual Studio provides multiple selection of the projects to change settings at once). So I had to reopen each solution one by one. I was luck as setting /Zi and /DEBUG could be done at the project file level and each source file (*.cxx) inherited these settings. This is not the case for optimization parameters (/O2) which are set in OCC shipped *.vcproj files on individual basis, for each source file. I believe this was done by intended oversight in some scripts generating *.vcproj from WOK. But this did not make my life easier two months ago when I initially had to set /Od for Release configurations (and I had to ‘grep’ project files and remove individual settings then). OCC folks in charge of the build environment and those who tweak OCC files might want to take this note into account.

Rebuilding OCC in MSVS (I am currently using 2005, by the way) was straightforward and seamless. Just had to reopen a new solution every few minutes and click Build Solution. The fact that there is no single global solution is a bit irritating though. I remember in the past we refrained from creating it because VS98 simply could not deal with it (running out of memory or something like that). Is it still the problem on newer VS (2003 and beyond), did anyone try ?
What was a bit annoying is number of warnings, sometimes really ugly (like intentional semicolon after some ‘if’ in Foundation Classes or constant arithmetic overflow in Modeling Algorithms). Right now in our team at Intel we are also fighting with warnings and set an aggressive goal to treat warnings as errors (we are not there yet however). Every code promotion will be rejected if it reveals warnings. Though it adds some overhead (and some developers complain) I believe this is beneficial for the product. So, if OCC team adopts that behavior sometime, we may see OCC compiled with 0 warnings ;-).

A few words on WOK. As I have to use my patched version of OCC 6.3.0 (to enable multi-threading and use some other modifications), I had to start with WOK. There are modifications in CDL extractor (that generates *.hxx from *.cdl) and in addition that are several new classes (with *.cdl definitions). Well, WOK is not something an average developer would (and should!) understand. It’s a beast in its own. Two months ago I spent a good deal of time to install it on my laptop and even this time when migrating to a lab workstation I had to be very careful in setting up an environment in multiple *.edl and *.bat files. Unless you spent several months or years with it, any mistake can cost you hours to figure out what goes wrong. I would seriously recommend you not to use it unless you really have to and understand what you are doing. WOK however was a great idea in 1990-es and let me shed some light on it.

(to be continued)

2008-11-24

Open CASCADE inside Intel, or ... Intel inside

Just finished the meeting with my Intel colleagues where we decided that Open CASCADE would be integrated into an internal application database for testing our software products. I am a program manager in the team that is developing new products – Intel Parallel Amplifier and Parallel Inspector, which will be part of the Intel Parallel Studio (www.intel.com/go/parallel). They are approaching public Beta program (feel free to sign up by the way!) and are currently in hands of our few Beta customers. Guess who is among those few ?

Why did we chose Open CASCADE software for app testing? Because it helps make Intel products better ! It already allowed us to find several bugs before we released first Beta, and we are still discovering more with it. Despite that it’s single-threaded today there are steps to multi-threading. I am also going to use the multi-threading framework prototype for testing it in parallel mode (mentioned on the forum in October).

I really enjoy situations like this – when you can combine interests of several parties. It’s good for Intel, it’s good for Open CASCADE, it’s good for me ;-). I love to be a ‘deal maker’, and you ?

Disclaimer: Whenever I talk about Intel, this represents my sole personal opinion, it does not reflect any official position of the company. That’s a policy. I had to say it. I did.

2008-11-21

Open CASCADE Handles. Let's handle'em. Part 3

(continued)

3. Bypass DownCast() in critical places.
Compare:
a.
Handle(Standard_Transient) aTransient = new OCC_UT_Id(1);

for (i = 0 ; i < aNbCycles; i++) {
anId = Handle(OCC_UT_Id)::DownCast (aTransient)->Id();
}
b.
for (i = 0 ; i < aNbCycles; i++) {
Handle(OCC_UT_Id)& anIdHTmp = *((Handle(OCC_UT_Id)*) &aTransient);
anId = anIdHTmp->Id();
}
c. for (i = 0 ; i < aNbCycles; i++) {
OCC_UT_Id* anIdPTmp = (OCC_UT_Id*)aTransient.Access();
anId = anIdPTmp->Id();
}

a. takes 1.75s, b. and c. – ~0.04s, or are 40+ times faster (in some runs 100+) !

You will find use of b. in BRep_Tool methods, by the way.

However beware of cases with potential problems. Never use direct cast unless you can reliably check, if you can safely use it. For instance, the following code throws an exception instead of returning a null handle:
TopoDS_Face aFace;
TopLoc_Location aLoc;
Handle(Geom_Surface) aSurf = BRep_Tool::Surface (aFace, aLoc);

These were my insights. Anybody to share others ? I’d love to hear.

As Handles() are most widely used classes, their implementation must be flawless. In this regard, I wonder if use of UndefinedHandleAddress (equal to 0xfefd0000 or 0xfefdfefdfefd0000 on 64-bit platforms – see Hande_Standard_Transient.hxx) to denote a null handle makes any sense. Perhaps, plain zero would be enough, and this would save on comparison operators. Is someone willing to experiment ? OCC folks ?

*Multi-threading considerations*
In the conclusion, let me add that Handle() is not (yet?) thread-safe. You need to protect a handle instance with critical section (e.g. with Standard_Mutex) to use concurrently. Otherwise you may have a data race (concurrent access to unprotected data) and may end up with broken reference counter and consequently memory leaks, access violations, or other headaches.

Well, that was it, folks. So, how useful was it ? Please post your comments and tell me what you think. This is important for me. Thanks.
Roman

2008-11-20

Open CASCADE Handles. Let's handle'em. Part 2

(continued)

*Standard_Transient and MMgt_TShared*
If you are attentive enough, you will likely notice that most handle classes inherit MMgt_TShared, while it does not add any value to Standard_Transient. The comments in the cdl file claims it supports reference counting. Well, reference counter is inside Standard_Transient ! I dove into the source code of Open CASCADE 3.0 (released in 1999, the first open source release) and it is the same. So, it seems this change was done more than ten years ago, but the orphan MMgt_TShared is still alive. So, I am now using Standard_Transient only and advised OCC folks to remove MMgt_TShared eventually. So, you might want to migrate to Standard_Transient as your base class as well.

*Cyclic dependencies*
The underlying objects won’t get destroyed if you have cyclic dependence (i.e. one object referring to the other), and you will end up with a memory leak. For instance the following class is prone to such a cyclic dependency:

DEFINE_STANDARD_HANDLE(OCC_UT_Employee,Standard_Transient)
class OCC_UT_Employee : public Standard_Transient
{
public:
OCC_UT_Employee (const Handle(OCC_UT_Employee)& theBoss) : myBoss (theBoss) {}
const Handle(OCC_UT_Employee)& Boss() const { return myBoss; }
void AddDirectReport (const Handle(OCC_UT_Employee)& theReport);

private:
Handle(OCC_UT_Employee) myBoss;
TColStd_ListOfTransient myDirectReports;
public:
DEFINE_STANDARD_RTTI(OCC_UT_Employee)
};

As Foundation Classes User’s Guide suggests you either have to use pointers in some case (e.g. OCC_UT_Employee* myBoss) or nullify some references first (myBoss.Nullify()).


*Handle overhead*
Like for anything in this world, you have to pay for convenience of using a handle. Any assignment or deallocation leads to execution of several instructions (comparisons, function calls, branching, etc). Keep that mind. If you are using handle in performance-critical places, think how you can optimize. Some hints on top of my head:

1. Make DownCast() to some expected type first and then use inside a cycle.
Compare two implementations:

static void CheckEmployee (const Handle(Standard_Transient)& theEmployee)
{
for (int i = 0 ; i < aNbCycles; i++) {
Handle(OCC_UT_Employee) anEmp = Handle(OCC_UT_Employee)::DownCast (theEmployee);
}
}

static void CheckEmployee (const Handle(Standard_Transient)& theEmployee)
{
Handle(OCC_UT_Employee) anEmployee = Handle(OCC_UT_Employee)::DownCast (theEmployee);
for (int i = 0 ; i < aNbCycles; i++) {
Handle(OCC_UT_Employee) anEmp = anEmployee;
}
}

With aNbCycles equal 10 millions, on my laptop, the former takes 1.6 secs, and the latter – 0.42 secs, or 3.8X speed up.

2. Avoid copies
Make your class methods return const Handle()& whenever possible and assign to the same type in the caller, rather than using plain Handle(). Consider the following two cases:

a. Imagine you initially defined the Boss() method in OCC_UT_Employee class as follows:
Handle(OCC_UT_Employee) Boss() const { return myBoss; }

and use it as follows:
static void CheckBoss (const Handle(Standard_Transient)& theEmployee)
{
Handle(OCC_UT_Employee) anEmployee = Handle(OCC_UT_Employee)::DownCast (theEmployee);
for (int i = 0 ; i < aNbCycles; i++) {
Handle(OCC_UT_Employee) aBoss = anEmployee->Boss();
}
}

b. Now you switch to const Handle()&:

const Handle(OCC_UT_Employee)& Boss() const { return myBoss; }
...
static void CheckBoss (const Handle(Standard_Transient)& theEmployee)
{
Handle(OCC_UT_Employee) anEmployee = Handle(OCC_UT_Employee)::DownCast (theEmployee);
for (int i = 0 ; i < aNbCycles; i++) {
const Handle(OCC_UT_Employee)& aBoss = anEmployee->Boss();
}
}

Speed up is from 0.26s to 0.03s, or 8.5x ! However, admittedly, Open CASCADE code itself is contaminated with these issues. OCC folks: if you are reading this, please take a note ;-).

(to be continued)

Open CASCADE Handles. Let's handle'em. Part 1

Let me start the first article with something simple yet important if you want to develop on Open CASCADE.
You likely noticed that many classes inherit Standard_Transient, directly or via other ancestors, e.g. Geom_Surface or AIS_InteractiveObject, and that in the code that are used as Handle(Geom_Surface).

Open CASCADE tries to never use pointers (at least in API). Whenever it needs a shared object it uses a handle.

Handle is a well known concept often referred to as a smart pointer. The Boost library (www.boost.org) , for instance, has several classes for smart point and, intrusive_ptr seems to be the closest equivalent to OCC’s Handle. Qt (www.trolltech.com) has QPointer.

Handle provides you with a mechanism to automatically refer to an object without a headache of destruction. The underlying object (Standard_Transient descendant) will get destroyed when the last handle pointing to it is destroyed.
In addition to that handle provides safe type recognition and downcasting. Read Foundation Classes User’s Guide for more details.

Unlike Boost or Qt which define smart pointers with templates, Open CASCADE uses two parallel explicit class hierarchies: one deriving from Standard_Transient and another from Handle_Standard_Transient. When CDL extractor generates a header file, or when you are using a macro DEFINE_STANDARD_HANDLE, then you end up with a new handle class. I believe a choice in favor of hierarchy of Handles was done for historical reason when old compilers in 1990es did not support templates well. Presumably, for the same reason TCollection classes are also based on #defines (until in 2002 there appeared NCollection which is template-based).

For the sake of truth, I must note that there is yet another parallel hierarchy – for so called persistent classes inheriting Standard_Persistent. But as they are almost never used directly we can omit them in this article. But everything said here, applies to those classes as well.

To create your handle class you need to use the following macros defined in Standard_DefineHandle.hxx:

DECLARE_STANDARD_HANDLE(class_name,ancestor_name)
DEFINE_STANDARD_RTTI(class_name)

IMPLEMENT_STANDARD_HANDLE(class_name,ancestor_name)
IMPLEMENT_STANDARD_RTTIEXT(class_name,ancestor_name)

For instance :

DEFINE_STANDARD_HANDLE(OCC_UT_Id,Standard_Transient)
class OCC_UT_Id : public Standard_Transient
{
public:
OCC_UT_Id() : myId (0) {}
OCC_UT_Id(const Standard_Integer theId) : myId (theId) {}
Standard_Integer Id() const { return myId; }
private:
Standard_Integer myId;
public:
DEFINE_STANDARD_RTTI(OCC_UT_Id)
};

IMPLEMENT_STANDARD_HANDLE(OCC_UT_Id,Standard_Transient)
IMPLEMENT_STANDARD_RTTIEXT(OCC_UT_Id,Standard_Transient)


Most curios folks might have noticed that DEFINE_STANDARD_RTTI does not really need an argument as it translates into method declaration inside the class, but it’s kept for better consistency ;-).

Until 6.3.0, the Open CASCADE CDL extractor generated explicit code for handle (e.g. Handle_Geom_Surface.hxx). I have recently sent a modified version thereof to the folks in the company so that it now generates macros-using header making code cleaner. May it eat own food.

Another note. On the forum I saw people using macros IMPLEMENT_STANDARD_RTTI along with IMPLEMENT_STANDARD_TYPE, IMPLEMENT_STANDARD_SUPERTYPE,
IMPLEMENT_STANDARD_SUPERTYPE_ARRAY, etc trying to imitate CDL extractor (what it puts into drv subdirectory). Don’t bother with that, folks. IMPLEMENT_STANDARD_RTTIEXT will do all the work for you, and you code will be cleaner.

(to be continued)

2008-11-19

My first blog post. Introduction

This is my first experience as a blogger. Ever. No idea if it will work out or not. Let's see together.

I had been working at Open CASCADE for 7 years till 2004, before moved to Intel where I am now. These were fantastic years, that gave me invaluable experience in software development and project management, customer relationship, and working across multiple geos and cultures.

My path started in 1997 as a software engineer in the CAD Data Exchange team, and this proved to be lucky as it allowed to learn much of the OCC code, as data exchange employs multiple OCC algorithms. If you ever happened to look into the source code of IGES, STEP, or Shape Healing modules you have probably noticed either my full name there (e.g. in IGESToBRep_IGESBoundary.cxx) , or an acronym 'rln' (e.g. in ShapeFix_Wire.cxx). Every Open CASCADE employee has an acronym, widely used internally (I remember meeting minutes containing something like "Attendees: ABV, PDN, GKA, SMH, SZV, ..."). RLN was mine. By the way, it was funny not to find this system in Intel which is overwhelmed with TLAs (Three Letter Acronyms), people use first names here (sometimes with a first letter of a last name - for example, Roman S - if there are several Romans in context).

I'm still in quite close touch with the Open CASCADE team, we are friends with many guys there. Even at Intel from time to time I dive into OCC to check what is new there, and take a chance to develop something(a few weeks ago I prototyped a framework for multi-threading in OCC).

So why a blog ? What is it for ?

- To share my inspiration. Sometimes I cannot resist to reply being caught by some question on the forum or to break for a lunch in the middle of some started prototype. Can you imagine a crazy guy who would develop a corporate product at home because his job did not imply his doing that in working hours? Well, that was about me in 2001 when I switched to full time management. Despite all its deficiencies, I believe OCC is a great product but substantially undervalued, imho. It does not have recognition it could have.
- To share knowledge and help others. There are people struggling on the forum asking same questions again and again, which I could answer in a matter of seconds. Reference documentation is not always enough, sometimes an overview would be a better option.

Is there anything in it for me ? First off, to learn more about who and how uses it. Visiting customers in the past, I saw impressive OCC-based apps so that I was ready to shout 'how did you do that ?'. So, if people would like share their tricks, I will be happy to learn with others. Software skills are important to maintain.

I am thinking to put my notes into the form of short (or not so short) articles that would be interesting for a community. So, if you already have some particular interest please advise what you would like to see.

A few clarifications:
- This blog is not supposed to be a one-way channel. Comments are encouraged and appreciated. Opinions and experiences are welcomed. If anyone will be ready to add his/her own article, then it will be great.
- I don't know all tiny details of OCC (and doubt there is a single person to meet that expectation). There are shadow areas, which I am not ready to comment upfront. But I can dig into it or share some insights as needed. Again, if there are people with deeper knowledge, their contribution will be invaluable.
- And yes, this blog is my personal initiative, in no way is it sponsored by Open CASCADE or any other parties.

Thank you.
Roman