Linqは内部でyieldを使っていますがyieldはコンテキストスイッチを行いますので、そのパフォーマンスはどうなのか調べてみました。
yieldのパフォーマンス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Windows.Forms;
namespace Project1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
public TimeSpan MeasureTime(Action action) { var sw = new Stopwatch(); try { sw.Start(); action(); } finally { sw.Stop(); } return sw.Elapsed; }
public IEnumerable<int> GetIntegerEnumerator() { for (var i = 0; i < 10000000; i++) { yield return i; } }
void test1() { foreach (var i in GetIntegerEnumerator()) { var x = i; } }
class Test2 : IEnumerator<int> { int _current = 0;
public int Current { get { return _current; } }
public void Dispose() {
}
object System.Collections.IEnumerator.Current { get { return _current; } }
public bool MoveNext() { _current++; return _current<10000000; }
public void Reset() { _current = 0; } }
void test2() { foreach (var i in new Test2().ToEnumerable()) { var x = i; } }
private void button1_Click(object sender, EventArgs e) { for (var i=0; i<5; i++) { Console.WriteLine(MeasureTime(test1)); } }
private void button2_Click(object sender, EventArgs e) { for (var i = 0; i < 5; i++) { Console.WriteLine(MeasureTime(test2)); } } }
public static class EnumeratorExtensions { private class EnumerableConverter<T> : IEnumerable<T> { IEnumerator<T> _enumerator;
public EnumerableConverter(IEnumerator<T> enumerator) { _enumerator = enumerator; }
public IEnumerator<T> GetEnumerator() { return _enumerator; }
IEnumerator IEnumerable.GetEnumerator() { return _enumerator; } }
public static IEnumerable<T> ToEnumerable<T>(this IEnumerator<T> enumerator) { return new EnumerableConverter<T>(enumerator); } }
}
|
リストではGetIntegerEnumeratorで整数を一千万回発生させ受け側test1ではforeachでごく軽い処理を行っています。
その結果は約0.7秒でした。
- 00:00:00.6699127
- 00:00:00.7322575
- 00:00:00.6883833
- 00:00:00.6887978
- 00:00:00.7158395
同等の処理をクラスで実現すると、0.5秒でした。
- 00:00:00.5362418
- 00:00:00.5300212
- 00:00:00.5566201
- 00:00:00.5479106
- 00:00:00.5479031
マシンスペックはAMD Athlon(tm) X2 Dual Core Processor BE-2350 2.11GHz、2.00 GB RAM、OSはWin
XP SP3 32Bitです。Visual Studio 2008でデバッグビルドしました。
yieldはクラスに比べると若干遅いのですが、ソースファイルやHTMLファイルのフィルタリング処理を行う程度であれば極端にパフォーマンスを落とすこともなさそうです。
バッファリングによってコンテキストスイッチの回数を減らすテストプログラムを書こうとしたのですが、複雑でパフォーマンスが上がりそうもなかったので中止しました。
その後、Reflectorで生成されたコードを分析すると、yield returnはクラスによって状態を保持するコードに置き換えられることがわかりました。私はWinAPIのファイバーの様な仕組みを想像していましたが違いました。傾向としてyield returnの記述が多ければ生成されたコードのswitchの選択肢が増えますのでパフォーマンスは悪くなります。