From 75ce374a20b0c8714a93f3fa9977dbf6434ddf1c Mon Sep 17 00:00:00 2001 From: Mike Rousos Date: Wed, 14 Nov 2018 11:09:20 -0500 Subject: [PATCH 1/3] Add initial draft of a Perf Best Practices doc Fixes aspnet/Docs##9526 --- .../performance/performance-best-practices.md | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 aspnetcore/performance/performance-best-practices.md diff --git a/aspnetcore/performance/performance-best-practices.md b/aspnetcore/performance/performance-best-practices.md new file mode 100644 index 000000000000..0cce6982c6f3 --- /dev/null +++ b/aspnetcore/performance/performance-best-practices.md @@ -0,0 +1,72 @@ +--- +title: ASP.NET Core Performance Best Practices +author: mjrousos +description: Tips for increasing performance in ASP.NET Core apps and avoiding common performance problems +monikerRange: '>= aspnetcore-1.1' +ms.author: riande +ms.date: 11/13/2018 +uid: performance/performance-best-practices +--- +# ASP.NET Core Performance Best Practices + +By [Mike Rousos](https://github.com/mjrousos) + +ASP.NET Core is a high-performance web framework, but problems in application code can still lead to slow or inconsistent response times. This document lists some common issues that may limit the performance of your ASP.NET Core app and how to avoid them. + +## Avoid Blocking Calls +ASP.NET Core applications need to be able to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle hundreds or thousands of concurrent requests by not waiting for blocking calls (such as database access). Instead, the thread can work on another request while the long-running operation completes. + +A common problem in ASP.NET Core applications that aren't performing well is blocking calls that could be asynchronous. This pattern leads to ThreadPool starvation and degrading response times. + +* **Do not** block asynchronous execution by calling `Task.Wait()` or `Task.Result`. +* **Do** make hot code paths asynchronous and call asynchronous APIs for any long-running operations (especially data access). +* **Do** make controller actions asynchronous since the entire callstack needs to be asynchronous in order to benefit from async/await patterns. +* **Do not** lock on common code paths since ASP.NET Core applications need to run highly parallelized. + +An indication that blocking calls may be slowing down your app is if profiling (using a tool like [PerfView](https://github.com/Microsoft/perfview), for example) shows threads regularly being added to the ThreadPool (as indicated by the `Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start` event). There is further guidance available in the [async guidance docs](TBD-Link_To_Davifowl_Doc). + +## Be Mindful of Large Object Allocations +The [.NET garbage collector](https://docs.microsoft.com/dotnet/standard/garbage-collection/) manages allocation and release of memory automatically in .NET applications. This means that, generally, .NET developers don't need to worry about when or how memory is freed. However, cleaning up unreferenced objects takes resources (CPU time), so developers need to be careful about allocating too many objects in very hot code paths. This is especially true of large objects (>85,000 bytes) since they will be stored on the large object heap and will eventually require a full (generation 2) garbage collection to clean up. Unlike generation 0 and generation 1 collections, a generation 2 collection requires application execution to be temporarily suspended. Frequent allocation and de-allocation of large objects can cause inconsistent performance in ASP.NET Core applications. + +* **Do** consider caching large objects that will be frequently used so that they don't need to be re-allocated each time they're needed. +* **Do** pool buffers by using an `ArrayPool` to store large arrays. +* **Do not** allocate many, short-lived large objects on hot code paths. + +Memory issues like this can be diagnosed by looked at GC stats in PerfView and seeing how long GC pauses were, what percentage of the processor time was spent in garbage collection, and how many garbage collections were gen 0, gen 1, or gen 2. + +## Optimize Data Access +Interactions with a data store or other remote services are often the slowest parts of an ASP.NET Core app. Because of that, it's important to make sure that data is read and written efficiently. In addition to making sure to perform data access asynchronously, some best practices include: + +* **Do** consider caching frequently-used data retrieved from a database or remote service if it is acceptable for the data to be slightly out-of-date. Depending on the scenario, you might use a [MemoryCache](https://docs.microsoft.com/aspnet/core/performance/caching/memory) or a [DistributedCache](https://docs.microsoft.com/aspnet/core/performance/caching/distributed). +* **Do not** use 'chatty' database interactions. Instead, retrieve all the data that will be needed in a single call rather than over several. +* **Do** use [no-tracking queries](https://docs.microsoft.com/ef/core/querying/tracking) in Entity Framework when accessing data in a read-only scenario. +* **Do** filter and aggregate LINQ queries (with `.Where`, `.Select`, or `.Sum` statements, for example) before resolving the query so that the filtering is done by the database and the response returned to your application is smaller. +* **Do not** retrieve more data than is necessary. Limit queries to return just the columns and rows that are necessary for the current HTTP request. + +## Pool HTTP Connections with HttpClientFactory +Although `HttpClient` implements the `IDisposable` interface, it is meant to be re-used. Closed `HttpClient` instances leave sockets open in the `TIME_WAIT` state for a short period of time. Consequently, if a code path that creates and disposes of `HttpClient` objects is used very frequently, the app may exhaust available sockets. `HttpClientFactory` was introduced in ASP.NET Core 2.1 as a solution to this problem. It handles pooling HTTP connections to optimize performance. + +* **Do not** create and dispose of `HttpClient` instances directly. +* **Do** use [`HttpClientFactory`](https://docs.microsoft.com/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) to retrieve `HttpClient` instances. + +## Keep Common Code Paths Fast +You want all of your code to be fast, of course, but some code paths are more critical than others to optimize. For example, middleware components in your app's request processing pipeline (especially those early in the pipeline) are sure to be run for every request, so they can have a large impact on app performance. Other examples would include code that is executed for every request (or multiple times per request) such as custom logging, authorization handlers, or initialization of transient services. + +* **Do not** use custom middleware components with long-running tasks. +* **Do** use performance profiling tools (like [Visual Studio Diagnostic Tools](https://docs.microsoft.com/visualstudio/profiling/profiling-feature-tour) or [PerfView](https://github.com/Microsoft/perfview)) to identify hot code paths specific to your app. + +## Complete Long-Running Tasks Outside of HTTP Requests +Most requests to an ASP.NET Core app can be handled by MVC controllers calling necessary services and returning an HTTP response. For some requests which involve long-running tasks, though, it is better to make the entire request-response process asynchronous. + +* **Do not** wait for long-running tasks to complete as part of ordinary HTTP request processing. +* **Do** consider handling long-running requests with [background services](https://docs.microsoft.com/aspnet/core/fundamentals/host/hosted-services) or out of process with an [Azure Function](https://docs.microsoft.com/azure/azure-functions/) (completing work out-of-process is especially valuable for CPU-intensive tasks). +* **Do** use real-time communication options like [SignalR](https://docs.microsoft.com/aspnet/core/signalr) to communicate with clients asynchronously. + +## Minify Client Assets +For ASP.NET Core apps with complex front-ends, it may be necessary to serve large amounts JavaScript, CSS, or image files. Performance of initial load requests can be improved in these scenarios by combining multiple files into one (bundling) and by reducing the size of those files by removing unnecessary characters (minifying). + +* **Do** use ASP.NET Core's [built-in support](https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification) for bundling and minifying client assets. +* **Do** consider other third-party tools like [Gulp](https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification#consume-bundleconfigjson-from-gulp) or [Webpack](https://webpack.js.org/) for more complex client asset management. + +## Use the Latest ASP.NET Core Releases +With every ASP.NET Core release, performance work is done. Optimtizations in .NET Core and additional ASP.NET Core performance features mean that newer versions of ASP.NET Core will outperform older versions. For example, .NET Core 2.1 addded support for compiled regular expressions and benefitted from [`Span`](https://msdn.microsoft.com/en-us/magazine/mt814808.aspx). ASP.NET Core 2.2 will bring support for HTTP/2. If performance is a priority, it may worthwhile upgrading to a recent ASP.NET Core version and taking advantage of [new performance features](TBD). \ No newline at end of file From 18e3af469d5180bc42ffe5ceb5575c37b9627d51 Mon Sep 17 00:00:00 2001 From: Mike Rousos Date: Wed, 14 Nov 2018 15:00:55 -0500 Subject: [PATCH 2/3] Minor proof-reading fixes --- .../performance/performance-best-practices.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/aspnetcore/performance/performance-best-practices.md b/aspnetcore/performance/performance-best-practices.md index 0cce6982c6f3..c48ea363a085 100644 --- a/aspnetcore/performance/performance-best-practices.md +++ b/aspnetcore/performance/performance-best-practices.md @@ -14,14 +14,14 @@ By [Mike Rousos](https://github.com/mjrousos) ASP.NET Core is a high-performance web framework, but problems in application code can still lead to slow or inconsistent response times. This document lists some common issues that may limit the performance of your ASP.NET Core app and how to avoid them. ## Avoid Blocking Calls -ASP.NET Core applications need to be able to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle hundreds or thousands of concurrent requests by not waiting for blocking calls (such as database access). Instead, the thread can work on another request while the long-running operation completes. +ASP.NET Core applications need to be able to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle hundreds or thousands of concurrent requests by not waiting on blocking calls. Instead, the thread can work on another request while the long-running task completes. A common problem in ASP.NET Core applications that aren't performing well is blocking calls that could be asynchronous. This pattern leads to ThreadPool starvation and degrading response times. * **Do not** block asynchronous execution by calling `Task.Wait()` or `Task.Result`. * **Do** make hot code paths asynchronous and call asynchronous APIs for any long-running operations (especially data access). * **Do** make controller actions asynchronous since the entire callstack needs to be asynchronous in order to benefit from async/await patterns. -* **Do not** lock on common code paths since ASP.NET Core applications need to run highly parallelized. +* **Do not** acquire locks in common code paths since ASP.NET Core applications need to run highly parallelized. An indication that blocking calls may be slowing down your app is if profiling (using a tool like [PerfView](https://github.com/Microsoft/perfview), for example) shows threads regularly being added to the ThreadPool (as indicated by the `Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start` event). There is further guidance available in the [async guidance docs](TBD-Link_To_Davifowl_Doc). @@ -41,16 +41,18 @@ Interactions with a data store or other remote services are often the slowest pa * **Do not** use 'chatty' database interactions. Instead, retrieve all the data that will be needed in a single call rather than over several. * **Do** use [no-tracking queries](https://docs.microsoft.com/ef/core/querying/tracking) in Entity Framework when accessing data in a read-only scenario. * **Do** filter and aggregate LINQ queries (with `.Where`, `.Select`, or `.Sum` statements, for example) before resolving the query so that the filtering is done by the database and the response returned to your application is smaller. -* **Do not** retrieve more data than is necessary. Limit queries to return just the columns and rows that are necessary for the current HTTP request. +* **Do not** retrieve more data than is necessary. Limit queries to return just the columns/fields and rows that are necessary for the current HTTP request. + +These issues can be detected by looking at how much time is spent accessing data using app monitoring (with [Application Insights](https://docs.microsoft.com/azure/application-insights/app-insights-overview), for example) or with profiling tools. Most databases also make statistics available concerning frequently-executed queries. ## Pool HTTP Connections with HttpClientFactory -Although `HttpClient` implements the `IDisposable` interface, it is meant to be re-used. Closed `HttpClient` instances leave sockets open in the `TIME_WAIT` state for a short period of time. Consequently, if a code path that creates and disposes of `HttpClient` objects is used very frequently, the app may exhaust available sockets. `HttpClientFactory` was introduced in ASP.NET Core 2.1 as a solution to this problem. It handles pooling HTTP connections to optimize performance. +Although `HttpClient` implements the `IDisposable` interface, it is meant to be re-used. Closed `HttpClient` instances leave sockets open in the `TIME_WAIT` state for a short period of time. Consequently, if a code path that creates and disposes of `HttpClient` objects is used very frequently, the app may exhaust available sockets. `HttpClientFactory` was introduced in ASP.NET Core 2.1 as a solution to this problem. It handles pooling HTTP connections to optimize performance and reliability. * **Do not** create and dispose of `HttpClient` instances directly. * **Do** use [`HttpClientFactory`](https://docs.microsoft.com/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests) to retrieve `HttpClient` instances. ## Keep Common Code Paths Fast -You want all of your code to be fast, of course, but some code paths are more critical than others to optimize. For example, middleware components in your app's request processing pipeline (especially those early in the pipeline) are sure to be run for every request, so they can have a large impact on app performance. Other examples would include code that is executed for every request (or multiple times per request) such as custom logging, authorization handlers, or initialization of transient services. +You want all of your code to be fast, of course, but some code paths are more critical than others to optimize. For example, middleware components in your app's request processing pipeline (especially those early in the pipeline) are sure to be run for every request, so they have a large impact on app performance. Other examples include code that is executed for every request (or multiple times per request) such as custom logging, authorization handlers, or initialization of transient services. * **Do not** use custom middleware components with long-running tasks. * **Do** use performance profiling tools (like [Visual Studio Diagnostic Tools](https://docs.microsoft.com/visualstudio/profiling/profiling-feature-tour) or [PerfView](https://github.com/Microsoft/perfview)) to identify hot code paths specific to your app. @@ -63,10 +65,10 @@ Most requests to an ASP.NET Core app can be handled by MVC controllers calling n * **Do** use real-time communication options like [SignalR](https://docs.microsoft.com/aspnet/core/signalr) to communicate with clients asynchronously. ## Minify Client Assets -For ASP.NET Core apps with complex front-ends, it may be necessary to serve large amounts JavaScript, CSS, or image files. Performance of initial load requests can be improved in these scenarios by combining multiple files into one (bundling) and by reducing the size of those files by removing unnecessary characters (minifying). +For ASP.NET Core apps with complex front-ends, it may be necessary to serve large JavaScript, CSS, or image files. Performance of initial load requests can be improved in these scenarios by combining multiple files into one (bundling) and by reducing the size of those files by removing unnecessary characters (minifying). * **Do** use ASP.NET Core's [built-in support](https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification) for bundling and minifying client assets. * **Do** consider other third-party tools like [Gulp](https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification#consume-bundleconfigjson-from-gulp) or [Webpack](https://webpack.js.org/) for more complex client asset management. ## Use the Latest ASP.NET Core Releases -With every ASP.NET Core release, performance work is done. Optimtizations in .NET Core and additional ASP.NET Core performance features mean that newer versions of ASP.NET Core will outperform older versions. For example, .NET Core 2.1 addded support for compiled regular expressions and benefitted from [`Span`](https://msdn.microsoft.com/en-us/magazine/mt814808.aspx). ASP.NET Core 2.2 will bring support for HTTP/2. If performance is a priority, it may worthwhile upgrading to a recent ASP.NET Core version and taking advantage of [new performance features](TBD). \ No newline at end of file +With every ASP.NET Core release, performance work is done. Optimtizations in .NET Core and additional ASP.NET Core performance features mean that newer versions of ASP.NET Core will outperform older versions. For example, .NET Core 2.1 addded support for compiled regular expressions and benefitted from [`Span`](https://msdn.microsoft.com/en-us/magazine/mt814808.aspx). ASP.NET Core 2.2 will bring support for HTTP/2. If performance is a priority, it may worthwhile upgrading to a recent ASP.NET Core version and taking advantage of new [performance features](TBD). \ No newline at end of file From 4a8095fc0fa291cb3cdf7b5d8262816eb9048fba Mon Sep 17 00:00:00 2001 From: Rick Anderson Date: Mon, 19 Nov 2018 16:17:38 -1000 Subject: [PATCH 3/3] Rick's changes to perf doc --- .../performance/performance-best-practices.md | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/aspnetcore/performance/performance-best-practices.md b/aspnetcore/performance/performance-best-practices.md index c48ea363a085..9c8949710422 100644 --- a/aspnetcore/performance/performance-best-practices.md +++ b/aspnetcore/performance/performance-best-practices.md @@ -9,23 +9,40 @@ uid: performance/performance-best-practices --- # ASP.NET Core Performance Best Practices +https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-pools +https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.wait?view=netframework-4.7.2 + + By [Mike Rousos](https://github.com/mjrousos) -ASP.NET Core is a high-performance web framework, but problems in application code can still lead to slow or inconsistent response times. This document lists some common issues that may limit the performance of your ASP.NET Core app and how to avoid them. +This topic provides guidelines for best practices on ASP.NET Core performance. + +## Cache aggressively + +For more information, see [Cache responses in ASP.NET Core](xref:performance/caching/index). ## Avoid Blocking Calls -ASP.NET Core applications need to be able to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle hundreds or thousands of concurrent requests by not waiting on blocking calls. Instead, the thread can work on another request while the long-running task completes. -A common problem in ASP.NET Core applications that aren't performing well is blocking calls that could be asynchronous. This pattern leads to ThreadPool starvation and degrading response times. +ASP.NET Core app should be architected to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle hundreds or thousands of concurrent requests by not waiting on blocking calls. Rather than waiting on a long-running synchronous task to complete, the thread can work on another request. + +A common performance problem in ASP.NET Core apps is blocking calls that could be asynchronous. Many synchronous blocking calls leads to [Thread Pool](https://docs.microsoft.com/en-us/windows/desktop/procthread/thread-pools) starvation and degrading response times. -* **Do not** block asynchronous execution by calling `Task.Wait()` or `Task.Result`. -* **Do** make hot code paths asynchronous and call asynchronous APIs for any long-running operations (especially data access). -* **Do** make controller actions asynchronous since the entire callstack needs to be asynchronous in order to benefit from async/await patterns. -* **Do not** acquire locks in common code paths since ASP.NET Core applications need to run highly parallelized. +**Do not**: -An indication that blocking calls may be slowing down your app is if profiling (using a tool like [PerfView](https://github.com/Microsoft/perfview), for example) shows threads regularly being added to the ThreadPool (as indicated by the `Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start` event). There is further guidance available in the [async guidance docs](TBD-Link_To_Davifowl_Doc). +* Block asynchronous execution by calling [Task.Wait](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.wait?view=netframework-4.7.2) or [Task.Result](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1.result?view=netframework-4.7.2). +* Acquire locks in common code paths. ASP.NET Core apps are most performant when architected to run highly parallelized. + + +**Do**: + +* Make frequently called or expensive code paths asynchronous. +* Call asynchronous APIs for any long-running operations (especially data access). +* Make controller/Razor Page actions asynchronous. The entire call stack needs to be asynchronous in order to benefit from [async/await](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) patterns. + +A profiler like [PerfView](https://github.com/Microsoft/perfview) can be used to look for threads frequently/regularly being added to the Thread Pool. The `Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start` event indicates a thread being added to the thread pool. For more information, see [async guidance docs](TBD-Link_To_Davifowl_Doc ). ## Be Mindful of Large Object Allocations + The [.NET garbage collector](https://docs.microsoft.com/dotnet/standard/garbage-collection/) manages allocation and release of memory automatically in .NET applications. This means that, generally, .NET developers don't need to worry about when or how memory is freed. However, cleaning up unreferenced objects takes resources (CPU time), so developers need to be careful about allocating too many objects in very hot code paths. This is especially true of large objects (>85,000 bytes) since they will be stored on the large object heap and will eventually require a full (generation 2) garbage collection to clean up. Unlike generation 0 and generation 1 collections, a generation 2 collection requires application execution to be temporarily suspended. Frequent allocation and de-allocation of large objects can cause inconsistent performance in ASP.NET Core applications. * **Do** consider caching large objects that will be frequently used so that they don't need to be re-allocated each time they're needed.