Proposal: OGRE_THREAD_SUPPORT == 3
Posted: Fri Oct 02, 2009 10:36 am
My experience of multicore programming with Ogre has improved over the last couple of years, and I realised a while ago that my original approach, OGRE_THREAD_SUPPORT==1 where resource management is fully threaded, was hopelessly naive. It required too many locks, and also that the rendersystem was multi-threaded. OGRE_THREAD_SUPPORT==2 improved that by not requiring that the rendersystem was threaded, and just doing disk I/O in the background, but still, the whole process is still driven within the Resource class, which means the locks are still in place on Resource and by association SharedPtr and a bunch of other classes too.
More recently I've been trying out alternative approaches in client projects, where I've built OGRE with OGRE_THREAD_SUPPORT disabled, and instead I've done my own, very much more specific threading and only interacted with Ogre in a single thread. I've worked very much in a data-driven model such that data is ring-fenced and passed between threads with minimal locks - data is generally not visible within more than one thread at a time (unlike the Resource approach, where even with OGRE_THREAD_SUPPORT==2 the resource instance & state is shared between the threads). This works very well, requires fewer locks, is easier to debug and is faster.
Recently, I've sneaked this approach into the Ogre core too. The new terrain component actually uses this approach - even though it's currently operating within OGRE_THREAD_SUPPORT==2, it doesn't do any data sharing between threads and instead simply has a hand-over of data via WorkQueue. I even created software, copyable versions of VertexDeclaration et al so that I could build them in another thread separately from the rendersystem. Therefore, if WorkQueue was still operating in a threaded manner, and I resolved a couple of issues with resource path lookups, I could build Ogre with OGRE_THREAD_SUPPORT disabled and still get threading on Terrain.
So, here's what I propose:
Thoughts? As I say, I've used this approach in client projects with OGRE_THREAD_SUPPORT=0 (basically implementing my own version of WorkQueue and container structures externally, and not using the resource paths to locate files) and it's worked really well; I'd like to standardise it. It's faster an simpler at the same time.
More recently I've been trying out alternative approaches in client projects, where I've built OGRE with OGRE_THREAD_SUPPORT disabled, and instead I've done my own, very much more specific threading and only interacted with Ogre in a single thread. I've worked very much in a data-driven model such that data is ring-fenced and passed between threads with minimal locks - data is generally not visible within more than one thread at a time (unlike the Resource approach, where even with OGRE_THREAD_SUPPORT==2 the resource instance & state is shared between the threads). This works very well, requires fewer locks, is easier to debug and is faster.
Recently, I've sneaked this approach into the Ogre core too. The new terrain component actually uses this approach - even though it's currently operating within OGRE_THREAD_SUPPORT==2, it doesn't do any data sharing between threads and instead simply has a hand-over of data via WorkQueue. I even created software, copyable versions of VertexDeclaration et al so that I could build them in another thread separately from the rendersystem. Therefore, if WorkQueue was still operating in a threaded manner, and I resolved a couple of issues with resource path lookups, I could build Ogre with OGRE_THREAD_SUPPORT disabled and still get threading on Terrain.
So, here's what I propose:
- Add a new OGRE_THREAD_SUPPORT option, 3
- This results in OGRE_MUTEX, OGRE_LOCK_MUTEX etc all becoming no-ops just like in OGRE_THREAD_SUPPORT=0 - so SharedPtr, Resource etc are all not thread safe
- WorkQueue changed to use different macros, e.g. OGRE_WQ_MUTEX, which are the only ones enabled when OGRE_THREAD_SUPPORT=3
- Resource prepare() when OGRE_THREAD_SUPPORT=3 is 'deferred' rather than 'threaded'. That is, we make all changes to Resource in the main render thread, and simply push a request on to WorkQueue to read / optionally pre-process the data in a thread. This thread cannot access any of Ogre in a threadsafe manner, it can only use software structures.
- All data exchanged between the threads must be totally encapsulated in the Request and Response, so that it is self-contained and ownership can be passed cleanly between threads.
- The main challenge here is the resource path & archive system - we need to make these lock-free (locking them will start to bleed into other areas and we'll end up with too much again). One option would be to have archive instances & resource paths copied to each thread, with changes queued up / logged and picked up when each thread needs them.
- So from a resource point of view, this is a lot like OGRE_THREAD_SUPPORT==2, except that Resource isn't threadsafe (actually, nothing is!), because it's only accessed in the main thread.
- We make this the default when Boost / POCO / TBB are detected
- Over time we expand this data-driven, lock-free model for future threading within Ogre
Thoughts? As I say, I've used this approach in client projects with OGRE_THREAD_SUPPORT=0 (basically implementing my own version of WorkQueue and container structures externally, and not using the resource paths to locate files) and it's worked really well; I'd like to standardise it. It's faster an simpler at the same time.