もちろん、コンパイラとコンパイラに渡されるオプションに依存します。
この特定の例では、最適化を行わずにコンパイルすると、GHC はユーザーが書いたとおりのコードを生成するため、2 番目のバージョンにはid
resp への呼び出しが含まれます。にnot
。これは、次の呼び出しのみを含む最初のバージョンよりもわずかに効率が低下しますnot
。
Xors.xor1 :: GHC.Types.Bool -> GHC.Types.Bool -> GHC.Types.Bool
[GblId, Arity=2]
Xors.xor1 =
\ (ds_dkm :: GHC.Types.Bool) (x_aeI :: GHC.Types.Bool) ->
case ds_dkm of _ {
GHC.Types.False -> x_aeI;
GHC.Types.True -> GHC.Classes.not x_aeI
}
Xors.xor2 :: GHC.Types.Bool -> GHC.Types.Bool -> GHC.Types.Bool
[GblId, Arity=1]
Xors.xor2 =
\ (ds_dki :: GHC.Types.Bool) ->
case ds_dki of _ {
GHC.Types.False -> GHC.Base.id @ GHC.Types.Bool;
GHC.Types.True -> GHC.Classes.not
}
(呼び出しはまだ生成されたアセンブリにありますが、コアの方が読みやすいので、それだけを投稿します)。
しかし、最適化を行うと、両方の関数が同じコアにコンパイルされます (したがって、同じマシン コードになります)。
Xors.xor2 =
\ (ds_dkf :: GHC.Types.Bool) (eta_B1 :: GHC.Types.Bool) ->
case ds_dkf of _ {
GHC.Types.False -> eta_B1;
GHC.Types.True ->
case eta_B1 of _ {
GHC.Types.False -> GHC.Types.True;
GHC.Types.True -> GHC.Types.False
}
}
id
GHC は 2 番目のバージョンを eta 拡張し、 andの呼び出しをインライン化したnot
ので、純粋なパターン マッチングが得られます。
2 番目の式で使用するFalse
かワイルドカードを使用するかは、最適化の有無にかかわらず、どちらのバージョンでも違いはありません。
おそらく、コンパイラはこの余分な呼び出しを最適化します。
最適化を要求すると、このような単純なケースでは、GHC は余分な呼び出しを排除します。
それが重要な関数であると想像してみましょう。
ここに考えられる問題があります。コードが十分に自明でない場合、コンパイラは、すべての引数が提供されていない関数を定義することによって導入されたすべての呼び出しを排除できない場合があります。ただし、GHCはそれを実行して呼び出しをインライン化するのがかなり得意なので、コードをコンパイルするときに知っている単純な関数の呼び出しを排除してGHCが失敗するようにするには、かなりの量の非自明性が必要です(もちろん、関数への呼び出しをインライン化することはできません)問題のモジュールをコンパイルするときの実装がわからない)。
重要なコードの場合は、コンパイラが生成するコードを常に確認してください。GHC の場合、関連するフラグは-ddump-simpl
、最適化後にコアを生成し-ddump-asm
、生成されたアセンブリを取得することです。