2016-10-11 6 views
6

最近、私はRust(関数内の関数)でローカル関数を作成できることに気付きました。ファイルの関数空間を汚染することなく、コードをクリーンアップする良い方法のようです。私は「外部」機能対地元の機能により、以下の意味を少量のサンプル:Rustでローカル関数を使用することにパフォーマンス面でマイナスの影響はありませんか?

fn main() { 
    fn local_plus(x: i64, y: i64) -> i64 { 
     x + y 
    } 
    let x = 2i64; 
    let y = 5i64; 

    let local_res = local_plus(x, y); 
    let external_res = external_plus(x,y); 
    assert_eq!(local_res, external_res); 
} 

fn external_plus(x: i64, y: i64) -> i64 { 
    x + y 
} 

これを行うのいずれかの負のパフォーマンスの意味合いがある場合、私は不思議でしたか?同様に、Rustは、関数を再宣言するか、または包含する関数が実行されるたびに望ましくない量の関数空間を取り込みますか?それとも文字通りパフォーマンスの意味はありませんか?

私は自分の答えを見つけ出す方法についてのヒントを、(特定のドキュメントを読むか、私が使用できるツールを使って)歓迎します。

+1

ベンチマークを実行したり、MIR /アセンブリ出力を確認したりして、自分で見つけることができます。 – ljedrz

+0

@ljedrz:ベンチマークは気まぐれな獣です。最適化されていない最適化されたMIR/LLVM IR /アセンブリを最もよく確認してください。 @MatthieuM。 –

+0

。ベンチマークは、他の方法がもはや実用的ではないより複雑なケースに適しています。 – ljedrz

答えて

5

影響はありません。私は両方のバリアントについて生成されたアセンブリをチェックし、それは同一です。

私が比較される2つのバージョン:

"外部":

fn main() { 
    let x = 2i64; 
    let y = 5i64; 

    let external_res = external_plus(x,y); 
} 

fn external_plus(x: i64, y: i64) -> i64 { 
    x + y 
} 

"ローカル":

fn main() { 
    fn local_plus(x: i64, y: i64) -> i64 { 
     x + y 
    } 
    let x = 2i64; 
    let y = 5i64; 

    let local_res = local_plus(x, y); 
} 

そして、両方が同じASM結果(今日の毎晩でリリースモード)を得:

.text 
    .file "rust_out.cgu-0.rs" 
    .section .text._ZN8rust_out4main17hb497928495d48c40E,"ax",@progbits 
    .p2align 4, 0x90 
    .type _ZN8rust_out4main17hb497928495d48c40E,@function 
_ZN8rust_out4main17hb497928495d48c40E: 
    .cfi_startproc 
    retq 
.Lfunc_end0: 
    .size _ZN8rust_out4main17hb497928495d48c40E, .Lfunc_end0-_ZN8rust_out4main17hb497928495d48c40E 
    .cfi_endproc 

    .section .text.main,"ax",@progbits 
    .globl main 
    .p2align 4, 0x90 
    .type main,@function 
main: 
    .cfi_startproc 
    movq %rsi, %rax 
    movq %rdi, %rcx 
    leaq _ZN8rust_out4main17hb497928495d48c40E(%rip), %rdi 
    movq %rcx, %rsi 
    movq %rax, %rdx 
    jmp [email protected] 
.Lfunc_end1: 
    .size main, .Lfunc_end1-main 
    .cfi_endproc 


    .section ".note.GNU-stack","",@progbits 

これは、生成されたバイナリに(パフォーマンスだけでなく)ゼロ差があることを意味します。

さらに、関数を使用するかどうかは関係ありません。次のアプローチ:

fn main() { 
    let x = 2i64; 
    let y = 5i64; 

    let res = x + y; 
} 

同じアセンブリが生成されます。

最終的には、main()に宣言するかどうかに関係なく、関数はインライン化されます。

編集:Shepmasterが指摘したように、このプログラムではそこには副作用ではないので、両方の変形のために生成されたアセンブリは、実際に1つのと同じです:

fn main() {} 

しかし、MIR出力両方とも同じであり(ブランクの場合とは異なります)、副作用があっても関数の場所からの違いはありません。

+2

私はかなりのオプティマイザは、観察可能な副作用がないので、プログラム内のすべてを削除していると確信しています。 'res'は完全に使用されていないので、' fn main(){} 'と同じものを見ると思います。 – Shepmaster

+0

@シェマーマスターTIL :)。私はこれを答えに加えます。 – ljedrz

6

私が自分の答えを見つけ出す方法についてのヒントは、(特定のドキュメントセットを読むか、私が使用できるツールを使用するかによって)歓迎されます。

あなたはthe Rust playgroundを知っていますか?

"実行"の代わりに "LLVM IR"、 "アセンブリ"または "MIR"をクリックすると、そのコードに対して発行された低レベル表現が表示されます。

私は個人的にLLVM IR(私はC++からの読み込みに慣れています)を好んでいますが、これは依然として投稿の言語ですが、まだアセンブリよりかなり高いレベルです。

これを実行することに悪影響があるかどうかは疑問でしたか?

これは非常に複雑な質問です。実際にさびにローカルまたは外部関数を宣言間

唯一の違いスコープの一つです。それをローカルに宣言するだけで、その範囲が縮小されます。 他にはありません

しかし...スコープと使用法は、コンパイルに大きな影響を与える可能性があります。

たとえば、1回だけ使用される関数は、10回使用される関数よりもはるかにインライン化されます。コンパイラは、pub関数(無制限)の使用回数を簡単に見積もることはできませんが、ローカルまたは非pub関数についての完全な知識があります。また、関数がインライン化されているかどうかにかかわらず、パフォーマンスのプロファイルに悪影響を与える可能性があります(悪化またはそれ以上)。

したがって、スコープを小さくして、使用法を制限することで、インライン化のための関数を考慮することをコンパイラに推奨します(ただし、 "cold"とマークしない限り)。

一方、スコープが縮小されているため、共有することはできません(明らかに)。

だから何ですか?

可能な限り厳密な範囲で項目を定義します。

これはカプセル化です:次回この修正が必要なときは、影響を受ける範囲を正確に知ることができます。

Rustを信頼していれば、回避できる場合はオーバーヘッドが発生しません。

+0

すばらしい答え!マイナーニックピック:おそらく、直接効果がないことを強調したいかもしれません。たとえば、Javaには、自動的に外部のものへの参照を持ついくつかの種類の「内部クラス」があります。 Rustクロージャー(簡単に言えば)と同じ。だから私は内部関数がその囲む関数から何も参照しておらず、囲み関数のスタック空間を占有していないことを明確にすることが重要であると言うでしょう。インライン展開には*間接的な影響しかありません。 –

+0

@ LukasKalbertodt:ここに太字の部分を追加しました。私はそれが影響を受けないもののリストを始めることには消極的です。人々がそれが影響を受けていると思われるかもしれないと思うようになる恐れがあるので、代わりにスコープだけに影響を及ぼしました。 –

関連する問題