0
const width=512
const height=512

img = ctx.createImageData(width,height)
//data = a 2D array that I'm trying to map to img

function data_to_rgb(h,w,data,img){
  //modify the img object at h,w with data at h,w
  //return nothing
}

function draw_loop(){

  //some code that acts on data

  for(let w=0;w<width;++w){
    for(let h=0;h<height;++h){
      data_to_rgb(w,h,data,img)
    }
  }
  ctx.putImageData(img,0,0)
}

How do I convert this piece of code to start executing data_to_rgb in parallel, wait for all of them to finish and then execute ctx.putImageData(img,0,0)? I've looked into how to do this myself but so far all the examples showing how to do this keep using functions that has no arguments, where as mine got 4.

I tried the code below but the performance went down to about ~1/10th. I've spent too many hours trying to solve this on my own.

const width=512
const height=512

img = ctx.createImageData(width,height)
//data = a 2D array that I'm trying to map to img

async function data_to_rgb(h,w,data,img){    //MADE THIS ONE ASYNC
  //modify the img object at h,w with data at h,w
  //return nothing
}

async function draw_loop(){                  //MADE THIS ONE ASYNC

  //some code that acts on data
  let tasks = []                             //DEFINED THIS TASKS LIST
  for(let w=0;w<width;++w){
    for(let h=0;h<height;++h){
      tasks.push(data_to_rgb(w,h,data,img))  //PUSHED IT INTO THE TASKS LIST
    }
  }
  await Promise.all(tasks)                   //ADDED THIS AWAIT
  ctx.putImageData(img,0,0)
}

Am I misusing async? (Maybe it should be... more used for network related things)
Is my attempt at solving it on my own even doing what I want it to do?


Here is the actual code
Here is the code with async in action
Here is the code without async in action

Harry Svensson
  • 330
  • 4
  • 18
  • looks like you really could use webworkers https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers if you need mass parallelization – SoluableNonagon Jun 18 '20 at 03:49
  • It depends whether your code in data_to_rgb is asynchronous or not. What does the body of that function look like? – Danny Harding Jun 18 '20 at 03:50
  • @SoluableNonagon I would still have a similar problem, no? Trying to execute a bunch of workers with arguments and then waiting for all of them to be done. - But okay, I may be using the wrong tool for the job. I will look into webworkers. But the core issue will remain. – Harry Svensson Jun 18 '20 at 03:52
  • Nested for loops are O(n^2), so not surprised your performance took a hit. Is there any way to optimize that loop to be shorter? Creating all those Promises will really tie up your thread. – SoluableNonagon Jun 18 '20 at 03:52
  • 512x512 = 262144 tasks you're trying to execute in parallel – SoluableNonagon Jun 18 '20 at 03:54
  • @SoluableNonagon and it runs at an attractive 60 fps without any async involved anywhere. And maybe 0.1 fps with async.- It is not weird having more things you want to do in parallel than you have computer cores. It just means that some of the work will have to wait. – Harry Svensson Jun 18 '20 at 03:57
  • @DannyHarding [Github link](https://github.com/Injisera/Programming-streams/blob/master/Twitch%20wavy%20stuff/2D_async.html) Here is the actual code if you want to have a look at it. It's only 160 rows, most of it being empty space and comments. Here it is [in action](https://injisera.github.io/Programming-streams/Twitch%20wavy%20stuff/2D_async.html) and [here is the one without any async](https://injisera.github.io/Programming-streams/Twitch%20wavy%20stuff/2D.html) – Harry Svensson Jun 18 '20 at 04:00
  • It is a bit weird, not sure why you need to make this run in parallel as single threaded JS will still execute it sequentially. Promise.all is basically intended for tasks that don't execute immediately (network calls, setTimeout, etc). If provided an array of immediately executed functions it will just wrap them in promises and there will be a large memory overhead for creating those promises and then processing overhead cause they immediately resolve. – SoluableNonagon Jun 18 '20 at 04:04
  • 1
    By definition using deferreed async willl be slower. If your goal is to defer execution so that other tasks can continue to execute (such as allowing your UI to still be responsive), you can replace the loop with a setTimeout loop (or your current async approach) to defer each iteration in the event loop and allow other tasks a chance to process. If it is to increase performance, this will only add overhead. If what you want is to increase performance, you need web workers to allow it to execute in another thread, although serialization will add overhead if it is actually a small task. – user120242 Jun 18 '20 at 04:04
  • All the tasks you are doing or APIs you are using are synchronous. You can't turn them into asynchronous simply by declaring the function with `async` keyword. – hangindev.com Jun 18 '20 at 04:11
  • @user120242 Alright, thank you for making it obvious that I'm using the wrong tool for the job (async instead of web workers). I still believe that I need to use some form of async and await with the web workers to guarantee that the web workers have done their job before the command to draw the image is done. Meaning that the core problem remains. – Harry Svensson Jun 18 '20 at 04:13
  • Web worker uses `postMessage` method to communicate with the main thread and you will use it to pass the data back to the main thread. – hangindev.com Jun 18 '20 at 04:16
  • It would be nice to have stackblitz or codesandbox set up rather than links to your github to test the code out. – SoluableNonagon Jun 18 '20 at 04:19
  • @HarrySvensson if this is performance critical real-time drawing code, you will probably want to stick with callbacks using the onmessage event with postMessage as Hangindev has mentioned and not try to wrap it with async. async/await and Promise are pretty performant nowadays, but something as time-sensitive as real-time drawing code probably won't be able to afford the overhead. There's an OffscreenCanvas for Web Workers available to Chrome. Hasn't made it to other browsers yet. – user120242 Jun 18 '20 at 04:20
  • And web workers may not help you to speed up your code unless you start several web workers at the same time (there is a limit). But then, you have to clone the image data to multiple web workers which also take time. What web workers can help you with is non-blocking UI in your interface which you may also achieve by wrapping a repeating task with `setTimout(() => { ... }, 0)`. – hangindev.com Jun 18 '20 at 04:25
  • Looking at the code, it doesn't look like you need any of this. rAF is already enough – user120242 Jun 18 '20 at 04:32

2 Answers2

0

If I understand the problem correctly you want to run all your image transforms, then apply those transforms once they are all finished.

I think the solution is to have data_to_rgb actually return something. But since your image is being manipulated by data_to_rgb you could just copy the image. One of these might help? html5: copy a canvas to image and back

function draw_loop(){
  const someNewImageData = copyImageSomehow(img)

  // apply the transforms on the copied image
  for(let w=0;w<width;++w){
    for(let h=0;h<height;++h){
      data_to_rgb(w,h,data, someNewImageData)
    }
  }

  // now apply the new image data
  ctx.putImageData(someNewImageData ,0,0)
}
SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
0

Here's an attempt at putting together the mess in the comments below the question.

How to start hundreds of async functions with arguments in JS

You actually succeeded in starting all of them. However, they are still only going to be executed serially because no extra threads are being generated by the async keyword.

Am I misusing async? (Maybe it should be... more used for network related things) Is my attempt at solving it on my own even doing what I want it to do?

Yes, you are misusing async in this particular case. Yes, it is doing what you "want it to do", you are launching hundreds of async calls, all of them generating an absurd amount of promises which slows everything down.


Async should be used when waiting for data to arrive, be it from the internet, your hard drive, some device connected to a usb cable. Some data that isn't in ram at the moment.


Async should not be used when a lot of data already is in ram that should be executed in parallel.


Web workers or webgl should be used in this particular case because you want to process the information that already exists in ram in parallel. In order to ensure that all of the web workers are done, look at another stackoverflow question that solves that problem.

Harry Svensson
  • 330
  • 4
  • 18