超絶マイナーネタ。現在進行形のお話。
C#で再帰する奴ー
.NETのx64JITには末尾再帰の最適化があります。
再帰してもコールスタックを消費しないすごいやつです。
これを全力で利用しまくったライブラリを書いてたんですが、そのときハマった話。
こんなコード
// ちょっとしたstruct
public struct AStruct
{
public int Value;
public AStruct(int x)
{
this.Value = x;
}
}
// 末尾再帰する関数
private static AStruct RecA(AStruct a)
{
//System.Console.WriteLine(new System.Diagnostics.StackTrace().FrameCount); // コールスタックのカウントだせるやつ
return (a.Value <= 0) ? a : RecA(new AStruct(a.Value - 1));
}
// よびだすやつ
internal static void Main(string[] args)
{
RecA(new AStruct(100000000));
}
.NETのtail-call optimizationを有効にするには、x64環境でReleaseビルドでコンパイルします。
実行すると、華麗にStackOverflowException
で死にます。んんん、だめじゃん。
// classにしてみた
public class BClass
{
public int Value;
public BClass(int x)
{
this.Value = x;
}
}
// 末尾再帰する関数
private static BClass RecB(BClass b)
{
//System.Console.WriteLine(new System.Diagnostics.StackTrace().FrameCount); // コールスタックのカウントだせるやつ
return (b.Value <= 0) ? b : RecB(new BClass(b.Value - 1));
}
// よびだすやつ
internal static void Main(string[] args)
{
RecB(new BClass(100000000));
}
これは正常に完了します。あれー???
RyuJITのバグ???
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<runtime>
<useLegacyJit enabled="1" />
</runtime>
</configuration>
App.config
にuseLegacyJit
を追加して値を1
にするとRyuJITの使用を無効化できます。
すると上のコードはAもBも正常に動くようになります。
これは一体……?
まだ検証中なの
上のコードのそのまま動くやつをとりあえずここに置きました。
今はRyuJITの仕様とかissueとかを探ってるんですけど、英語が全く読めないので死んでます。
何か関連する情報があったらくださいおねがいします。