dotnet C# calls the object assignment of the delegated GetInvocationList

Posted by deansp2001 on Sat, 25 Dec 2021 03:19:30 +0100

This article is also called learning performance optimization series with Stephen Toub. This is what I learned from Stephen Toub's performance optimization for WPF framework. Under the hot path, that is, for frequently called modules, if the delegated GetInvocationList method is called, new array objects of different sizes will be created each time depending on the size of the delegate, and in frequently called modules, A large number of objects will be created

As a delegate of the following code, of course, the same is true for events

            Action action = Foo;
            for (int i = 0; i < 10; i++)
            {
                action += Foo;
            }

            static void Foo()
            {

            }

If the GetInvocationList method of action is called, some memory will be applied for each call. For example, use the following code for testing

            for (int i = 0; i < 100; i++)
            {
                var beforeAllocatedBytesForCurrentThread = GC.GetAllocatedBytesForCurrentThread();
                var invocationList = action.GetInvocationList();
                var afterAllocatedBytesForCurrentThread = GC.GetAllocatedBytesForCurrentThread();
                Console.WriteLine(afterAllocatedBytesForCurrentThread - beforeAllocatedBytesForCurrentThread);
            }

Getallocatedbytes forcurrentthread in the above code is a method at the GC level, which can be used to obtain the memory allocated by the current thread. This is a method used to assist debugging. For details, please see dotnet uses GC Getallocatedbytes forcurrentthread gets the memory size allocated by the current thread

You can see the console output at runtime as follows

312
112
112
112
112
112
112
112
112
112
112
112
// No water

This is because in the underlying implementation, the code for calling the GetInvocationList method is as follows

    public override sealed Delegate[] GetInvocationList()
    {
      Delegate[] delegateArray;
      if (!(this._invocationList is object[] invocationList))
      {
        delegateArray = new Delegate[1]{ (Delegate) this };
      }
      else
      {
        delegateArray = new Delegate[(int) this._invocationCount];
        for (int index = 0; index < delegateArray.Length; ++index)
          delegateArray[index] = (Delegate) invocationList[index];
      }
      return delegateArray;
    }

You can see that you need to re apply for the array every time, and then give the elements in the array. If the GetInvocationList method is called repeatedly in modules that call frequently, there will be a certain performance loss. For example, in WPF's mobile mouse and other logic

An optimization method is that if the specified delegate or event is incremented less than the number of calls to GetInvocationList, such as the pre notifyinput of WPF, it can be cached during incrementing, so that subsequent calls do not need to reallocate memory

For details of the above optimization, please see Avoid calling GetInvocationList on hot paths by stephentoub · Pull Request #4736 · dotnet/wpf

All the code in this article github and gitee Welcome to visit

You can obtain the source code of this article in the following ways. First create an empty folder, then use the command line cd command to enter this empty folder, and enter the following code on the command line to obtain the code of this article

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6ed312e74e286d581e3d609ed555447474259ae4

The above uses the source of gitee. If gitee cannot access it, please replace it with the source of github

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

After getting the code, go to the FairhojafallJeeleefuyi folder

This article will be updated frequently. Please read the original text: https://blog.lindexi.com/post/dotnet-C-%E8%B0%83%E7%94%A8%E5%A7%94%E6%89%98%E7%9A%84-GetInvocationList-%E7%9A%84%E5%AF%B9%E8%B1%A1%E5%88%86%E9%85%8D.html In order to avoid the misleading of old wrong knowledge and have a better reading experience.

This work adopts Knowledge sharing Attribution - non-commercial use - sharing in the same way 4.0 international license agreement License. Welcome to reprint, use and republish, but be sure to keep the signed Lin Dexi (including link: https://blog.lindexi.com ), shall not be used for commercial purposes, and the works modified based on this article must be distributed under the same license.