LINQの最適化について調査してみた

こんにちは。エンジニアの今井です。

コードを書いているとき、可読性のために以下のように冗長に書くことは少なくないと思います。

そこで今回は、冗長に書いても最適化されることを確認してみました。

ちなみに、利用している.NETのVerは.NET5です。

1. 例題

今回はLINQを分割してみました。

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {
            var userList = new List<User> {
                new User(0, "hoge", 35),
                new User(1, "foo", 21),
                new User(2, "piyo", 48)
                };

            var targetUser = GetUser(userList);

            Console.WriteLine(targetUser.Id);
        }

        // 冗長化あり
        static User GetUser(List<User> userList)
        {
            var nameList = userList.Where(u => u.Name.Length > 3);
            var ageList = nameList.Where(u => u.Age > 40);
            var result = ageList.SingleOrDefault();
            return result;
        }
        
        //  冗長化なし
        /*
        static User GetUser(List<User> userList)
        {
            var result = userList.Where(u => u.Name.Length > 3).Where(u => u.Age > 40).SingleOrDefault();
            return result;
        }
        */
    }

    class User
    {
        public int Id { get; }
        public string Name { get; }
        public int Age { get; }

        public User(int id, string name, int age)
        {
            Id = id;
            Name = name;
            Age = age;
        }
    }
}

こういうコードは最適化されるはずだと思っていたのですが、実際に確認したことがなかったので今回はそれを確認してみました。

2. 確認するもの

今回はすべてReleaseモードとし、ビルド時の最適化オプションの有無で最適化の条件を確認しました。

最適化の確認にはILを確認できるildasm.exeを利用します。

3. 確認結果

確認したものは①冗長あり・最適化なし、②冗長あり・最適化あり、③冗長なし・最適化なし、④冗長なし・最適化ありの四つです。

①冗長あり・最適化なし

.method private hidebysig static class ConsoleApp5.User 
        GetUser(class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp5.User> userList) cil managed
{
  // コード サイズ       90 (0x5a)
  .maxstack  3
  .locals init (class [System.Runtime]System.Collections.Generic.IEnumerable`1<class ConsoleApp5.User> V_0,
           class [System.Runtime]System.Collections.Generic.IEnumerable`1<class ConsoleApp5.User> V_1,
           class ConsoleApp5.User V_2,
           class ConsoleApp5.User V_3)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0007:  dup
  IL_0008:  brtrue.s   IL_0021
  IL_000a:  pop
  IL_000b:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0010:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_0'(class ConsoleApp5.User)
  IL_0016:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_001b:  dup
  IL_001c:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0021:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0026:  stloc.0
  IL_0027:  ldloc.0
  IL_0028:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_002d:  dup
  IL_002e:  brtrue.s   IL_0047
  IL_0030:  pop
  IL_0031:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0036:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_1'(class ConsoleApp5.User)
  IL_003c:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_0041:  dup
  IL_0042:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_0047:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_004c:  stloc.1
  IL_004d:  ldloc.1
  IL_004e:  call       !!0 [System.Linq]System.Linq.Enumerable::SingleOrDefault<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_0053:  stloc.2
  IL_0054:  ldloc.2
  IL_0055:  stloc.3
  IL_0056:  br.s       IL_0058
  IL_0058:  ldloc.3
  IL_0059:  ret
} // end of method Program::GetUser

②冗長あり・最適化あり

.method private hidebysig static class ConsoleApp5.User 
        GetUser(class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp5.User> userList) cil managed
{
  // コード サイズ       79 (0x4f)
  .maxstack  3
  IL_0000:  ldarg.0
  IL_0001:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_0020
  IL_0009:  pop
  IL_000a:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_000f:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_0'(class ConsoleApp5.User)
  IL_0015:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_001a:  dup
  IL_001b:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0020:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0025:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_002a:  dup
  IL_002b:  brtrue.s   IL_0044
  IL_002d:  pop
  IL_002e:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0033:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_1'(class ConsoleApp5.User)
  IL_0039:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_003e:  dup
  IL_003f:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_0044:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0049:  call       !!0 [System.Linq]System.Linq.Enumerable::SingleOrDefault<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_004e:  ret
} // end of method Program::GetUser

①冗長なし・最適化なし

.method private hidebysig static class ConsoleApp5.User 
        GetUser(class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp5.User> userList) cil managed
{
  // コード サイズ       86 (0x56)
  .maxstack  3
  .locals init (class ConsoleApp5.User V_0,
           class ConsoleApp5.User V_1)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0007:  dup
  IL_0008:  brtrue.s   IL_0021
  IL_000a:  pop
  IL_000b:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0010:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_0'(class ConsoleApp5.User)
  IL_0016:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_001b:  dup
  IL_001c:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0021:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0026:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_002b:  dup
  IL_002c:  brtrue.s   IL_0045
  IL_002e:  pop
  IL_002f:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0034:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_1'(class ConsoleApp5.User)
  IL_003a:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_003f:  dup
  IL_0040:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_0045:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_004a:  call       !!0 [System.Linq]System.Linq.Enumerable::SingleOrDefault<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_004f:  stloc.0
  IL_0050:  ldloc.0
  IL_0051:  stloc.1
  IL_0052:  br.s       IL_0054
  IL_0054:  ldloc.1
  IL_0055:  ret
} // end of method Program::GetUser

①冗長なし・最適化あり

.method private hidebysig static class ConsoleApp5.User 
        GetUser(class [System.Collections]System.Collections.Generic.List`1<class ConsoleApp5.User> userList) cil managed
{
  // コード サイズ       79 (0x4f)
  .maxstack  3
  IL_0000:  ldarg.0
  IL_0001:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0006:  dup
  IL_0007:  brtrue.s   IL_0020
  IL_0009:  pop
  IL_000a:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_000f:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_0'(class ConsoleApp5.User)
  IL_0015:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_001a:  dup
  IL_001b:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_0'
  IL_0020:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0025:  ldsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_002a:  dup
  IL_002b:  brtrue.s   IL_0044
  IL_002d:  pop
  IL_002e:  ldsfld     class ConsoleApp5.Program/'<>c' ConsoleApp5.Program/'<>c'::'<>9'
  IL_0033:  ldftn      instance bool ConsoleApp5.Program/'<>c'::'<GetUser>b__1_1'(class ConsoleApp5.User)
  IL_0039:  newobj     instance void class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool>::.ctor(object,
                                                                                                             native int)
  IL_003e:  dup
  IL_003f:  stsfld     class [System.Runtime]System.Func`2<class ConsoleApp5.User,bool> ConsoleApp5.Program/'<>c'::'<>9__1_1'
  IL_0044:  call       class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0> [System.Linq]System.Linq.Enumerable::Where<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                                              class [System.Runtime]System.Func`2<!!0,bool>)
  IL_0049:  call       !!0 [System.Linq]System.Linq.Enumerable::SingleOrDefault<class ConsoleApp5.User>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
  IL_004e:  ret
} // end of method Program::GetUser

以上から、最適化なしだと差が出ますが、最適化ありだと差が出ないことがわかりました。

おわりに

今回は中間変数を置いていても最適化が入っていることがわかりました。そのため、可読性のために中間変数を置くことに罪の意識を持たなくて良さそうでよかったです。

関連記事

プロジェクトストーリー

技術

コメント

この記事へのコメントはありません。

カテゴリー

TOP
TOP