Thursday, September 24, 2020

How to improve the performance of ASP.Net Core applications -GOOD ONE and Simple to understand

 ASP.Net Core is a fast, lightweight, open-source, cross-platform rewrite of the ASP.Net framework that runs on Windows, Linux, and even Mac OS X. Speed is one of the key features of ASP.Net Core, meaning that the ASP.Net Core framework is optimized for better performance. Nevertheless, there are some best practices and strategies you can adopt to improve the performance of your applications that leverage the .Net Core runtime.

The essence of improving application performance is ensuring that you build applications that consume the least amount of resources to produce the desired output. This article presents a discussion of the best practices you can adopt to improve the performance of ASP.Net Core applications.

Inline methods

Inlining methods improves performance as it saves the cost of jumps, passing arguments, and saving and restoring registers. Note that a method that contains a throw statement will not be inlined by the JIT (just-in-time) compiler. To solve this, you can take advantage of a static helper method to contain the throw statement.

Minimize virtual calls

Calls to virtual members are slower because virtual calls require indirection. Devirtualization is a feature of JIT compilers and a devirtualized method can become a candidate for inlining. When the type of an object is known, RyuJIT — the JIT for .Net Core — can devirtualize non-sealed method calls. To avoid virtual calls, you can follow these guidelines:

  • Mark classes or methods as sealed by default
  • Mark overridden methods as sealed as well
  • Use concrete types in lieu of interfaces

Pool HTTP connections

Although HttpClient implements the IDisposable interface, it is advisable to reuse HttpClient instances. The reason is that HttpClient leaves the socket open and in the wait state for a short duration of time even after it has been closed. So, if you try to create new HttpClient instances every time a HttpClient is needed, you might run out of available sockets.

With this in mind, HttpClientFactory was introduced in ASP.Net Core 2.1 to pool HTTP connections. By pooling connections, HttpClientFactory optimizes performance, scalability, and reliability. So, avoid creating and destroying HttpClient instances directly, and use the HttpCLientFactory to retrieve HttpClient instances.

Reduce allocations

The introduction of new types like System.ValueTuple and Span<T> provide new ideas for improving performance. Take advantage of System.ValueTuple to reduce allocations when you are trying to return multiple values from a method. And take advantage of Span<T> to avoid array allocations and data copying.

Cache aggressively

Caching is one of the best ways to improve performance. You should cache aggressively and cache any data that is relatively stale. ASP.Net Core provides support for response caching middleware, which you can use to implement response caching. Response caching is an enhanced form of output caching. It refers to the ability to cache web server responses using cache-related headers in the HTTP response objects. You can learn more by reading my article on using response caching middleware in ASP.Net Core

You can also take advantage of a distributed cache like NCache to cache data that is relatively stale. NCache is an extremely fast and scalable in-memory distributed cache that is easy to configure and use. You can read my article on using NCache in ASP.Net Core

Enable compression

Reducing the size of the response improves the performance of the application because less data is transferred between the server and the client. You can take advantage of response compression in ASP.Net Core to shrink the response and reduce bandwidth requirements. Response compression in ASP.Net Core is available as a middleware component. The following code snippet shows how you can add response compression middleware to the request processing pipeline.

    public void ConfigureServices(IServiceCollection services) 
    { 
        services.AddResponseCompression(); 
        services.Configure<GzipCompressionProviderOptions>
        (options => 
        { 
            options.Level = CompressionLevel.Fastest; 
        }); 
    }

You can also take advantage of ASP.Net Core’s built-in support for bundling and minifying client files or assets to improve performance.

For more information, you can read my article on using response compression in ASP.Net Core

Reduce HTTP requests

Every time the web browser opens a connection to the server, there is TCP/IP overhead. A great optimization tip is reducing the number of HTTP requests. You can also take advantage of HTTP/2 — this new version of HTTP introduces some useful optimizations. You should avoid client-side redirects and cache your web pages to reduce the number of connections made to the web server. If you minify the files and then bundle them, the data will load faster and reduce the number of HTTP requests that are needed to render the web page.

Avoid blocking calls

You should avoid blocking calls as they might lead to performance degradation. If you have too many synchronous blocking calls, you might encounter a thread starvation problem. You shouldn’t block asynchronous execution by using Task.Wait or Task.Result. You should make hot code paths asynchronous — e.g., data access and long running operations should be called asynchronously. You can take advantage of performance profilers like PerfView to examine the thread behavior. For more information, you can read my article on understanding the .Net CLR thread pool

Minimize large object allocations

The .Net Core garbage collector is adept at releasing memory occupied by objects in the managed heap. However, freeing up objects can take up CPU time, especially when the objects are large objects (85 Kbytes or more). Garbage collection happens in four generations — generations 0, 1, and 2 and the large object heap (LOH). The garbage collector works much more frequently in the lower generations than in the higher ones.

Garbage collection of large objects is expensive. To minimize large object allocations, you should take advantage of pool buffers using ArrayPool<T> and cache large objects that are used frequently. You should not acquire locks on common code paths and avoid allocating too many short-lived objects on hot code paths.

Use exceptions only when necessary

It should be noted that throwing and catching exceptions is expensive. Hence, exceptions should be used only when they are needed. You should minimize the use of exceptions in your application and avoid using exception handling to control program flow. You can read my article on best practices for exception handling to learn more. 

Optimize data access

Remember that data access is one of the slowest operations in an application. You should call all data access APIs asynchronously. You should minimize roundtrips to the database and retrieve only the data that is needed. Avoid using projection queries on collections.

If you are using Entity Framework Core for data access, follow the recommended guidelines and practices in Entity Framework Core. If you are reading data that won’t be modified by the application, you should take advantage of no-tracking queries in Entity Framework Core. See my article on best practices for Entity Framework to learn how to improve Entity Framework performance. 

While web application performance tuning is complicated, there are some common techniques and recommended practices you can take advantage of to improve the performance of your ASP.Net Core applications. You can also leverage certain tools like MiniProfilerGlimpseApplication Insights, and Stackify Retrace to measure application performance, detect the performance bottlenecks, and determine the appropriate remedies to fix them. 

Avoid synchronous and use asynchronous

Try to avoid synchronous calling when developing ASP.NET Core 3.0 applications. Synchronous calling blocks the next execution until the current execution is completed. While fetching data from an API or performing operations like I/O operations or independent calling, execute the call in an asynchronous manner.

Avoid using Task.Wait and Task.Result, and try to use await. The following code shows how to do this.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public class WebHost
{
    public virtual async Task StartAsync(CancellationToken cancellationToken = default)
    {
 
        // Fire IHostedService.Start
        await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
 
        // More setup
        await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
 
        // Fire IApplicationLifetime.Started
        _applicationLifetime?.NotifyStarted();
 
        // Remaining setup
    }
}

Entity Framework 3.0 Core also provides a set of async extension methods, similar to LINQ methods, that execute a query and return results.

Asynchronous querying

Asynchronous queries avoid blocking a thread while the query is executed in the database. Async queries are important for quick, responsive client applications.

Examples:

  • ToListAsync()
  • ToArrayAsync()
  • SingleAsync()
1
2
3
4
5
6
7
public async Task<List> GetBlogsAsync()
{
    using (var context = new BloggingContext())
    {
        return await context.Blogs.ToListAsync();
    }
}

Asynchronous saving

Asynchronous saving avoids a thread block while changes are written to the database. It provides DbContext.SaveChangesAsync() as an asynchronous alternative to DbContext.SaveChanges().

1
2
3
4
5
6
7
8
9
public static async Task AddBlogAsync(string url)
{
    using (var context = new BloggingContext())
    {
        var blogContent = new BlogContent { Url = url };
        context.Blogs.Add(blogContent);
        await context.SaveChangesAsync();
    }
}

Optimize data access

Improve the performance of an application by optimizing its data access logic. Most applications are totally dependent on a database. They have to fetch data from the database, process the data, and then display it. If it is time-consuming, then the application will take much more time to load.

Recommendations:

  • Call all data access APIs asynchronously.
  • Don’t try to get data that is not required in advance.
  • Try to use no-tracking queries in Entity Framework Core when accessing data for read-only purposes.
  • Use filter and aggregate LINQ queries (with .Where.Select, or .Sum statements), so filtering can be performed by the database.

You can find approaches that may improve performance of your high-scale apps in the new features of EF Core 3.0.

Use caching technology

Increase the performance of an application by reducing the number of requests to the server. Avoid calling the server every time and cache the data instead. Store the response for the future, and use it the next time you make a call for the same response.

These are some caching techniques:

  • In-memory caching.
  • Distributed cache.
  • Cache tag helper.
  • Distributed cache tag helper.

Use response caching middleware

Middleware controls when responses are cacheable. It stores responses and serves them from the cache. It is available in the Microsoft.AspNetCore.ResponseCaching package, which was implicitly added to ASP.NET Core.

In Startup.ConfigureServices, add the Response Caching Middleware to the service collection.

1
2
3
4
5
public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCaching();
    services.AddRazorPages();
}

Use JSON serialization

ASP.NET Core 3.0 uses System.Text.Json for JSON serialization by default. Now, you can read and write JSON asynchronously. This improves performance better than Newtonsoft.Json. The System.Text.Json namespace provides the following features for processing JSON:

  • High performance.
  • Low allocation.
  • Standards-compliant capabilities.
  • Serializing objects to JSON text and deserializing JSON text to objects.

Reduce HTTP requests

Reducing the number of HTTP requests is one of the major optimizations. Cache the webpages and avoid client-side redirects to reduce the number of connections made to the web server.

Use the following techniques to reduce the HTTP requests:

  1. Use minification.
  2. Use bundling.
  3. Use sprite images.

By reducing HTTP requests, these techniques help pages load faster.

Use exceptions only when necessary

Exceptions should be rare. Throwing and catching exceptions will consume more time relative to other code flow patterns.

  • Don’t throw and catch exceptions in normal program flow.
  • Use exceptions only when they are needed.

Use response compression

Response compression, which compresses the size of a file, is another factor in improving performance. In ASP.NET Core, response compression is available as a middleware component.

Usually, responses are not natively compressed. This typically includes CSS, JavaScript, HTML, XML, and JSON.

  • Don’t compress natively compressed assets, such as PNG files.
  • Don’t compress files smaller than about 150-1000 bytes

(sourcehttps://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AspNetCoreGuidance.md
Package: Microsoft.AspNetCore.ResponseCompression is implicitly included in ASP.NET Core apps.

The following sample code shows how to enable Response Compression Middleware for the default MIME types and compression providers.

01
02
03
04
05
06
07
08
09
10
11
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddResponseCompression();
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseResponseCompression();
    }
}

These are the providers:

01
02
03
04
05
06
07
08
09
10
11
12
public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression(options =>
    {
        options.Providers.Add<BrotliCompressionProvider>();
        options.Providers.Add<GzipCompressionProvider>();
        options.Providers.Add<CustomCompressionProvider>();
        options.MimeTypes =
            ResponseCompressionDefaults.MimeTypes.Concat(
                new[] { "image/svg+xml" });
    });
}

HttpContext accessibility improvements

HttpContext accessibility is only valid as long as there is an active HTTP request in ASP.NET Core. Here are some suggestions for accessing HttpContext from Microsoft’s documentation:

Client-side improvements

Client-side optimization is one important aspect of improving performance. When creating a website using ASP.Net Core, consider the following tips:

Bundling

Bundling combines multiple files into a single file, reducing the number of server requests. You can use multiple individual bundles in a webpage.

Minification

Minification removes unnecessary characters from code without changing any functionality, also reducing file size. After applying minification, variable names are shortened to one character and comments and unnecessary whitespace are removed.

Loading JavaScript at last

Load JavaScript files at the end. If you do that, static content will show faster, so users won’t have to wait to see the content.

Use a content delivery network

Use a content delivery network (CDN) to load static files such as images, JS, CSS, etc. This keeps your data close to your consumers, serving it from the nearest local server

No comments:

Post a Comment