TECH MEDIA

テックメディア


ノウハウ
ブログ

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

.NETC#
目次
  1. 01|はじめに
  2. 02|作ったもの
  3. 03|解説
  4. 04|おわりに

1. はじめに

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

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

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

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

2. 作ったもの

今回は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;
}
}
}

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

 

3. 解説

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

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

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

①冗長あり・最適化なし


.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

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

4. おわりに

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

RECRUIT 採用情報

「eビジネスに関わる全ての人を幸せにする」
私達とともに新たな時代をつくりませんか?