Box2D Forums

It is currently Fri May 24, 2013 6:09 pm

All times are UTC - 8 hours [ DST ]




Post new topic Reply to topic  [ 97 posts ]  Go to page Previous  1 ... 3, 4, 5, 6, 7, 8, 9, 10  Next
Author Message
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 5:28 am 
Offline

Joined: Mon Jan 07, 2008 10:51 am
Posts: 1911
ElectroDruid wrote:
a) This is my short-term hacky fix - I set the neighbours vector to have an initial capacity of 256, which seemed like plenty to me - even then, pushing stuff onto the vector isn't as cheap as I'd like

Really? I'm no performance expert, (or C++ expert), but I would have thought if you don't increase the capacity then nothing could be faster. I'd have thought it essentially boils down to
*end++ = data;

ElectroDruid wrote:
b) I think I'm going to reuse a fair bit of the stuff that gets created in applyLiquidConstraint. There are a lot of temporary lists of things, and although it's a bit of memory hit to keep them around all the time, I suspect the performance increase would make it worthwhile.

If you have lots of tempories, consider using b2StackAllocator, which was written for this. b2BlockAllocator may be helpful too.

ElectroDruid wrote:
c)

Fair enough, I was assuming they were vectors of b2Vec2.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 3:45 pm 
Offline

Joined: Thu Jan 22, 2009 3:37 pm
Posts: 8
I wrote a little constant-size container to replace the vector, use "lvector<int, nParticles>" instead of "std::vector<int>":
Code:
template<typename T, int msize> class lvector {
   // Hastily thrown together limited size, non-allocating vector. Should only be used on POD types due to lack of ctor/dtor calling
   // With "exceptions"
   T data[msize];
   unsigned int csize;
public:
   lvector() : csize(0) { }
   void push_back(const T& val) {
      if (csize >= msize) {
         throw "Vector full";
      }
      data[csize++] = val;
   }
   void pop_back() {
      if (size == 0) {
         throw "Vector empty";
      }
      --size;
   }
   T& at(unsigned int where) {
      if (where >= csize) {
         throw "Out of range";
      }
      return data[where];
   }
   T& operator[](unsigned int where) {
      if (where >= csize) {
         throw "Out of range";
      }
      return data[where];
   }
   const T& at(unsigned int where) const {
      if (where >= csize) {
         throw "Out of range";
      }
      return data[where];
   }
   const T& operator[](unsigned int where) const {
      if (where >= csize) {
         throw "Out of range";
      }
      return data[where];
   }
   unsigned int size() const {
      return csize;
   }
   unsigned int max_size() const {
      return msize;
   }
   void clear() {
      csize = 0;
   }
};

The exceptions are all strings, which could be much improved, although it shouldn't matter because the vectors shouldn't overflow.
Also, instead of
Code:
float* vlen = new float[neighbors.size()];
just use
Code:
float vlen[neighbors.size()];
and remove the delete, to avoid allocations.
The "neighbors" array can also be kept in the class instead of allocated on the stack each time it is needed, although I don't know if this gives an improvement.
In any case, applyLiquidConstraints() runs twice as quickly with these changes applied.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 4:38 pm 
Offline

Joined: Mon Jan 07, 2008 10:51 am
Posts: 1911
Ok, seeing the previous post I decided to actually look at your code, rather than give general hints.

I'm basically having a hard time believing that any sort of custom class is going to be better than std::vector. The given one is basically a wrapped array, you might as well just be using the array?

Anyway, given what you said about perfomance, I see two main problems with your code, that I don't think you would have fixed given the previous advice.

Firstly, you have this:
Code:
for(int i = 0; i < nParticles; i++)
{
   // Populate the neighbor list from the 9 proximate cells
   std::vector<int> neighbors;
   ...
   neighbors.clear();
}

This is no good. Because you've declared neighbours inside the loop, it will be destroyed and recreated each time, and adding a capacity will not help. You want:
Code:
std::vector<int> neighbors(256);
for(int i = 0; i < nParticles; i++)
{
   ...
   neighbors.clear();
}


Secondly, as goffrie points out, you are allocating vlen every iteration too. I'd replace it with std:vector as well:

Code:
std::vector<int> neighbors(256);
std::vector<float> vlen();
for(int i = 0; i < nParticles; i++)
{
   ...
   // Particle pressure calculated by particle proximity
   // Pressures = 0 if all particles within range are idealRad distance away
   vlen.resize(neighbors.size());//Ensure vlen is the same size as neighbors
   ...
   neighbors.clear();
}


NB: Apparently Visual Studio de-allocates on a .clear(). If you are testing on this platform, you should check for that, as it will negate any benefits.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 4:58 pm 
Offline

Joined: Thu Apr 24, 2008 2:09 pm
Posts: 91
All of your points are valid and useful, Boris. I'm working on the code as we speak, and I'd spotted some of these, but not all of them. I'm definitely in agreement, having looked into it further, that the STL is pretty close to being as optimal as I'd thought it was, and that the performance problems are stemming from me using it wrongly.

My main line of inquiry at the moment (on top of all the stuff you mentioned) is that as well as neighbours and vlen being clumsy in their scoping, I'm not convinced that building that neighbours list for every particle is such a great idea in the first place, at least not in the way I'm building it at the moment. It makes life easier to iterate through the neighbours list for the bulk of the algorithm, but it's expensive to build the list in the first place - I'm spending a lot of time just grabbing ints from cells in the hashGrid and push_back'ing them into neighbours. I'm looking at a few options, but my main hunch right now is that if I can build this using linked lists (stl::list, presumably) rather than vectors, I can get basically the same outcome by just fixing up a few pointers rather than constantly copying values from one vector to another. I need to try a few things out, but I will keep people updated on my progress, if it's of interest.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 4:58 pm 
Offline

Joined: Sun Sep 23, 2007 2:35 pm
Posts: 803
goffrie wrote:
Also, instead of
Code:
float* vlen = new float[neighbors.size()];
just use
Code:
float vlen[neighbors.size()];
and remove the delete, to avoid allocations.

This is not technically valid C code, as stack-allocated array sizes must be compile-time constants.

However, I'm pretty sure gcc allows it (not sure about Microsoft's compiler), so you might get away with it anyways.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Thu Jan 22, 2009 6:07 pm 
Offline

Joined: Thu Jan 22, 2009 3:37 pm
Posts: 8
ewjordan wrote:
goffrie wrote:
Also, instead of
Code:
float* vlen = new float[neighbors.size()];
just use
Code:
float vlen[neighbors.size()];
and remove the delete, to avoid allocations.

This is not technically valid C code, as stack-allocated array sizes must be compile-time constants.

However, I'm pretty sure gcc allows it (not sure about Microsoft's compiler), so you might get away with it anyways.

I didn't know that; gcc does allow it. (Compiling with -pedantic-errors, I see that it complains about it not being ISO C++)


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Sun Jan 25, 2009 5:38 pm 
Offline

Joined: Thu Apr 24, 2008 2:09 pm
Posts: 91
Okay, so I've played with a few more optimisations, but it's becoming clear quickly that currently the big performance hit is in the custom hashGrid broadphase stuff, and there's no easy way to solve it. I've considered a few techniques (and tried a couple, without much success), before I realised that I've been barking up the wrong tree, because really that custom broadphase is the thing we're wanting to remove anyway, and replace with the one that's running in Box2D already. I'm prepared to give this a go, but I'm so inexperienced with the internals of Box2D that I'm not even sure which questions I should be asking to get started. I'll have a go, though, and see if anyone can provide food for thought :)

Earlier in the thread, ewjordan talked about how he imagined these fluid particles might work in the engine:

Quote:
What I had considered before was creating a new type of shape that represented the neighbor range for a fluid particle, stored any dynamic SPH per-particle variables, and reported a special type of fluid contact which would be passed along to the SPH solver. If that's the route you want to take, you need to figure out a lot of stuff, for instance SPH "contacts" can't be solved pair by pair like normal contacts (pressures depend on all of a particle's neighbors, and pairwise interactions depend on pressures), so you've got to store away some of the data, figure out the best time to process the group, etc, or maybe decide to use the old values, or something like that. It gets a bit messy, esp. when you realize that you have to also integrate a whole new contact type, which means digging into the internals to figure out exactly what need changing.


This sounds quite complex, but I'm not 100% convinced about how much of it is necessary. It seems to me that having something that behaves like a sensor shape (representing the neighbour range, but otherwise not doing any rigid body collision) is a reasonable starting point. With regards to the specifics of the SPH fluid constraint solving stuff, I'd be happy as a first pass to stick with something external to the core of the engine for now, so that there can be some flexibility in how users work out the specifics of the maths to tune things for the right behaviour. Perhaps eventually we can work out some universal system for solving the SPH and just allow the user to tweak a few values based around ranges, density, viscosity, etc, but for now all I'm thinking about is how these particles should behave with regards to the broadphase.

So, what would we need to do for basic stuff like flagging a shape in a way that says "this is a sensor, but it's one I'm using for a fluid particle?". The main thing as far as I can see is simply that a "fluid sensor" should register contacts with other fluid contacts as normal, but:

1 - Should possibly ignore contacts with shapes which aren't flagged as also being fluid sensors (not sure of exactly how we'll handle interactions between fluids and non-fluids yet, so I don't know how applicable this is)

2 - Should avoid creating a pair when it detects a contact with another fluid sensor. The fluid simulation doesn't need or use pairs, and given the high amount of overlap between particles, this is the first thing that makes Box2D assert when dealing with any notable number of particles.

Now, I don't really understand yet how fundamental pairs are to the engine. So I don't know if just turning off the bit of code that generates pairs for fluid sensors is a reasonable thing to do. Is anyone able to explain how the Box2D broadphase works, exactly? How and why it generates pairs, and what they're used for? Whether it's reasonable to just not generate pairs for certain types of shapes if we know we won't need them, or whether that's nonsensical - and if it is nonsensical, why?


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Sun Jan 25, 2009 6:12 pm 
Offline

Joined: Mon Jan 07, 2008 10:51 am
Posts: 1911
As I understand it. Pairs are created whenever two proxies (shape AABBs) overlap. If the shapes are filtered, then a pair is still generated, but it's the null pair. I think in principle it's possible to avoid this overhead, but it isn't done yet.
I'd keep particles separate from the engine for now, but you can reuse the broadphase code quite easily by creating a new broadphase, and passing it proxies wrapping each particle.

You should probably expect a lot of your processor time to be spent in the broadphase, this is fairly unavoidable.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Sun Jan 25, 2009 6:46 pm 
Offline

Joined: Thu Apr 24, 2008 2:09 pm
Posts: 91
I'm not sure what you mean by a "null pair". Certainly by (for instance) taking the sensor test, making the balls also be sensors, and turning on "draw pairs" in the debug stuff, it still draws the pairs. Perhaps the pairs are not used for doing any rigid body collision response, but they're still generated.

What's involved in creating a new broadphase, and is there any overhead for doing so? Right now, the code runs two collision systems (Box2D's one, and the grid-based one bolted onto the top, which may or may not be considered a broadphase depending on your definition), and it seems strange to pay the price for both of them.

For the record, I'm currently paying something in the region of 35% of the processing time in the Box2D broadphase (mostly processing particles which get handled in the custom code anyway), but I'm paying at least another 35% on top of that trying to manage the hash grid for the SPH processing. Trying to find a way to turn off the Box2D stuff I'm not using seems counter-intuitive. It would make much more sense to use Box2D to tell me which particles are close to any given particle, rather than waste CPU time trying to figure that out myself. It's just that if I try to do so, those pairs are a killer.


Top
 Profile  
 
 Post subject: Re: Fluid Simulation
PostPosted: Sun Jan 25, 2009 7:35 pm 
Offline

Joined: Sun Sep 23, 2007 2:35 pm
Posts: 803
You know, I think simply using sensors for the fluid particles could work just fine. Looking back, the main reasons I didn't do this were:

1) With the JBox2d testbed's software renderer, it's way too expensive to draw all the sensors (like, 10 fps vs. 50 fps expensive). The solution here is easy, though I didn't try it at the time (I forget why, esp. since I ended up doing this with the solid particles anyways) - just turn off drawing of the entire sensor and replace it with a specialized fluid rendering step.

2) Fluid-solid interactions need to be handled properly if the particles don't have solid cores.

3) There are a lot of pairs, so the maxPairs setting needs to be set pretty high (not a problem at all).

4) Sensor processing was a major hassle back when I wrote that code, something which is vastly easier now thanks to all of BorisTheBrave's work. Now it's a simple matter to read off a list of currently touching particles (at least in the C++ engine).

But I think just taking a sensor based approach could work very well, and that might be the best way to avoid broadphase duplication. What you'd do is just make the sensors have half the size of the neighborhood that you're looking at. Storing some of the SPH fields on each particle might help, too, as the pressure calculation step could then proceed pair-by-pair instead of particle-by-particle (which doubles the number of operations, hitting each pair twice). There might even be some things that could be stored away to make this all more cache friendly, but that would be a later optimization.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 97 posts ]  Go to page Previous  1 ... 3, 4, 5, 6, 7, 8, 9, 10  Next

All times are UTC - 8 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Powered by phpBB® Forum Software © phpBB Group