Skip to content

Instantly share code, notes, and snippets.

@rowillia
Created September 19, 2020 00:33
Show Gist options
  • Save rowillia/d759ea6835c29bf2f35d49dc73b0c38e to your computer and use it in GitHub Desktop.
Save rowillia/d759ea6835c29bf2f35d49dc73b0c38e to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-10T22:09:43.768733Z",
"start_time": "2020-02-10T22:09:42.685638Z"
}
},
"source": [
"# Gevent Performance\n",
"\n",
"## Overview\n",
"\n",
"Geven't sweet spot for performance is network-bound workloads. Coroutines allow us to efficiently interleave other CPU work while waiting on the network instead of just waiting for results.\n",
"\n",
"As we discussed in part 1: gevent uses coroutines for multitasking and coroutines are _not_ threads. They run completely in user space, from the kernel's perspective there is still a single task. Coroutines rely on user level libraries for scheduling via an event loop - in gevent's case the default is `libev`. Coroutines are **significantly** lighter weight than real threads so a process can run significantly more of them, and switching between coroutines can also be much faster as there's no requirement to thunk into kernel space.\n",
"\n",
"In exchange for being higher speed and lighter weight, coroutines require more thought and work to be done in user space. Since coroutines are not threads, there aren't any interrupts as with real threads. If an OS thread is hogging too many resources the OS will interrupt it for another task to run, keeping things as fair as possible.\n",
"\n",
"At the end of the day the machine only has so many CPUs though and we have to deal with those real physical limitations - coroutines are not useful for parallelizing CPU-bound work, they'll probably make things worse, but are incredibly useful for parallelizing network-bound work.\n",
"\n",
"## Effective use of gevent\n",
"\n",
"Let's imagine we have an endpoint that needs to fetch 3 resources, does some computation, and returns. The simplest way to write this would be to fetch all 3 resources serially, compute the result, and return:"
]
},
{
"cell_type": "code",
"execution_count": 224,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T20:45:43.422193Z",
"start_time": "2020-02-18T20:45:43.386797Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"6"
]
},
"execution_count": 224,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def fetch_resource(result, response_time=0.01):\n",
" gevent.sleep(response_time)\n",
" return result\n",
"\n",
"def compute_thing():\n",
" a = fetch_resource(1)\n",
" b = fetch_resource(2)\n",
" c = fetch_resource(3)\n",
" return a + b + c\n",
"\n",
"compute_thing()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Great! This works as expected. Let's see how long it takes:"
]
},
{
"cell_type": "code",
"execution_count": 228,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T20:48:36.525451Z",
"start_time": "2020-02-18T20:48:34.037445Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"30.7 ms ± 39 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
]
}
],
"source": [
"%timeit compute_thing()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Hmm - not bad, but it can probably be faster. This workload appears to be totally dominated by network time, so it's probably a good candidate to be sped up with coroutines!"
]
},
{
"cell_type": "code",
"execution_count": 230,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T20:50:15.933175Z",
"start_time": "2020-02-18T20:50:07.569432Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"6\n",
"10.3 ms ± 24.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
"source": [
"def compute_thing_parallel():\n",
" tasks = [\n",
" gevent.spawn(fetch_resource, 1),\n",
" gevent.spawn(fetch_resource, 2),\n",
" gevent.spawn(fetch_resource, 3)\n",
" ]\n",
" a, b, c = [t.get() for t in gevent.joinall(tasks)]\n",
" return a + b + c\n",
"\n",
"print(compute_thing_parallel())\n",
"%timeit compute_thing_parallel()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Mixing CPU and I/O\n",
"\n",
"Few workloads are as clean as above though - usually our servers do some amount of compute, some amount of I/O, even if that compute is just serilization/deserialization of requests. Let's create a benchmark where we vary the amount of CPU work, Network-bound work, and the number of coroutines we have running in parallel to simulate concurrent requests. This benchmark does half of it's CPU work, then makes a network call, then does the remaining CPU work. You'll find the code for this benchmark in the last cell - moved there to make this article read easier.\n",
"\n",
"First, let's run a workload that does 5ms of CPU work and 55ms of networking, with 1 greenlet as a start:"
]
},
{
"cell_type": "code",
"execution_count": 239,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T21:05:59.369345Z",
"start_time": "2020-02-18T21:05:22.846887Z"
},
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5ms CPU/55ms Network per request (500 requests with 1 green treads)\n",
"\tThroughput: 16.49 rps (1.00X Speedup)\n",
"\tCPU Utilization: 8.24%\n",
"\tp50: 60.53ms\n",
"\tp95: 60.60ms\n",
"\tp99: 60.62ms\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"output_benchmark_result(run_benchmark(work_time_ms=5, network_time_ms=55, num_green_threads=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"So far so good - our RPS is roughly what we would expect from a 60ms workload and we have a really tight bounding of response times. The only downside is that utilization number - we're only using 8.24% of our CPU! Let's throw more green threads at this task since we're _mostly_ dominated by I/O:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-25T21:44:11.623328Z",
"start_time": "2020-02-25T21:43:59.284774Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5ms CPU/55ms Network per request (500 requests with 5 green treads)\n",
"\tThroughput: 81.61 rps (4.94X Speedup)\n",
"\tCPU Utilization: 40.80%\n",
"\tp50: 60.88ms\n",
"\tp95: 61.38ms\n",
"\tp99: 64.57ms\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"output_benchmark_result(run_benchmark(work_time_ms=5, network_time_ms=55, num_green_threads=5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Amazing! A nearly linear speedup with the number of green threads, and now we're using 41% of our CPU instead of 8%! Well if 5 green threads was good, 50 must surely be better, right?"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-09-18T23:52:00.613713Z",
"start_time": "2020-09-18T23:51:51.832850Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5ms CPU/55ms Network per request (500 requests with 50 green treads)\n",
"\tThroughput: 192.46 rps (11.60X Speedup)\n",
"\tCPU Utilization: 96.23%\n",
"\tp50: 182.12ms\n",
"\tp95: 182.52ms\n",
"\tp99: 182.77ms\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"output_benchmark_result(run_benchmark(work_time_ms=5, network_time_ms=55, num_green_threads=50))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Uh Oh....while our throughput is 11.6x higher than the baseline (a number tantalizingly close to 12, or `1/(1-(55/60))` but more on that later) our response times have skyrocketed! How can it be the case that the p50 time ffor a workload that takes 60ms is 188ms? Probably the GIL or garbage collector, right? Let's try to debug - let's dump who was doing what during our longest request (when we pass `print_details=True` the benchmark will output details of what request is actually running and what it is doing. We'll use a different mix to more simply show the problem):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-25T22:02:32.485640Z",
"start_time": "2020-02-25T22:01:46.618042Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"50ms CPU/150ms Network per request (500 requests with 5 green treads)\n",
"\tThroughput: 19.49 rps (3.91X Speedup)\n",
"\tCPU Utilization: 97.46%\n",
"\tp50: 230.27ms\n",
"\tp95: 255.59ms\n",
"\tp99: 255.89ms\n",
"=========================\n",
"Longest Request:\n",
"\t[1582668126.763] Request 3: + 25.00ms CPU time\n",
"\t[1582668126.788] Request 3: + 150.00ms Network time\n",
"\t[1582668126.788] Request 4: + 25.00ms CPU time\n",
"\t[1582668126.813] Request 4: + 150.00ms Network time\n",
"\t[1582668126.863] Request 0: - 150.00ms Network time took 150.72ms\n",
"\t[1582668126.863] Request 0: + 25.00ms CPU time\n",
"\t[1582668126.889] Request 5: + 25.00ms CPU time\n",
"\t[1582668126.914] Request 5: + 150.00ms Network time\n",
"\t[1582668126.915] Request 1: - 150.00ms Network time took 177.05ms\n",
"\t[1582668126.915] Request 1: + 25.00ms CPU time\n",
"\t[1582668126.940] Request 2: - 150.00ms Network time took 177.02ms\n",
"\t[1582668126.940] Request 2: + 25.00ms CPU time\n",
"\t[1582668126.965] Request 6: + 25.00ms CPU time\n",
"\t[1582668126.990] Request 6: + 150.00ms Network time\n",
"\t[1582668126.990] Request 7: + 25.00ms CPU time\n",
"\t[1582668127.015] Request 7: + 150.00ms Network time\n",
"\t[1582668127.016] Request 3: - 150.00ms Network time took 228.47ms\n",
"\t[1582668127.016] Request 3: + 25.00ms CPU time\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"output_benchmark_result(run_benchmark(work_time_ms=50, network_time_ms=150, num_green_threads=5), print_details=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Interesting - when running with 5 green threads we see that the issue is for the longest request 150ms of network time \"took\" 228.47ms. Looking at the details we see that there was 25ms of CPU time scheduled for 8 other requests - helping us blow past our 150 network time. The first 6 times CPU time was scheduled it was OK as our request wasn't done...but every addional time CPU work was scheduled we were starved. The issue is as the number of concurrent requests increases the likelyhood more work gets scheduled than can be effectively be parallelized increases. Let's see what this looks like in the limit:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2020-09-19T00:02:28.771158Z",
"start_time": "2020-09-19T00:00:02.716107Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"results = [run_benchmark(work_time_ms=5, network_time_ms=55, num_green_threads=x) for x in range(1, 25, 2)]\n",
"\n",
"green_threads = [x.num_green_threads for x in results]\n",
"speedups = [x.speedup for x in results]\n",
"ax = plt.axes()\n",
"ax.plot(green_threads, speedups, color='blue')\n",
"ax.set(xlim=(0, max(green_threads)), ylim=(0, int(max(speedups)) + 2),\n",
" xlabel='Number of Green Threads', ylabel='Speedup',\n",
" title='Throughput Increase vs. # of Green Threads');\n",
"plt.hlines(y=12, xmin=0, xmax=max(green_threads), colors='r', linestyles='--', lw=2)\n",
"plt.plot()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-09-19T00:02:28.911127Z",
"start_time": "2020-09-19T00:02:28.772629Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"green_threads = [x.num_green_threads for x in results]\n",
"p50s = [np.percentile(x.response_times, 50) for x in results]\n",
"p95s = [np.percentile(x.response_times, 95) for x in results]\n",
"p99s = [np.percentile(x.response_times, 99) for x in results]\n",
"ax = plt.axes()\n",
"ax.plot(green_threads, p50s, color='blue', label='p50 Response Time')\n",
"ax.plot(green_threads, p95s, color='green', label='p95 Response Time')\n",
"ax.plot(green_threads, p99s, color='purple', label='p99 Response Time')\n",
"ax.set(xlim=(0, max(green_threads)), ylim=(0, int(max(p99s)) + 2),\n",
" xlabel='Number of Green Threads', ylabel='p50 response time',\n",
" title='Response times vs. # of Green Threads');\n",
"plt.vlines(x=11, ymin=0, ymax=int(max(p99s)) + 2, colors='r', linestyles='--', lw=2)\n",
"plt.legend();\n",
"plt.plot()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We see that the speedup asymptotically approaches 12, but after ~11 concurrent requests response times start to go up. Let's revisit that `1/(1-(55/60))` number - this come from [Amdahl's Law](https://en.wikipedia.org/wiki/Amdahl%27s_law) which states the maximum speedup of a task by parallelization is `1/1-p` where `p = {Parallelizable Ratio of Workload}`. In our example here 55ms out of 60ms can be parallelized, so `p = 0.92`\n",
"\n",
"<img src=\"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/AmdahlsLaw.svg/1920px-AmdahlsLaw.svg.png\" width=\"400\" />\n",
"\n",
"\n",
"Our use case here is _slightly_ different than Amdahl's law - in the case of Amdahl's Law we throw more machines at a problem to incrementally reduce the cost of the parallelizable portion. With gevent and cooperative multitasking after `1/1-p` workers the amount of time the parallelizable portion takes goes to zero as we can run all our CPU work while the network tasks are running.\n",
"\n",
"### Putting it into practice\n",
"\n",
"\n",
"Even though we focused on Python and Gevent, these issues aren't limited to gevent - they would be just as applicable to NodeJS, Asyncio, or any other cooperative multitasking system - if a cooperative system attempts to handle more simultaneous requests than it can response times will degrade. For putting this into practice, however, we will focus on `gevent` and `gunicorn`.\n",
"\n",
"In practice, this means we would like to minimize the number of concurrent requests a given process is handling - any processes handling too many concurrent requests will degrade response times. With this in mind, there's a few things to be aware of:\n",
"\n",
"1. [epoll unfairly routes requests](https://idea.popcount.org/2017-02-20-epoll-is-fundamentally-broken-12/), favoring higher PIDs. This can quickly lead to a situation where one process is handling too many requests, running super hot while the others are barely scheduled. This is exacerbated [bug in gunicorn](https://github.com/benoitc/gunicorn/pull/2266) that causes a worker to accept as many connections as it can upon waking up. Pulling in this fix leads to more fair routing.\n",
"\n",
"2. Setting `SO_REUSEPORT` on the listening socket also [improves fairness](https://lwn.net/Articles/542629/). This flipped from always being on [to being opt-in in gunicorn 19.8](https://github.com/benoitc/gunicorn/pull/1669). Services should consider manually specifyng `--reuse-port` in their gunicorn configuration.\n",
"\n",
"3. Gunicorn allows services to speecify the maximum number of conncurrent requests a worker will services with the [--worker-connections](https://docs.gunicorn.org/en/stable/settings.html#worker-connections) flag. This defaults to `1000` which is almost certainly too high for most services (for example, services would have to do 1ms of CPU work, 1s of network bound work per request for this to be correct). Consider tuning this down to a more appropriate number using tools like tracing to determine how long the service is blocked on network vs. consuming CPU. A more proper number is `{network_time}/{cpu_time}` - for example if a service does 5ms of CPU work and 55ms of waiting on network for a typical request it should likely tune `--worker-connections=12` - anything higher and response times will degrade.\n",
"\n",
"4. Optimize CPU time and avoid unnecessary yielding: Reducing CPU increase the `{network_time}/{cpu_time}` naturally allowing the service to handle more concurrent requests. Yielding more often to the event loop, however, will reduce predictability of responses since there are now more opportunities for other requests to run and slow down the running request. Below we run the same benchmark, except instead of 1 55ms network call per request there's 5 11ms network calls per request. We see response times degrade faster as the number of green threads grow.\n",
"\n",
"Note that all of the above optimizations really only help services that are running somewhat hot. If each host is only ever serving a single request at a time this won't help - concurrency isn't an issue in that case. That said, the issues mentioned above are important blockers to running hotter - if a service saw response times degrade at higher CPU try making the above optimizations and try running hotter again."
]
},
{
"cell_type": "code",
"execution_count": 223,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T20:44:49.401294Z",
"start_time": "2020-02-18T20:42:16.663528Z"
}
},
"outputs": [],
"source": [
"results_5 = [run_benchmark(work_time_ms=5, network_time_ms=55, splits=5, num_green_threads=x) for x in range(1, 25, 2)]"
]
},
{
"cell_type": "code",
"execution_count": 227,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T20:46:56.031409Z",
"start_time": "2020-02-18T20:46:55.910189Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 227,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"green_threads = [x.num_green_threads for x in results_5]\n",
"speedups = [x.speedup for x in results_5]\n",
"ax = plt.axes()\n",
"ax.plot(green_threads, speedups, color='blue')\n",
"ax.set(xlim=(0, max(green_threads)), ylim=(0, int(max(speedups)) + 2),\n",
" xlabel='Number of Green Threads', ylabel='Speedup',\n",
" title='Throughput Increase vs. # of Green Threads');\n",
"plt.hlines(y=12, xmin=0, xmax=max(green_threads), colors='r', linestyles='--', lw=2)\n",
"plt.plot()"
]
},
{
"cell_type": "code",
"execution_count": 253,
"metadata": {
"ExecuteTime": {
"end_time": "2020-02-18T22:05:38.597891Z",
"start_time": "2020-02-18T22:05:38.430676Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"[]"
]
},
"execution_count": 253,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"green_threads = [x.num_green_threads for x in results_5]\n",
"p50s = [np.percentile(x.response_times, 50) for x in results_5]\n",
"p95s = [np.percentile(x.response_times, 95) for x in results_5]\n",
"p99s = [np.percentile(x.response_times, 99) for x in results_5]\n",
"ax = plt.axes()\n",
"ax.plot(green_threads, p50s, color='blue', label='p50 Response Time')\n",
"ax.plot(green_threads, p95s, color='green', label='p95 Response Time')\n",
"ax.plot(green_threads, p99s, color='purple', label='p99 Response Time')\n",
"ax.set(xlim=(0, max(green_threads)), ylim=(0, int(max(p99s)) + 2),\n",
" xlabel='Number of Green Threads', ylabel='p50 response time',\n",
" title='Response times vs. # of Green Threads');\n",
"plt.vlines(x=9, ymin=0, ymax=int(max(p99s)) + 2, colors='r', linestyles='--', lw=2)\n",
"plt.legend();\n",
"plt.plot()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-09-18T23:51:05.480674Z",
"start_time": "2020-09-18T23:51:02.857667Z"
}
},
"outputs": [],
"source": [
"!pip install gevent eventlet greenlet matplotlib numpy\n",
"%matplotlib inline\n",
"\n",
"import time\n",
"from typing import List, NamedTuple\n",
"\n",
"import matplotlib.pyplot as plt\n",
"\n",
"import gevent\n",
"import numpy as np\n",
"from gevent.pool import Pool\n",
"\n",
"\n",
"def do_cpu_work(work_time_ms, name, output):\n",
" start = time.time()\n",
" output.append(f'[{start:.3f}] {name}: + {work_time_ms:.2f}ms CPU time')\n",
" start_output_index = len(output) - 1\n",
" time.sleep(work_time_ms / 1000.0) # Simulate CPU Work by calling sleep\n",
" end = time.time()\n",
" #output.append(f'[{end:.3f}] {name}: - {work_time_ms:.2f}ms CPU work took {(end - start) * 1000:.2f}ms')\n",
" end_output_index = len(output) - 1\n",
" return start_output_index, end_output_index\n",
"\n",
"\n",
"def do_network_work(network_time_ms, name, output):\n",
" start = time.time()\n",
" output.append(f'[{start:.3f}] {name}: + {network_time_ms:.2f}ms Network time')\n",
" start_output_index = len(output) - 1\n",
" gevent.sleep(network_time_ms / 1000.0) # Simulate Network Work by calling sleep\n",
" end = time.time()\n",
" output.append(f'[{end:.3f}] {name}: - {network_time_ms:.2f}ms Network time took {(end - start) * 1000:.2f}ms')\n",
" end_output_index = len(output) - 1\n",
" return start_output_index, end_output_index\n",
" \n",
"\n",
"def do_work(args):\n",
" start = time.time()\n",
" work_time_ms, network_time_ms, splits, name, output = args\n",
" start_output_index, _ = do_cpu_work(work_time_ms / (splits + 1), name, output)\n",
" for _ in range(splits):\n",
" do_network_work(network_time_ms / splits, name, output)\n",
" _, end_output_index = do_cpu_work(work_time_ms / (splits + 1), name, output)\n",
" end = time.time()\n",
" return end - start, (start_output_index, end_output_index)\n",
"\n",
"\n",
"class BenchmarkResult(NamedTuple):\n",
" work_time_ms: float\n",
" network_time_ms: float\n",
" iterations: int\n",
" num_green_threads: int\n",
" throughput_rps: float\n",
" speedup: float\n",
" cpu_utilization: float\n",
" response_times: np.array\n",
" longest_request: List[str]\n",
" \n",
"\n",
"def run_benchmark(work_time_ms, network_time_ms, num_green_threads, splits=1, baseline_iterations=100, iterations=500):\n",
" start = time.time()\n",
" pool = Pool(num_green_threads)\n",
" output = []\n",
" # Compute baseline\n",
" start = time.time()\n",
" for x in range(baseline_iterations):\n",
" do_work((work_time_ms, network_time_ms, splits, x, []))\n",
" end = time.time()\n",
" baseline_duration_s = end - start\n",
" \n",
" # Run benchmark\n",
" start = time.time()\n",
" results = [\n",
" x for x in\n",
" pool.imap_unordered(\n",
" do_work, \n",
" [(work_time_ms, network_time_ms, splits, f'Request {name}', output) for name in range(iterations)]\n",
" )\n",
" if x\n",
" ]\n",
" result_array = np.array([x[0] * 1000.0 for x in results])\n",
" longest_request = max(results)\n",
" end = time.time()\n",
" total_time_s = end - start\n",
" avg_baseline_duration_s = baseline_duration_s / baseline_iterations\n",
" baseline_rps = baseline_iterations / baseline_duration_s\n",
" result_rps = iterations / total_time_s\n",
" max_rps = (1 / (work_time_ms / 1000.0))\n",
" speedup = result_rps / baseline_rps\n",
" cpu_utilization = result_rps * 100.0/max_rps\n",
" return BenchmarkResult(\n",
" work_time_ms=work_time_ms, network_time_ms=network_time_ms, num_green_threads=num_green_threads,\n",
" iterations=iterations, throughput_rps=result_rps, speedup=speedup, cpu_utilization=cpu_utilization,\n",
" response_times=result_array, longest_request=output[longest_request[1][0]:longest_request[1][1] + 1]\n",
" )\n",
"\n",
"\n",
"def output_benchmark_result(result, print_details=False):\n",
" print(f'{result.work_time_ms}ms CPU/{result.network_time_ms}ms Network per request '\n",
" f'({result.iterations} requests with {result.num_green_threads} green treads)')\n",
" print(f'\\tThroughput: {result.throughput_rps:.2f} rps ({result.speedup:.2f}X Speedup)')\n",
" print(f'\\tCPU Utilization: {result.cpu_utilization:.2f}%')\n",
" print(f'\\tp50: {np.percentile(result.response_times, 50):.2f}ms')\n",
" print(f'\\tp95: {np.percentile(result.response_times, 95):.2f}ms')\n",
" print(f'\\tp99: {np.percentile(result.response_times, 99):.2f}ms')\n",
" if print_details:\n",
" print('=' * 25)\n",
" print('Longest Request:')\n",
" print('\\t' + '\\n\\t'.join(result.longest_request))\n",
" plt.hist(result.response_times, 20)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.9"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment