私のロギング コードはbacktrace()の戻り値を使用して現在のスタックの深さを判断します (きれいに印刷するため) が、プロファイリングから、これはかなり高価な呼び出しであることがわかります。
これを行うより安価な方法はないと思いますか?フレームアドレスは気にしないことに注意してください。フレームアドレスがいくつあるかだけです。
編集: これらのログ機能は大規模なコードベース全体で使用されるため、スタックの深さを手動で追跡することは実際にはオプションではありません。
私のロギング コードはbacktrace()の戻り値を使用して現在のスタックの深さを判断します (きれいに印刷するため) が、プロファイリングから、これはかなり高価な呼び出しであることがわかります。
これを行うより安価な方法はないと思いますか?フレームアドレスは気にしないことに注意してください。フレームアドレスがいくつあるかだけです。
編集: これらのログ機能は大規模なコードベース全体で使用されるため、スタックの深さを手動で追跡することは実際にはオプションではありません。
自分でスタックをウォークするのは非常に高速です。速度低下のほとんどは、backtrace()
シンボル名の検索によるものです。x86 では、次のことができます。
inline uint32_t get_ebp(void)
{
__asm__ __volatile__("mov %%ebp, %%eax");
}
int get_stack_depth(void)
{
uint32_t ebp = get_ebp();
int stack_depth = 0;
while(ebp != 0)
{
ebp = *(uint32_t *)ebp;
stack_depth++;
}
return stack_depth;
}
ebp
これにより、ポインターのチェーンがたどられます。これは非常に移植性がないことに注意してください。また、これは、インライン化された関数や末尾呼び出しが最適化された関数をカウントしないことに注意してください (もちろん、backtrace()
同じ問題があります)。
もう 1 つの重要な問題は、終了条件です。一度 までバックトレースするとmain()
、スタックで何が見つかるかについて保証されないことがよくあります。そのため、libc がヌル フレーム ポインターを配置しない場合、segfault が発生する可能性が非常に高くなります。の最初の部分を見ると、終了値を取得できますmain()
。
アーム アーキテクチャの場合:
register unsigned long *rfp asm("fp");
unsigned long *fp = rfp;
unsigned long depth = 0;
while(fp)
{
fp = (unsigned long *)(*(fp -3));
depth++;
}
return depth;
きれいな印刷関数が適切に含まれている場合は、インデント (またはインデント サイズ) をパラメーターとして渡し、他の表示関数を呼び出すときにそれをインクリメントします。
「深さ」と呼ばれるTLS変数を持ち歩き、関数ごとに増分/減分することはできませんか? 独自のコードを記述してスタックをすばやく処理することもできますが、変数を持ち歩くよりも遅くなります。