-
Notifications
You must be signed in to change notification settings - Fork 54
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
Add in-process concurrency support to the PS worker #123
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SteveL-MSFT
requested changes
Jan 5, 2019
Co-Authored-By: daxian-dbw <[email protected]>
SteveL-MSFT
approved these changes
Jan 5, 2019
…ule path env variable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good - just a couple questions
Co-Authored-By: daxian-dbw <[email protected]>
TylerLeonhardt
approved these changes
Jan 7, 2019
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢it
Thanks for the review, the in-proc concurrency support is now merged. |
Closed
6 tasks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Add in-process concurrency support to the PS worker:
The major changes are:
PSThreadOptions.ReuseThread
for theInitialSessionState
when creatingRunspace
, so that everyPowerShellManager
only creates one thread and then reuse it afterwards. The default behavior is to create a new thread every timePowerShell.Invoke
is called.RequestProcessor
to processInvocationRequest
in asynchronously via tasks.PowerShellManagerPool
usingBlockingCollection
PSWorkerInProcConcurrencyUpperBound
PowerShellManager
viaCheckoutIdleWorker
on the main thread. Once getting an idle instance back, the main thread will queue a task to process an invocation request on a thread-pool thread and forget about it -- the main thread then can go ahead to process the next message.RpcLogger
and make everyPowerShellManager
have its own logger instance.RequestId
andInvocationId
for logger. The original way to setup the context only works for single-thread design.MessagingStream
to use aBlockingCollection
to hold all messages that are about to be written out, then use a single thread-pool thread to take out items and write them to the gRPC channel.AveInvTime
represents the average processing time (ms) for a invocation request, and it's 94 vs. 2.6 ms, huge improvement.Before Removing
After Removing
Throughput investigation
Design Change
The original design is to not block the main thread -- it reads a request message, start a task for processing and forget about it, then it reads the next message.
With this design, every in-coming request will be handled immediately -- a task will be created for it. So, a large amount of tasks will be created rapidly, and thus a large amount of thread-pool threads will be assigned to run those tasks. All those threads will compete for an idle
PowerShellManager
from the pool, which results in lock contention in the pool. The average time for fetching an idlePowerShellManager
instance gets worse dramatically when velocity of in-coming requests reaches about 70/s -- it reaches 994 ms in my measurements, and becomes a huge bottleneck.Therefore, I changed the design to make the main thread the only one that checks out idle
PowerShellManager
instance. When the main thread is able to check out one, it creates a task with thepsManager
for processing the invocation request in a thread-pool thread. When the main thread is not able to check out one, it's blocked on the pool until an idle instance becomes available in the pool.With this design, the PS worker creates tasks only if there is a
PowerShellManager
instance available, so the number of tasks running in parallel is limited to the size of the pool. In this way, lock contention is minimized when checking out instance from the pool, and the pressure onTaskScheduler
is also minimized.Below are some data for comparing before/after the design change. Pay attention to the
AveFetchFromPoolTime
column. The average time to check out aPowerShellManager
instance from the pool for an invocation request was ~900 ms with the original design when there are fast in-coming requests. The average time for the same with the new design drops drastically. Be noted that, theAveFetchFromPoolTime
drops to 0.0054 ms after removing the 2 system logging because it takes much less time for processing every invocation request, and thus anPowerShellManager
instance can become idle and reclaimed by the pool much faster.System Logging Mystery
In a AzF worker, a system logging is in fact a log message written out by calling
Console.WriteLine
. AzF host redirects the worker process's STDOUT, and thus can intercept the message and make it a system log.I don't know why the system logging causing such a big impact to the invocation processing time when there are fast in-coming requests. Will discuss this with @pragnagopa offline.
The Throughput Test Results
I use LOCUST as the infrastructure for load testing locally, so it's guaranteed that only one PS worker is involved (it will auto-scale if testing against Azure).
Here is the benchmark I got for C# script function, JavaScript function and PowerShell (new design + no system logging) function:
As you can see, we are at the same level with C# script function and JavaScript function.
However, using a pool of 5 instances doesn't show a better concurrency performance comparing with a pool of 1 instance. Based on the data I collected from the measurements, I believe that's because the AzF Host (Function Runtime) becomes the bottleneck when the in-coming requests become very fast (> 70/s), and thus the PS worker hasn't got to the point where a pool with more instances benefits more from the extra
PowerShellManager
instances. Take a look at theAveFetchFromPoolTime
column of the last two tables below (pool-5 vs. pool-1), the average time for fetching an idle instance from the pool is 100x faster for the pool-5 case. However, even though 100x slower, the pool-1 case can still get an idle instance in about 0.28 ms on average, and that's why we don't see a difference in RPS.Again, I will have an in-depth discussion with @pragnagopa offline about the bottleneck in the AzF host.
Original Design
New Design with 2 System Logging (pool size - 5)
New Design without 2 System Logging (pool size - 5)
New Design without 2 System Logging (pool size - 1)