Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[batch] maybe reduce average JVMJob "connecting to jvm" time #13870

Merged
merged 13 commits into from
Oct 25, 2023

Conversation

danking
Copy link
Contributor

@danking danking commented Oct 20, 2023

No description provided.

@danking danking force-pushed the batch-reduce-connecting-to-jvm-time branch from f2145c4 to d550af9 Compare October 20, 2023 05:05

async def recreate_jvm(self, jvm: JVM):
self._jvms.remove(jvm)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a latent bug; the JVM is still owned by the job when recreate_jvm is called, it won't be in this array. If this every happened it would fail.

@danking danking force-pushed the batch-reduce-connecting-to-jvm-time branch 2 times, most recently from 0b6d075 to ba7204c Compare October 20, 2023 05:07
@@ -2984,8 +3004,9 @@ async def shutdown(self):
log.info('Worker.shutdown')
self._jvm_initializer_task.cancel()
async with AsyncExitStack() as cleanup:
for jvm in self._jvms:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems a bit odd; the JVMs might still be held by jobs, right?

@danking danking force-pushed the batch-reduce-connecting-to-jvm-time branch from ba7204c to 7767141 Compare October 20, 2023 05:08
@danking danking force-pushed the batch-reduce-connecting-to-jvm-time branch from 7767141 to 92bf566 Compare October 20, 2023 05:09
@danking
Copy link
Contributor Author

danking commented Oct 20, 2023

It takes about 1.3s to start a JVM.

INFO 2023-10-20T05:19:41.171496149Z [resource.labels.instanceId: 5522146237091918701] JVM-0: trying to establish connection; elapsed time: 0.0 seconds
INFO 2023-10-20T05:19:42.258313605Z [resource.labels.instanceId: 5522146237091918701] listening on /host/jvm-4ed4369f8e4241a69ad5aa4ddbbc94ac/socket
INFO 2023-10-20T05:19:42.426801136Z [resource.labels.instanceId: 5522146237091918701] negotiating start up with worker
INFO 2023-10-20T05:19:42.465000271Z [resource.labels.instanceId: 5522146237091918701] JVM-1: trying to establish connection; elapsed time: 0.0 seconds
INFO 2023-10-20T05:19:43.492820485Z [resource.labels.instanceId: 5522146237091918701] listening on /host/jvm-d674ae13bb154cd59e8a0ef0f36ee890/socket
INFO 2023-10-20T05:19:43.722434655Z [resource.labels.instanceId: 5522146237091918701] negotiating start up with worker
INFO 2023-10-20T05:19:43.759994063Z [resource.labels.instanceId: 5522146237091918701] JVM-2: trying to establish connection; elapsed time: 0.0 seconds
INFO 2023-10-20T05:19:44.860396159Z [resource.labels.instanceId: 5522146237091918701] listening on /host/jvm-9cee668d601a4b7cb272b21c9b14fa19/socket
INFO 2023-10-20T05:19:45.021762412Z [resource.labels.instanceId: 5522146237091918701] negotiating start up with worker
INFO 2023-10-20T05:19:45.060262301Z [resource.labels.instanceId: 5522146237091918701] JVM-3: trying to establish connection; elapsed time: 0.0 seconds
INFO 2023-10-20T05:19:46.169876471Z [resource.labels.instanceId: 5522146237091918701] listening on /host/jvm-65900a48075b423796c4d2bbdaf16845/socket
INFO 2023-10-20T05:19:46.319864345Z [resource.labels.instanceId: 5522146237091918701] negotiating start up with worker

jigold
jigold previously requested changes Oct 20, 2023
n_cores = self._jvm_waiters.get_nowait()
jvmqueue = self._jvms_by_cores[n_cores]
jvmqueue.queue.put_nowait(await JVM.create(global_jvm_index, n_cores, self))
jvmqueue.total += 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having a hard time following this code and why the outer queue of jvm_waiters is necessary.


assert self._waiting_for_jvm_with_n_cores.empty()
assert all(jvmpool.full() for jvmpool in self._jvmpools_by_cores.values())
log.info(f'JVMs initialized {self._jvmpools_by_cores}')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jigold Thanks for pushing back! What do you think of it now?

To directly answer your question: one queue (jvmpool.queue) is a place for a consumer to borrow a JVM, the other queue (waiting_for_jvm_with_n_cores) is a place for a producer to learn that a consumer is waiting.

Without waiting_for_jvm_with_n_cores, _initialize_jvms has no way to be told that someone is waiting for a JVM. asyncio.Queue doesn't expose a method like has_waiters().

@jigold
Copy link
Contributor

jigold commented Oct 23, 2023

Much better! I understand what's going on now. Just to make sure I understand where the performance improvements are, we don't wait for all JVMs to be intitialized before accepting JVM jobs and the queue is FIFO so we reuse the same JVMs that are warm already?

jigold
jigold previously requested changes Oct 23, 2023
Copy link
Contributor

@jigold jigold left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment.

@danking
Copy link
Contributor Author

danking commented Oct 23, 2023

we don't wait for all JVMs to be intitialized before accepting JVM jobs and the queue is FIFO so we reuse the same JVMs that are warm already?

No. We have no way to accept only JVM jobs or only Batch jobs, so we either accept all jobs or no jobs. In main, we accept jobs before the JVMs have initialized. We wait for all JVMs to initialize before giving JVMs to any JVM Job. So, concretely, in main and in this PR we accept jobs before JVMs are ready; however, in this PR we don't wait for all JVMs to initialize before running jobs.

There are two improvements in this PR:

  1. If a JVM with the requested number of cores is available, allow the requesting JVMJob to start before the remaining JVMs are initialized.
  2. Rather than starting all the JVMs in parallel, start JVMs serially and also start them in the order that they are requested. If we have three waiting JVM Jobs two requesting 1 core and one requesting 4 cores, prefer to start JVMs with 1 and 4 cores to JVMs with 2 or 8 cores.

(2) might sound slower (why start serially when we can stat in parallel?) but it appears that 30 JVMs competing for CPU time dramatically slows down average start up time. In both main and this PR it takes about ~25s for all JVMs to be ready; however, in this PR, some jobs can start much sooner than 25s b/c their JVMs are started first.

@danking danking merged commit 2f69f8a into hail-is:main Oct 25, 2023
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants