Through code profiling, I have found the Math.sqrt function specifically to be a major bottleneck in a large doubly-nested loop that runs every timestep in my program. Is there any way to improve its performance? Should I inline some kind of iterative calculation, or lookup table-based calculation?
Any help would be greatly appreciated!
I cannot replace it with a squares calculation instead, since it is not a comparison.
EDIT: The relevant portion of the code looks roughly as follows
var width = 2000;
var height = 2000;
function update() {
for (var j = 0; j < height; ++j) {
for (var i = 0; i < width; ++i) {
array[i][j] = Math.sqrt(/* some expression involving i and j */);
}
}
}
var fps = 60;
setInterval(update, 1000 / fps);
As your iterating in a 2-dimensional array you can reduce the iterations by ~2.
For instance :
if your expression is i x j, and your array is 3-3 size starting from 0 your going to compute in your loop :
0*1 AND 1*0
0*2 AND 2*0
1*2 AND 2*1
which is the same
0x0 1x0 2x0
0x1 1x1 2x1
0x2 1x2 2x2
Related
Been following along with Dan Shiffmans videos, trying to brush up on my Object orientated programing using classes.
Ive wrote some code that generates bubbles with random diameters at random positions, using p5's noise function to give the bubbles some movement.
My intention is for the bubbles to pop (be removed from the array with splice()) every time a bubble reaches the edges of the canvas or when two or more bubbles intersect.
The code run as desired, but after a while it crashes throwing up the error "Uncaught TypeError: Cannot read property 'x' of undefined (sketch: line 15)"
Ive tried hacking around but no joy, if anyone could shed some light on why this error occurs, or general pointers on my approach i would be most grateful. Here's the code in question.
var balls = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
for (var i = 0; i < balls.length; i++) {
balls[i].showBall();
balls[i].moveBall();
for (var j = 0; j < balls.length; j++) {
if (balls[i].x < 0 + balls[i].r ||
balls[i].x > width - balls[i].r ||
balls[i].y < 0 + balls[i].r ||
balls[i].y > height - balls[i].r ||
balls[i].life >= 220 ||
i != j && balls[i].intersect(balls[j])) {
balls.splice(i, 1);
}
}
}
}
class Ball {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.t = 0.0;
this.t2 = 107.0;
this.life = 0;
}
showBall() {
noFill();
stroke(this.life);
strokeWeight(2);
ellipse(this.x, this.y, this.r * 2);
}
moveBall() {
this.x += map(noise(this.t), 0, 1, -1, 1) * 0.5;
this.y += map(noise(this.t2), 0, 1, -1, 1) * 0.5;
this.t += 0.02;
this.life += 0.15;
}
intersect(other) {
if (dist(this.x, this.y, other.x, other.y) < this.r + other.r) {
return true;
} else {
return false;
}
}
}
function randBubbleGen() {
let foo = floor(random(11));
console.log(foo);
if (foo >= 5) {
let b = new Ball(random(41, 359),
random(41, 359),
random(5, 40));
balls.push(b);
}
}
setInterval(randBubbleGen, 1000);
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
Thanks!
P
The first part of the problem is a classic splice issue:
const arr = [..."abcde"];
for (let i = 0; i < arr.length; i++) {
if (i === 2) { // some arbitrary condition
arr.splice(i, 1);
}
else {
console.log(i, arr[i]);
}
}
What happened here? After splicing the element at index 2, "c", the length of arr becomes 4, yet i++ still happens, skipping an element "d" which is never printed or visited in the loop. The solution is to i-- after each splicing operation or iterate in reverse so that splice doesn't cause unvisited elements to be skipped.
As for your error, the problem is that your inner loop over all j splices out an element, then continues on, acting as if balls[i] wasn't just removed. Based on the above demonstration, we know that after balls.splice(i, 1), balls[i] becomes the next element after the original i for the rest of that iteration of the outer loop body. This is a bug because some collisions will be skipped for i+1 after the spliced element, but won't cause errors unless i happens to be the last element in the balls array. In that case, balls[i+1] is undefined and you can't access properties on undefined.
The solution is to break out of the inner j loop after splicing out an element. That's in addition to iterating in reverse or using i-- after each splice call to avoid skipping balls.
From a time complexity standpoint, splice is a poor choice because it's O(n). If you have n collisions in the balls array, you'll need to loop over it a factor of n times, causing a triply-nested loop running in your update code.
A better general approach is to create a new array of elements that survived a round, then resassign that new array to the old balls array after the frame. This involves some allocation overhead.
Other tips:
Use const instead of let wherever possible.
Use let instead of var for loop counters and mutable variables. Ideally, never use var or let, although p5 promotes mutability due to its window-attached functions.
Prefer forEach and for ... of loops to classical C-style for loops.
You can return dist(this.x, this.y, other.x, other.y) < this.r + other.r since it's already a boolean, no need for if bool return true else return false verbosity.
Keep rendering and position updating separate as much as possible. It probably doesn't matter much for this animation, but as things get more complex, it can be odd when something dies but still gets rendered for a frame as is the case here.
Move the collision detection and edge detection to external functions -- the if (balls[i].x < 0 + balls[i].r ... condition is difficult to read.
I am working on a procedural terrain generator, but the 3d Map is constantly morphing and changing, calling for at least 4d noise (5d if I need to make it loop). I haven't found a good perlin/simplex noise library that will work in this many dimensions, so I thought this would be a good time to learn how those algorithms work. After starting to make my own "perlin" noise, I found a large problem. I need to get a psudo random value based on the nD coordinates of that point. So far I have found solutions online that use the dot product of a single point and a vector generated by the inputs, but those became very predictable very fast (I'm not sure why). I then tried a recursive approach (below), and this worked ok, but I got some weird behavior towards the edges.
Recursive 3d randomness attempt:
function Rand(seed = 123456, deg = 1){
let s = seed % 2147483647;
s = s < 1 ? s + 2147483647 : s;
while(deg > 0){
s = s * 16807 % 2147483647;
deg--;
}
return (s - 1) / 2147483646;
}
function DimRand(seed, args){
if(args.length < 2){
return Rand(seed, args[0]);
}else{
let zero = args[0];
args.shift();
return DimRand(Rand(seed, zero), args);
}
}
var T = 1;
var c = document.getElementById('canvas').getContext('2d');
document.getElementById('canvas').height = innerHeight;
document.getElementById('canvas').width = innerWidth;
c.width = innerWidth;
c.height = innerHeight;
var size = 50;
function display(){
for(let i = 0; i < 20; i ++){
for(let j = 0; j < 20; j ++){
var bright = DimRand(89,[i,j])*255
c.fillStyle = `rgb(${bright},${bright},${bright})`
c.fillRect(i*size, j*size, size, size);
}
}
T++;
}
window.onmousedown=()=>{display();}
And here is the result:
The top row was always 1 (White), the 2d row and first column were all 0 (Black), and the 3d row was always very dark (less than ≈ 0.3)
This might just be a bug, or I might have to just deal with it, but I was wondering if there was a better approach.
I'm currently working on a maze generating algorithm called recursive division. The algorithm is quite simple to understand: Step 1: if the height of your chamber is smaller than the width, divide your grid/chamber with a vertical line. If the height is greater than the width, then divide your chamber with a horizontal line. Step 2: Repeat step 1 with the sub-chambers that were created by the lines. You want to repeat these steps until you get a maze (until the width or height equals 1 unit).
The problem that I have with this algorithm is that JavaScript prints out a RangeError, meaning that I called the function that creates the maze too many times (I'm trying to implement this algorithm with a recursive function). Is there any way to avoid/prevent this from happening? Or am I missing something important in my code that makes the algorithm not work properly?
I have tried to implement a trampoline function, but since I'm a beginner I just don't understand it well enough to implement my self. I have also restarted my entire project ruffly 3 times with some hope that I will come up with a different approach to this problem, but I get the same error every time.
My code here:
//leftCord = the left most x coordinate of my chamber/grid, upCord = the upmost y coordinate of my
grid etc.
//(0, 0) IS POSITIONED IN THE LEFT TOP NODE OF MY GRID
function createMaze(leftCord, rightCord, upCord, downCord) {
var height = Math.abs(downCord - upCord);
var width = Math.abs(rightCord - leftCord);
if (height < 2 || width < 2) {
//The maze is completed!
return;
} else {
if (height < width) {
//cut the chamber/grid vertically
//Getting a random number that's EVEN and drawing the function x = 'random number' on the grid
var x = randomNum(leftCord / 2, rightCord / 2) * 2;
var lineX = [];
for (i = upCord; i < downCord; i++) {
lineX.push(grid[i][x]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineX.length / 2) * 2 + 1;
lineX.splice(randomDoor, 1);
//Drawing the line
for (i = 0; i < lineX.length; i++) {
lineX[i].className = "wall";
}
//Making the same thing again, but with the left and right sub-chambers that were created by the line
createMaze(leftCord, x, upCord, downCord);
createMaze(x, rightCord, upCord, downCord);
} else {
//cut the chamber/grid horizontally
//Getting a random number that's EVEN and drawing the function y = 'random number' on the grid
var y = randomNum(0, downCord / 2) * 2;
var lineY = [];
for (i = leftCord; i < rightCord; i++) {
lineY.push(grid[y][i]);
}
//Making a random door/passage and making sure it's ODD
var randomDoor = randomNum(0, lineY.length / 2) * 2 + 1;
lineY.splice(randomDoor, 1);
//Drawing the line
for(i = 0; i < lineY.length; i++){
lineY[i].className = "wall";
}
//Making the same thing again, but with the upper and lower-chambers that were created by the line
createMaze(leftCord, rightCord, upCord, y);
createMaze(leftCord, rightCord, y, downCord);
}
}
}
This is happening because you never initialize i with var- it is sent into the global scope and is overwritten each function call.
I drawed a grid based system on canvas using PIXI.js.
I'm trying to animate the thing, first each particle position.y is -200, then using Tween.js I'm trying to make them fall.
I change the position to the correct position, which is particle._y.
As you notice you will see after falling there are some empty spaces and CPU is over heating.
http://jsbin.com/wojosopibe/1/edit?html,js,output
function animateParticles() {
for (var k = 0; k < STAGE.children.length; k++) {
var square = STAGE.children[k];
new Tween(square, 'position.y', square._y, Math.floor(Math.random() * 80), true);
}
}
I think I'm doing something wrong.
Can someone please explain me what I'm doing wrong and why there are some empty spaces after falling?
The reason for the empty spaces is that some of your animations are not starting. The cause is in this line:
new Tween(square, 'position.y', square._y, Math.floor(Math.random() * 80), true);
Looking at your function definition for Tween.js, I see this:
function Tween(object, property, value, frames, autostart)
The fourth parameter is frames. I'm assuming this is the number of frames required to complete the animation.
Well your Math.floor function willl sometimes return zero, meaning the animation will have no frames and won't start!!
You can fix this by using math.ceil() instead. This way there will always be at least 1 frame for the animation:
new Tween(square, 'position.y', square._y, Math.ceil(Math.random() * 80), true);
Now, as for performance, I would suggest setting this up differently...
Animating all those graphics objects is very intensive. My suggestion would be to draw a single red square, and then use a RenderTexture to generate a bitmap from the square. Then you can add Sprites to the stage, which perform WAY better when animating.
//Cretae a single graphics object
var g = new PIXI.Graphics();
g.beginFill(0xFF0000).drawRect(0, 0, 2, 2).endFill();
//Render the graphics into a Texture
var renderTexture = new PIXI.RenderTexture(RENDERER, RENDERER.width, RENDERER.height);
renderTexture.render(g);
for (var i = 0; i < CONFIG.rows; i++) {
for (var j = 0; j < CONFIG.cols; j++) {
var x = j * 4;
var y = i * 4;
//Add Sprites to the stage instead of Graphics
var PARTICLE = new PIXI.Sprite(renderTexture);
PARTICLE.x = x;
PARTICLE.y = -200;
PARTICLE._y = H - y;
STAGE.addChild(PARTICLE);
}
}
This link will have some more examples of a RenderTexture:
http://pixijs.github.io/examples/index.html?s=demos&f=render-texture-demo.js&title=RenderTexture
I have made a board with 156X64 divs 3 pixel each with border radius, so it looks like a board out of LED. I have string representing 0 or 1 of each 7X5 matrix of letters.
var lgeoa="00100001000001000001100011000101110";//7X5 matrix letter A
var lgeob="10000111000010001010100011000101110";//7X5 matrix letter B
and so on...
Drawing letter means change corresponding div background color. It is working fine, but after that I wanted to animate them the problem started. I clear line and draw in every 10 milliseconds, but its very very laggy. So please how can this code be optimized to work without lags?
P.S. Surprisingly it's working better in IE11 rather than in chrome.
Here is a fiddle
There's a lot of optimization that can be done here. I'll point out a couple.
Starting with the animate function, the first thing I notice is that you're running a bit of code every 10ms. Why don't we check out what's being run?
function animate() {
var string = "აბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰ ტესტ ტესტ აი ემ ე თეიბლ ტექსტი იწერება აქ"; //string to animate
position = 150; //initial position of string
window.setInterval(function () {
clearLine(0);
drawOnBoard(string, position, 0);
position = position - 1;
}, 10);
}
Clearline is the first function.
function clearLine(n){
for(var i=n*symbolHeight*lineWidth+n*lineWidth;i<(n+1)*symbolHeight*lineWidth+n*lineWidth;i++)
leds[i].style.backgroundColor="black";
}
That's a bit of a mess in the for loop. My understanding is that non-compiled code will run all of that math for every single iteration. So let's move it out of the for loop.
function clearLine(n) {
var initial = n * symbolHeight * lineWidth + n * lineWidth;
var length = (n + 1) * symbolHeight * lineWidth + n * lineWidth;
for (var i = initial; i < length; i++)
leds[i].style.backgroundColor = "black";
}
Ah but there's still more to be done. I see that both equations have a lot of shared math.
function clearLine(n) {
var whateverThisIs = symbolHeight * lineWidth + n * lineWidth;
var initial = n * whateverThisIs;
var length = (n + 1) * whateverThisIs;
for (var i = initial; i < length; i++)
leds[i].style.backgroundColor = "black";
}
I saw that you're moving on so I'll stop working on this for now. There's still plenty more to optimize.
Here's a fiddle of the updated version.