Not sure why i'm getting slower results compared to Processing


#1

Hey guys, i’ve been using processing for a couple years now and at this point i’m really not satisfied by the speed i get from my projects.
I’ve been aware of openframeworks for quite some time and people all over the internet seem to be suggesting it’s faster than processing so i decided to give it a try.
The first project i’m trying to implement in OF is a particle system, since, in processing, it gets really low framerates after 8-9 hundred particles. It has some basic physics including some force interactions and seperation among particles which is what slows things down so much (since it needs an extra nested loop in each iteration to check for distances). I know that there are optimisations that can help here (eg quadtrees) though i’m still curious about what results i can get with OF.
Anyways, after dealing with all the OF/c++/ide weirdness i managed to create a simple particle class and things seem to work fine but when i add the nested loop in the particle update function it gets really slow really fast (even at 100 particles).

In case anyone can help here’s a drive link with the source files:
https://drive.google.com/open?id=1o4c-BDaaWaOix22WMzcVR9gPd1Bhh-oO
Thanks!


#2

Hey!

Did you try to use

ofSetFrameRate(30);

like said in this post ( OF and Macbook Pro 2018? ) by @zach L?

Otherwise, the code seem to be pretty good (to me), maybe try using a pointer on the other particle in the update function?

void Particle::update(Particle* other) {
	if (ofDist(pos.x,pos.y,other->pos.x,other->pos.y) < sepLim) {
		cons.push_back(other->pos);
	}
	pos.x += ofRandom(-1, 1);
	pos.y += ofRandom(-1, 1);
}

and in the update function :


//--------------------------------------------------------------
void ofApp::update(){
	for (int i = 0; i < particles.size(); i++) {
		for (int j = 0; j < particles.size(); j++) {
			particles[i].update(&particles[j]);
		}
	}
}

Look at this excellent code by junkiyoshi for inspiration, as it is more advanced.
Essentially, the draw function takes the whole array as an input parameter, then checks the distance to the rest of the particules without pushing into an array of distances, and draw the circles and lines straight.

Hope this helps.

++


#3

Hi,
As @pierre_tardif00 mentioned (hope you’re doing well Pierre :smiley: ),
use a pointer or a reference in the update, otherwise you are copying the particle objects on each update and that can be really slow. Instead of the pointer, you can also use a reference and that would not change the syntax of the function at all, but will avoid the copy.
In

for (int j = 0; j < particles.size(); j++) {
	Particle other = particles[j];
	particles[i].update(other);
}

you are copying the particles in the particles vector into other, and then the update function will also make a copy, so you are copying twice!.
Make it

for (int j = 0; j < particles.size(); j++) {
	particles[i].update(particles[j]);
}

and in Particle.h change
void update(Particle other) into void update(Particle & other)
and Particle.cpp change
void Particle::update(Particle other) into void Particle::update(Particle & other)
I just added an & which tells to use the parameter as a reference to the original, thus avoiding the copy.

Also, In the particle::update(…) function you are pushing the distance into a vector and you are doing such for each particle so at the end you have a lot of unnecessary info but Then you delete all this in the draw which means that each time you have to realocate memory and that can be slow too. Try just to resize such array as needed, instead of creating/deleting it on each pass. You can also benefit a lot by calculating the distances between each particle just once, as currently you are doing it twice, at least; ie, if you have 2 particles, A and B, when you callA.update(B) you will calculate the distance between these and keep if meets a certain criteria but then you’ll do B.update(A) which is calculating the exact same distance, which you already have. Another optimization is to use the squared distances instead of the “regular” distance, as the latter need a square root calculation which tends to be quite expensive computationally speaking.
Next, the way you are drawing. Take a look at the examples/gl/billboardExample and use bilboarding instead of ofDrawCircle, it will improve a lot the performance.
Last but not least, take a look at ofxSpatialHash, an excellent addon made by chris baker.

Hope all this helps. cheers.

BTW, also, please post the code inline instead of the gdrive link. It would be much easier for you to get help. Just copy and paste the code into de forums text editor, select it and press the </> button from the toolbar, which will format it as code.


#4

just to echo what roy said, I haven’t tested this but that push back will be very slow

 if (ofDist(pos.x,pos.y,other.pos.x,other.pos.y) < sepLim) {
	cons.push_back(other.pos);
}

also, if you do pass by reference and can manipulate both particles in the same function, you could rewrite this:

for (int i = 0; i < particles.size(); i++) {
		for (int j = 0; j < particles.size(); j++) {
			particles[i].update(&particles[j]);
		}
	}

to this:

for (int i = 0; i < particles.size(); i++) {
		for (int j = 0; j < i; j++) {
			particles[i].update(&particles[j]);
		}
	}

which cuts down on the nested for loop alot (if you do a vs b it doesn’t also do b vs a)

also try compiling in release not debug if you can to judge speed. debug can be pretty slow esp with vector stuff, etc.

  • zach

#5

Hey guys! Thanks so much i didn’t expect so quick and well written feedback… you guys are awesome :slight_smile:
First let me say i am a total noob with c++… so far i only read about what was necessary for me to write this first test so your responses have a lot of new info for me. I know this isn’t the best way to go, though i just wanted to make sure at first that it’s worth the effort. Anyways just be aware that i’m only familiar with concepts that c++ shares with java.

@pierre_tardif00 , i tried both of your suggestions though neither seems to work. (i’m probably doing smthn wrong with the pointers though setting the framerate doesn’t do anything)
@roymacdonald , your suggestion definitely works though it’s still slower overall compared to processing.
Zack, i’m not sure what you mean with “if you do pass by reference…the same function”. I’m guessing you mean manipulating pairs of particles outside of the particle class (thus using it more like a data structure). Let me know if i’m correct.

My 2 main questions so far are these:
-In the original processing project the nested loop is inside the particle-update function and for some reason i can’t access the particles vector through the particle class here. This is why i tried writing the nested loop in ofApp::update. Because of that, i just realised that i’ve been updating the particle’s position as many times as there are particles in every iteration the way i’ve written this.
-I probably should just read a bit more about OF but could you explain to me the difference between ofApp::update and ofApp::draw? I figured you can’t use drawing functions inside update so i don’t get why i can’t just update the particles and draw them inside of ofApp::draw. This is the reason i added the confusing cons array, because in processing i used to draw the connections directly inside the nested loop.

I will definetely look up the rest of your suggestions and try these optimisations (probably in processing first :P) so thanks again for being so helpful.
Hope all this makes sense… Cheers!


#6

Hi, can you share the processing code please?

In the following

it means to use a function with an & between the type and the argument name. It is what I suggested to do instead of using a pointer.

you can access the vector of particles inside the particle class, but you’ll need to pass it by reference, otherwise you will create a copy of the vector. it is the same as above.

you can update and draw all inside the draw function, just put everything inside the draw function and ignore the update.


#7

Sure!

// Symmetry in Chaos 2017 - Emergence 1.0
// https://www.facebook.com/symmetryinchaos/

// Input:
// 'a' controls seperation with mouseX
// 's' to toggle adding particles
// 'i' to toggle interaction
// 'd' to toggle repulsion
// 'f' to save frame

ArrayList<Particle> particles;
PVector cntr;
PVector startPos;

boolean stop=false;
boolean valid;
boolean interact=true;

float circleD,centerD,removeD;

void setup(){
  size(720,720,P3D);
  background(35);
  rectMode(CENTER);
  
  cntr = new PVector(width/2,height/2);
  particles = new ArrayList<Particle>();
}

void draw(){
  background(35);
  
  runCircles();
  
  if(frameCount%10==0) println("agnts: " + particles.size(),"frm: " + frameCount);
}

// particles initialisation - addition rate - checks
void runCircles(){
  if(stop==false){
    int a=0;
    while(a<5){
      valid = true;
      startPos = new PVector(random(width),random(height));
    
      // check if starting spot is within another agent's radius, if so find another spot
      for(Particle c : particles){
        circleD = PVector.dist(startPos,c.pos);
        if(circleD<c.radius+2 && circleD>0) valid = false;
      }
    
    // check if starting spot is within a radius from the center of the window
    centerD = PVector.dist(startPos,cntr);
    if(centerD>250) valid = false;
    
    // if its valid create agent
    if(valid) particles.add(new Particle(startPos));
    a++;
    }
  }
  
  // update and display each particle
  for(Particle c : particles){
    c.update();
    c.display();
  }
    
  // remove every particle that is of the bounds of the window
  for(int i=0;i<particles.size();i++){
    Particle c = particles.get(i);
    float removeD = c.radius+2;
    if(c.pos.y>height+removeD||c.pos.x>width+removeD||c.pos.y<0-removeD||c.pos.x<0-removeD){
      particles.remove(c);
    }
  }
}
  
// inputs
void keyPressed(){
  if(key == 's') stop = !stop;
  if(key == 'i') interact = !interact;
  if(key == 'f') saveFrame("output/circ_####.png");
}

void fade(){
  noStroke();
  fill(255/8,255/10);
  rect(width/2,height/2,width,height);
}

class Particle{
  PVector pos = new PVector();
  PVector sep = new PVector();
  PVector speed = new PVector();;
  
  float growRate=1.5;
  float radius=0;
  float mag,sepMag,tarMag,alpha=0;
  float eRadius,rRadius;
  float lim = 200;      // seperation limit

  // constructor
  Particle(PVector p){
    pos = p;
  }
  
  //update position/size/speed/forces
  void update(){
    if(keyPressed & key == 'a') lim = map(mouseX,0,width,50,350);
    
    for(Particle other : particles){
      float d = PVector.dist(pos,other.pos);
      float limit = (radius+other.radius+radius);
      
      // growth stop/interaction/seperation if agents "touch"
      if(limit > d && d>0){
        growRate=0;
        
        // interaction
        speed.add(other.speed);
        mag = map(d,.01,limit,0,.9);
        
        // draw conections if agents are interacting with eachother
        alpha = map(d,.1,limit,150,10);
        stroke(245,alpha); 
        strokeWeight(1.2); 
        line(pos.x,pos.y,other.pos.x,other.pos.y); 
      }
      
      // separation
      if(d < lim && d>0){
        sep = PVector.sub(other.pos,pos);
        sepMag = map(d,0,lim,2,0);
        sep.setMag(sepMag);
        
        speed.sub(sep);
      }
    }
    
    // interact with a target
    PVector desired = PVector.sub(cntr,pos);
    float cD = dist(cntr.x,cntr.y,pos.x,pos.y);
    tarMag = map(cD,0,300,0,40);
    desired.setMag(tarMag);
    if(keyPressed && key == 'd') desired.mult(-1.2);
    speed.add(desired);
    
    // update position/growth
    radius+=growRate;
    
    speed.mult(.5);
    pos.add(speed);
    if(interact) speed.setMag(mag);
    else speed.mult(0);
  }
  
  // draw and move agents
  void display(){
    stroke(245);
    noFill();
    strokeWeight(2);
    
    rRadius = radius*.5;
    rect(pos.x,pos.y,rRadius,rRadius);
  }
}

Oh okay i didn’t think of passing in the vector. I’ll give it a try and i’ll let you know how it goes.
Thanks again!


#8

Happy New year Roy!! - and Zach-.

Well I’m glad it worked in the end, and more experienced guys came to your rescue @hector.

++


#9

I just had a glance at your code without running too deep, but I would suggest: don’t use a massive amount of push/pop on vectors if you need performance and speed. Dynamic memory allocation is the main enemy of performance. Each time you push/pop, under the hood large areas of memory have to be allocated, deallocated, moved and/or reorganized. If you do that each frame, 60 times in a second, times hundreds of iterations, times all the values you need to push/pop it may result lethal for performance. Perhaps you could try to do it “C old style”: instead of vectors you may use oversized arrays together with index variables to keep track of particles and their properties. That way the memory you need for your application gets allocated once and for all at startup then released at exit. To help in keeping things clear you may also arrange each array and relevant indexes and properties in a struct{}.