Since the beta release of our application is coming, I thought it could be interesting to summarize my experiences with OpenCascade on iOS.
First of all, I would like to declare, that I was very satisfied with OCCT, despite all of its problems, it is still the best option for CAD development if you want an open source solution, and in many ways its knowledge competes with other (extremely expensive) CAD kernels.
So here are my experiences with OCCT on iOS:
1. Compilation
It was pretty straightforward to compile OCC. I just had to modify the osutils.tcl WOK files, to generate iOS project files, instead of MacOS project files, regenerate the Xcode projects, and press compile. There were a few trivial compilation issues, coming mainly from C++11 compatibility (such as string syntax), but those were easy to fix. After the release of OCCT7 this will be even easier with the new build system.
2. Performance
Well, that was the most exciting part, and here I made some improvements, that I am going to publish of course. In my application, after a few days of profiling, I identified two major performance bottlenecks:
a.) Polynomial evaluation
The biggest spike in the profiler was the polynomial evaluation (PLib::EvalPolynomial). After reading Roman's great article on optimizing BSpline evaluation (http://opencascade.blogspot.com/2014/05/applying-vectorization-technique...), I found that these techniques could be applied on ARM too, since the architecture has the so called NEON, that is a 128bit floating point SIMD (single instruction multiple data) unit. So finally I reimplemented the EvalPolynomial function, and after a few days of experimenting, it turned out, that this algorithm could benefit from C++'s dark magic: template metaprogramming. It turned out, that the implementing the Horner algorithm using template metaprogramming is not a very hard task. The benefits were huge: I was able to get 10-12x performance improvement in polynomial evaluation, in real life scenarios! The solution is a little bit hacky, since template metaprogramming generates static programs, but the parameters are varying, so I pregenerated many many possible Horner algorithm (from 0 degree to 16 degree, from 0th derivative to 8th derivative) like this:
EvalPoly,
EvalPoly,
EvalPoly,
EvalPoly,
EvalPoly,
EvalPoly,
where the signature of this template metaprogram is this:
template
void EvalPoly(Standard_Real x0, Standard_Real *coeff, Standard_Real *resultVec) {…}
And the actual implementation looks like this:
void EvalPoly(const Standard_Real U,const Standard_Integer DerivativeOrder,const Standard_Integer Degree,const Standard_Integer Dimension,Standard_Real& PolynomialCoeff,Standard_Real& Results)
{
if (Degree > MaxDegree || Dimension > MaxDimension || DerivativeOrder > MaxDerivativeOrder)
_EvalPolynomial(U, DerivativeOrder, Degree, Dimension, PolynomialCoeff, Results);
else
evalPolyFuncs[DerivativeOrder + MaxDerivativeOrder * Dimension + MaxDimension * MaxDerivativeOrder * Degree](U, &PolynomialCoeff, &Results);
}
So if there is no pregenerated algorithm, we fall back to the original implementation, otherwise we call the template metaprogram. Since the output of these metaprograms are like lots of embedded SIMD instructions (like multiply_add(a, b, multiply_add(c, d, …)) ), this leaves room for the compiler for insane optimizations, leading to great performance. Despite I am pretty sure, that such a solution will never make it to the main branch of OCCT, of course I will publish this implementation also. Now it is ARM64 only, but basically only one line has to be changed to use it on Intel, or on other architecture that supports SIMD instructions. Yes, I know, template metaprogramming is dark magic, and produces write-only code, but in this case, it really worth it.
b.) Locks
That was one of the most surprising experience I had during the development. It turned out, that atomic operations on ARM are not as effective as on Intel CPUs, resulting that even locking an unlocked mutex can be expensive. That led to big performance bottlenecks on iOS, since every BSpline curve/surface evaluation involves locking and unlocking a mutex, and that led to spending 30-50% of CPU time in locking/unlocking during meshing and intersection calculations. Yuck… And the worst part is, that you can not really do anything about this, because the BSpline caching requires locking. Finally I ended up simply removing mutexes from those classes, and disabling parallelism. But I am still struggling with this one, because now parallelism would be super useful for boolean operations, but on the other hand I would slow down other parts of the application. I will try to disable caching, and run some performance tests, maybe that is going to be the solution (but I don't know how much does caching matters, is there any measurement for this?)
I also tried to replace mutexes with GCD (Grand Central Dispatch) synchronization primitives, but it was also slow.
These two are the only significant changes I made in OCCT.
3. Bugs
I have not found many bugs in OCCT (at least not many that could not be worked around), but there was one that is really annoying, bug #25563 (pretty funny, I can't access it now, why?). It can be a compiler bug in Clang, but can be a bug in the code as well. The problem is that if you compile AdvApp2Var_ApproxF2var.cxx with optimization level >=2, it will crash. And the problem with this, is that many algorithms are based on this. I spent two days trying to separate the issue without any success, mainly because that code for me is impossible to understand :( But this can be worked around by simply setting O1 optimization level to that file.
4. Allocations
Thats just a note: it would be sometimes very useful to be able to provide your own allocator for some classes, because sometimes I found that allocations take lots of time in some algorithms, especially in (GCPntsQuasiUniform/GCPntsTangential)_Deflection. Probably this is also not an issue on Intel CPUs.
5. Overall
OCCT has lots of legacy, but they are working really hard to get rid of them. They are pretty responsive, and if you file a bugreport, they will take a look at it. The whole library is getting better every week, and the development process and communication improved a lot since OCCT went LGPL, so kudos to the OCCT team, and keep up the good work.
PS.: I have attached the template metaprogram, if someone wants to take a look, get it here: https://www.dropbox.com/s/qij7rdvuuirsh00/eval_poly_template_metaprogram....
I will make the whole branch available next week, but I think I have summarized everything that could be interesting in this post. I just have to make some cleanup. If anyone wants to compile OCCT on iOS feel free to contact me.