How to write the fast code/ high performance in C#

Performance Techniques for C# 4.0

Anthony Moore

Contents

Contents.1

Overview..1

Patterns by Construct.1

Cheat Sheet.1

Collection Interfaces (IEnumerable<T>,ICollection<T>, IList<T>).2

Internal and Semi-Public APIs.2

Public APIs.3

Linq for Collections.3

ElementAt() and Last().3

Count().4

First().4

ToArray().5

Select without Where and ToArray().5

Overview

This document is a collection of techniques for writing thefastest possible C# code. Measurements and features are specific to C# and .NET4.0 and may not apply to other versions.

Patterns by Construct

This is a commentary of performance patterns group by thetype of construct or API. It includes some patterns to avoid, and cases wherethere are multiple ways to write something that have similar readability andmaintenance but different performance.

Cheat Sheet

A very high-level summary of the content below.

Use IList<T> for collection declaration for internal and  “semi-public” API rather than ICollection<T> or IEnumerable<T>.

Use IEnumerable<T> for inputs and Collection<T> for  outputs for “fully” public APIs.

Avoid use of the Linq helpers ElementAt(), First(), Last() and  Count().

Be careful with excessive use of ToArray() on collections and Linq  queries.

Avoid use of the pattern “collection.Select(expression).ToArray()”,  where no filtering is needed. There is analternative  helper for mapping that is much faster.

Collection Interfaces (IEnumerable<T>,ICollection<T>, IList<T>)

Sometimes use:

IEnumerable<T>

Rarely use:

ICollection<T>

Favor:

IList<T>

It is worth understanding collections in detail, but somequick rules of thumb that will lead to a good balance between speed andflexibility:

Case

Declarations

Internal APIs or Public APIs not parted of a supported and versioned  framework

IList<T> for inputs, return values and properties

Public versioned framework APIs

Collection<T>  or  ReadOnlyCollection<T> for return values and properties.

IList<T> for inputs if you need to index into the results.

IEnumerable<T> for inputs if you don’t need to index.

Note: there is an FxCop rule that indicates thatCollection<T> is preferred over List<T> in APIs. This can causepeople to favor ICollection<T> over IList<T>. The names arecounter-intuitive in this case, as the concrete types and interfaces don’t havethe same relative relationship to each other. Collection<T> is anindexable collection that versions better than List<T>, butICollection<T> is a writable collection without indexing support, whichis much slower than IList<T> if indexing is actually needed.

Internal and Semi-Public APIs

If in doubt, default to IList<T> for APIs that have aninternal audience.

A pattern that can create code that is significantly slowerthan necessary is to define internal collections with copies as data as eitherIEnumerable<T> or ICollection<T>, particularly when the underlyingdata structures are actually Array or List<T>. Because these interfacesdon’t support indexes, iteration is done with the slower IEnumerator pattern.It can cause extreme slowdown if Linq APIs like ElementAt(int), Count() andLast() are used against the interfaces, because these APIs either simulatingindexing by looping through the enumerator, or they have to cast and unpack tospeed up the special case, which still has considerable overhead.

As types and APIs are used you may find a need to pass innon-indexable constructs such as Linq queries. In these cases you can change tothe weaker IEnumerable<T> as needed, providing your use of the collectiondoes not involve indexing. If you do need to index into an input from a Linqquery it is often faster to leave it as IList<T> and work on a copy ofthe data. While Linq provides helpers to allow you to index into anIEnumerable<T>, these helpers are extremely slow because they simulatethe indexing by iterating.

Declaring collections in internal APIs as Array is veryslightly faster than IList<T>. However, baking in the specific typeremoves the flexibility of changing the collection type without refactoringcosts. So a good rule of thumb is to use IList<T> by default and considerchanging to array in very hot code-paths only.

Public APIs

For public APIs shipped to customers and versioned, it isnot as simple as using IList<T> everywhere. In this case a good rule ofthumb is:

“Return the strongest thing you have. Require theweakest thing you need”.

So for public APIs as inputs such as method arguments,declaring as IEnumerable<T> adds the flexibility of passing in Linqqueries and can be a good tradeoff providing you only loop through once andnever index in. If your API does need to use indexing, it is usually muchbetter to declare the input as IList<T> and let the caller decide if theywant to make a copy than to use Linq helpers to simulate indexing.

Similarly, it gives most flexibility to and users ifproperties and return values have more powerful collection type likeCollection<T>, which has many helpers on it and is designed to beversioned over time if you want to swap out the underlying store.

Linq for Collections

Linq has many constructs and helpers for day to day use ofcollections. Some of these can significantly improve expression, readabilityand maintenance at little or no effective performance cost. However, many ofthe constructs are dangerous from a performance perspective, making easy to unintentionallywrite code significantly slower than the equivalent non-Linq constructs thatdon’t express in a significantly different way.

ElementAt() and Last()

Never Use:

value = myCollection.ElementAt(n);

value = myCollection.Last();

Favor:

value = myCollection[n];

value = myCollection[myCollection.Count- 1];

For a codebase where performance matters it is best tocompletely eliminate these Linq helpers.

If they are needed it is usually a sign that the collectionshould either be typed as IList<T>, the indexing should be avoided, orthe input should be copied first.

These are Linq helper APIs that allow you to index in to anyIEnumerable<T>. For things like Linq queries, they force a re-evalationof the query each time to navigate to the specific value, which is highoverhead and at least O(N) per call. Since these are often inside loops, thiscan easily lead to quadratic time or worse.

If the underlying collection is actually an IList<T>or Array, these APIs can detect this and avoid the O(N) call. However, theconstant overhead of the functions has been measured at about ~5X that of theunderlying indexed operation, and so even this should be avoided.

Count()

Never use:

count = myCollection.Count();

Favor:

count = myCollection.Count;

The regular Count with no parenthesis is a property get andthe collection being called stores its size, so it is a fast constant timeoperation. The call Count() with parenthesis is a Linq helper that simulatesthis by walking the whole enumerable. This is a high overhead linear timeoperation (O(N)).

Count() is O(1) if the underlying collection has a count,but even in this case it is about 5X slower than the direct call. So this shouldbe avoided.

First()

Rarely use

Value =collection.First();

Favor:

Value = collection[0];

First() is a simulated indexing operation like ElementAt()and Last(). It’s use is not as serious as the others because it is constanttime O(1). But it is still fairly high overhead operation that can add up iffrequently used.

If the collection is a pure enumerable like a Linq this issometimes appropriate. But it still incurs much of the fixed overhead of a fullenumeration. In most cases this is a sort of sniff to make a decision prior toa full enumeration, and there is often a way to avoid doing the

If the underlying collection is indexable IList<T> orarray, it will be about 5X faster to call the indexer.

ToArray()

Carefully use:

            value =collection.ToArray();

It is often necessary to use ToArray() where there is nobetter alterantive, but it should be handled with care and considered anexpensive operation that can lead to bottlenecks. Creating an array has highoverhead even if the array is small. If the amount of data is large, the timeand memory cost of making the copy can be higher than if there was a way toconsume the data from its original source.

Select without Where and ToArray()

Never use:

  result = list.Select(e=> e.Inner).ToArray();

Favor (common code paths):

    result =Helers.MapList(list, (OuterType o) =>o.Inner);

Where MapList is a simple helper:

        public Dest[] MapList<Source, Dest>(IList<Source> source,Func<Source, Dest> map)

        {

            int size = source.Count;

           Dest[] result = new Dest[size];

            for (int i = 0; i< size; i++)

            {

               result[i] = map(source[i]);

            }

            return result;

        }

Alternatively, Array.ConvertAll can be used if the input isalready typed as array.

For very hot code-paths consider putting the full codein-line:

   result = new InnterType[list.Count];

   for (int i = 0; i< list.Count; i++)

   {

      result[i] = list[i].Inner;

   }

The pattern of using a Linq select statement to take anenumerable to create an array that has a simple map of another list is muchslower than constructs with similar readability and maintenance. The MapListhelper above is ~5-10X faster than the Linq equivalent depending on in theinput size, and has similar code expression. Array.ConvertAll can only be usedif the input is an array, but is about 20% faster still.

For very hot code-paths, fully in-lining this mapping is afurther 25% faster than the  helperbecause it avoid the delegate calls. And if this can be combined with the inputbeing declared as an array it can be 50% faster still, for a total of ~20xfaster than the Linq version. But this takes significantly more code, so thisshould only be used in bottlenecks.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值