Standard allocator interface.
As you know, Open CASCADE has its own memory allocation mechanism, which entry points are Standard::Allocate() and Standard::Free(). They forward (de-)allocation requests to a current memory manager, which can either be a). default system allocator (if environment variable MMGT_OPT=0), b). own Open CASCADE's (MMGT_OPT=1), or c). Intel TBB (MMGT_OPT=2).
Most Open CASCADE classes (e.g. all which are defined in .cdl files) redefine operators new and delete to use Standard::Allocate() and Standard::Free() respectively – look at any .hxx file.
Using common memory allocation mechanism allows to decrease memory footprint and/or increase performance, especially when using TBB allocator (in multi-threaded apps). However, you may easily miss this advantage in your application in the following typical cases:
1. You dynamically allocate objects of your classes and their new/delete operators do not use Standard::Allocate()/::Free().
2. You use standard containers (std::vector, std::map, std::list,...).
#1 is easily addressed by redefining new/delete operators in your base class(es) in a way similar to OCC.
#2 is more tough, as standard collections by default use standard allocator (std::allocator<T>). So all auxiliary elements (e.g. list nodes) are allocated outside of OCC-governed memory allocator.
This was until now. Hereby I would like to share a code that implements the standard allocator interface as defined by the ISO C++ Standard 2003 (and also conforming to a recently published C++11). Download Standard_StdAllocator.hxx.
This is a pure header implementation, so no .cxx files and linking with libraries is needed; just copy to your project and start using. Implementation is derived from Intel tbb::tbb_allocator, which itself just follows the C++ standard requirements.
The file is intentionally made copyright-free and put into a Public domain, the most permissive way. I also hope that the OCC team will be willing to pick up this file and integrate into OCC.
The simplest example can be as follows:
typedef Standard_StdAllocator<void> allocator_type;
std::vector<TopoDS_Shape, allocator_type> aSVec;
TopoDS_Solid aSolid = BRepPrimAPI_MakeBox (10., 20., 30.);
aSVec.push_back (aSolid);
std::list<int_Shape, allocator_type> aList;
aList.push_back (1);
There is also a unit test (download Standard_StdAllocatorTest.cxx). The code is based on Qt test framework and should be self-explaining to port to any other test framework.
Those who are curious enough may offer similar standard allocator implementation for NCollection_BaseAllocator which is used in NCollection containers. It is as straightforward as this one.
12 comments
Thanks Roman for this very interesting post and your contribution. However, it seems there is an issue with the repository you used. I'm able to download hxx/cxx files, but they appear to be some kind of html code, nothing to do with C++ code.
ReplyDeleteHi Thomas,
ReplyDeletePlease just go to the page following the url's and click the Download buttons. That should work.
Otherwise - download from the OCC thread http://www.opencascade.org/org/forum/thread_22234/.
The referred files (Standard_StdAllocator.hxx ...) are broken.
ReplyDeleteAs mentioned above, click the links. Then push the download buttons.
ReplyDeleteHi Roman, there is a compile error on OSX/gcc-4.2.1 (see https://github.com/tpaviot/oce/issues/204)
ReplyDeleteHi Thomas, thanks for reporting this and sorry for the trouble. I only verified compilation with VS2008 as was traveling with a notebook.
ReplyDeleteWill try to look into the issue and provide a fix.
Roman
The fix has been pushed to the OCE repositoy.
ReplyDeleteBy the way, you write that "Using common memory allocation mechanism allows to decrease memory footprint and/or increase performance". What should we expect from your patch in terms of performance/memory footprint? Did you measure any significant improvement?
Thank you and Denis Barbier for fix and integrating it!
ReplyDeletePerformance and memory footprint benefits heavily depend on a workload where both OCC and standard mechanisms are mixed. I have not created a synthetic case to show it but the following explanation should give an idea.
If the code intensively uses both, then there can be extra footprint given that standard containers allocate memory outside of OCC mechanism (when using OCC or TBB allocator), which meanwhile can hoard memory without using it.
Performance penalties (from standard allocation) may come from two sources: a). fragmentation, which may appear during intensive allocation/deallocation of objects via standard allocator; b). multi-threaded environment where standard allocator uses a central coarse-grain lock causing threads to wait for each other. When using TBB allocator both issues are addressed, when using OCC - #a is addressed, while #b remains.
Hi Roman,
ReplyDeleteI have been stuck with this problem since past two weeks - All I wanted to do was to create a simple cylindrical face.
So, the way I chose to do this is create a Solid cylinder and extract the cylindrical face.
Then I tried some simple checks and I notice that the Wire order is not proper - how is that possible - Opencascade is creating something that is invalid to start with !!!!!
Could you please help me out with this - I will really appreciate any time you spend on this. I need this real desparately, I tried seeking help on the forum, raised a bug with opencascade but no help yet.
// code
double radius = 2;
double height = 1600;
gp_Pnt P(0, 0, 0);
gp_Vec V(0, 0, 1);
V.Normalize();
TopoDS_Solid solidCylinder = kernel->CreateSolidCylinder(P, V, radius, height);
// explore the solid and gets its faces
//kernel->AnalyzeFace(cylface, 0, true);
TopoDS_Face cylindricalFace;
for (TopExp_Explorer it(solidCylinder, TopAbs_FACE);
it.More(); it.Next())
{
const TopoDS_Face &aFace = TopoDS::Face(it.Current());
const Handle(Geom_Surface) &aSurface = BRep_Tool::Surface(aFace);
if (aSurface->DynamicType() == STANDARD_TYPE(Geom_CylindricalSurface))
{
cylindricalFace = aFace;
break;
}
}
for (TopExp_Explorer it(cylindricalFace, TopAbs_WIRE);
it.More(); it.Next())
{
const TopoDS_Wire& aWire = TopoDS::Wire(it.Current());
double precision = 1e-04;
ShapeAnalysis_Wire saw(aWire,cylindricalFace, precision);
if(saw.CheckOrder())
throw exception("Not expecting this");
}
//code ends
Hi Kapil,
ReplyDeleteWhy wouldn't you just do this straightforward:
TopoDS_Face aFace = BRepBuilderAPI_MakeFace (new Geom_CylindricalSurface (gp_Ax3 (P, V, Vx), radius), 0, 2 * PI, 0, height); //Vx is perpendicular to V and defines X axis
Hope this helps !
Roman
Thanks a ton for replying.
ReplyDeleteThe reply might be a bit long but please bear with me.
So, a bit on what I am trying to do
I want to create a Cylinder *face*, triangulate it,
cut it with other *surfaces* (planar, cylindrical, conical, ...).
As a first step
Step 1) I create a Cylindrical face and then
bound it using a bounding box.
Step 2) The way I do it is I create a bounding box with the given bounds and then complement the bounding box and then subtract the complemented box from the face I created in step 1.
** I notice that if I create even a simple planar face in step 1 and then perform step 2 on it - it gives me a face with an improper wire order (so wither there is something seriously wrong with the boolean cut operation or with the code that I have written) I have pasted the code at the end (snippet 1)
Step 3) Triangulation
If I use the API that you suggested I observe that
it fails while triangulating for the case where I create a complete cylinder (0 to 2*PI) for other value it behaves fine. No clue why that should happen
--
The only thing that robustly worked so far is to create the cylindrical surface using BRepPrimAPI_MakeRevol. The only issue I faced with this API is it always creates an outer cylinder ((surface geometry) and sometimes I want to reverse it - I could not figure out a way to reverse the sursurface (->VReverse) as well as modify the topology so as to make sure that the inside and outside are consistent with what they were before the surface reversal.
I am not sure if any of that made sense to you but I have a very strong feeling that there is somewhere some bug in OpenCascade -
As a very simple thing - I created a box and the wires on the box come out as *out of order* to start with. Why would that happen?? (Code snippet 2)
Is that I am doing something seriously wrong??
And though I agree that this the way I listed above is a very round about way - but I would still want to know why is the order coming wrong. I would be more than happy if you throw some light on it.
Code snippet 1:
test_face_trimming_with_bounding_box(TopoDS_Face aShape)
{
gp_Pnt min(-50, -50, -50);
gp_Pnt max(50, 50, 50);
gp_Pnt minO(-5000, -5000, -5000);
gp_Pnt maxO(5000, 5000, 5000);
BRepPrimAPI_MakeBox inner(min, max);
inner.Build();
TopoDS_Solid aInnerBox = inner.Solid();
BRepPrimAPI_MakeBox outer(minO, maxO);
outer.Build();
TopoDS_Solid aOuterBox = outer.Solid();
BRepAlgoAPI_Cut aSub(aOuterBox, aInnerBox);
aSub.Build();
const TopoDS_Shape& aOutBoxWithHole = aSub.Shape();
TopoDS_Face splitFace;
BRepAlgoAPI_Cut aCut(aShape, aOutBoxWithHole);
aCut.Build();
for (TopExp_Explorer iter(aCut.Shape(), TopAbs_FACE); iter.More(); iter.Next())
{
//expecting a single face
splitFace = TopoDS::Face(iter.Current());
}
}
Code snippet 2:
test_bounding_box()
{
gp_Pnt min(-50, -50, -50);
gp_Pnt max(50, 50, 50);
BRepPrimAPI_MakeBox aBox(minPt, maxPt);
aBox.Build();
const TopoDS_Solid& aSolid = aBox.Solid();
for (TopExp_Explorer it(aSolid, TopAbs_WIRE);
it.More(); it.Next())
{
double precision = 1e-04;
ShapeAnalysis_Wire saw(aWire, *aParentFace, precision);
if (saw.CheckOrder())
{
throw exception("Wire not ordered");
}
}
}
Thanks again.
Hmm I didn't know about this.
ReplyDelete