C/C+--コードテクニックと最適化


私はいくつかのふだんプログラミングする小さい技巧と非アルゴリズム類の最適化を総括して、各位がいっしょに討論することを望んで、自分の技巧も分かち合います
1、inline/define適量のコード冗長性:
「コード冗長性」は嫌なことで、同じコードを2つの場所で見たら、最初の反応は「再構築」すべきだが、defineのようなコード「ローカル置換」の適切な冗長性は、コード実行を加速させ、関数呼び出しのイベントを省いてCPUを順番に実行させることができる.適切なdefineとinlineがもたらすコード冗長性は悪くありません.
2、適当なgoto:
多くの本には基本的に「gotoの使用禁止」という言葉が書かれていますが、特定の場合、gotoを適切に使うときれいに問題を解決することができます.kernel開発では、gotoが「多重ループから飛び出す」と「エラー処理ジャンプ」に用いられ、論理がより明確になり、「gotoは関数にまたがらない」、「gotoは上へジャンプしない」を満たす限りgotoは役に立つ.
int foo(int value)
{
    if(value<=0)
        goto err;

    value *= 100;
    ... 
    ...

    if (value>=10000)
        goto err;

    // OK
    return 0;

err:
    LOG_WRITE("Error");
    return -1; 
}

3、do{}while(0)マクロ:
「マクロ関数」を書くと不思議な効果をもたらすことがあります.
#define  fun(condition)  if(condition)  dosomething();  
 
             :  
 
if(temp)  
	macro(i);  
else  
        doanotherthing();  

コードコンパイルに成功したバンド結果は意外で、elseのトリガは非常に奇妙で、elseが2つのifに「気絶した」ためだ.この現象はよく見られ、コードがまったくコンパイルされない可能性もあります.解決策はkernelのstyleでdo{}while(0)でこのマクロを包むことで、問題はありません.
#define fun(blah) do { \
                printf("it is %s/n", blah); \
                do_something_useful(blah); \
        } while (0);

4、if elseの代わりに三元演算子を使用する:
if elseは簡単な判断にすぎない場合があります.例えば、
if (key==1)
    doSomething();
else
    doSomethingElse();

このようにCPUはシーケンス実行力ではなく、ロジックによっていくつかの性能損失をもたらし、見た目も快適ではありません.に置き換えます.
key==1 ? doSomething() : doSomethingElse();

5、if else増加予判メカニズムlikelyマクロ:
次のようなシーンを想像すると、1つのforサイクル1~1000で、100の整倍数の数字を見つけて、簡単にfor+if(num%100)elseで済むのですが、ここで最適化するところがあります.if elseではelseの大部分が実行されるので、ifのコードが実行される確率は1%後になるので、if条件の予断ができます.linux kernelではgccの内蔵機能を使用することでif elseの分布状況を予断できると考えられています.
#define likely(x) __builtin_expect((x),1)
#define unlikely(x) __builtin_expect((x),0)

likelyはxが本当である可能性が高いことを示し,unlikelyは逆である.これにより、コンパイラはifの後の文のアドレスを直接cpuの次の命令に格納してキャッシュすることができ(プログラミング者がgccが本当だと教える可能性が高いため)、このようにして、ある程度分岐構造を順序構造に変え(cpuのcacheがヒットする)、1%の場合にのみメモリ都命令を再削除することができる.
if (likely(x%100))
    printf("no");
else
    printf("Yes");

6、cacheヒット:
上記と同様に、このセクションでは、CPUが順番に操作するのが一番好きなので、次のコマンドや次のデータはいつもcpuの1級cacheと2級cacheに読み込まれ、命中率がメモリのアクセスがなくなると思っています.
7、(スタック)配列cache:
できるだけスタック空間でスタック空間を使うことができ、できるだけ配列で配列を使うことができます.スタックは自動的にcの実行時に管理され、破片や漏洩の問題を心配する必要はなく、スタックよりも速度が速い(具体的な桁数はテストされていない).また、スタックの解放時には大きなメモリ(通常、スタックはlinuxではデフォルト8 M、windowsでは1 M)として、全体的にオペレーティングシステムに返されるのはmallocなどのように1枚ずつシステムに返されないが、ここではメモリプールのいくつかの概念に関連しており、後述する文章で詳しく説明する.また配列は,長さを予知できれば配列を用いることが望ましいが,メモリが連続しているため,cpuがcacheを除去しやすく,array[x]のようなインデックスによって直接位置決めできる.
8、構造体の位置合わせ:
cpuがメモリに対してデータを読み出す際の効率性のために、gccのデフォルトはバイト整列をオンにし、具体的な数値(2または4または8)はオペレーティングシステムのビット数に依存し、_align_of__マクロを設定します.場合によっては、バイトの位置合わせを利用して、いくつかの最適化を行うことができます.たとえば、linux kernelの赤と黒のツリーのノードは次のように宣言されます.
struct rb_node
{
	unsigned long rb_parent_color;
	#define RB_RED 0
	#define RB_BLACK 1
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

ここには小さなテクニックがありますrb_parent_colorはそんなに簡単ではありません.名前が示すように、このメンバーにはparentへのポインタとこのノードの色が含まれています.構造体全体がsizeof(long)サイズで整列しているので、rb_Node構造体のアドレスの低い2桁は必ずゼロ(メモリ割り当て時に得られるヘッダアドレスは必ず4の整数倍で、すぐに3バイトを申請します.システムは効率と断片化防止を考慮しているので)、それらで色を表すことができます.どうせ色は2種類で、1桁で十分です.
#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))    //      ,       
//               
#define rb_color(r) ((r)->rb_parent_color & 1)
時々、私たちmallocのオブジェクトは必ずしもfreeを使い切る必要はありません.refcount(参照カウント、自分でbaiduしてもいい)を作るかもしれません.0=refcountを待って、本当のfreeで解放します(オブジェクトの再利用).もし私たちのシステムがx 64であれば、私たちのポインタは8つのbyteで、アドレスの範囲はすべてTBで、つまり最初の数桁はまったく使えないので、このカウンターを使うことができます(使い方は同じで、ビット&すぐに出ます).
9、switch最適化:
一、可能な値をできるだけ前に置く.
二、switchはswitchをセットして、switchの列を階層化することができます.
三、文字列であれば、最初のアルファベットでインデックスを作成し、階層化することができ、最外層は最大24+10個(アルファベットと数字)です.
四、巧みにcaseで破壊し、いくつかのcaseが完全に等価であれば、破壊の方法で同じ論理で処理することができる.
10、パラレルコード
double a[100], sum1, sum2, sum3, sum4, sum;
int i;
sum1 = sum2 = sum3 = sum4 = 0.0;
for (i = 0; i < 100; i += 4)
{
  sum1 += a[i];
  sum2 += a[i+1];
  sum3 += a[i+2];
  sum4 += a[i+3];
}
sum = (sum4+sum3)+(sum1+sum2); 

4パス分解を用いたのは,このように4セグメントの流水線加算を用い,加算の各セグメントが1クロック周期を占有し,最大の資源利用率を保証したからである.
    
10、linuxロック:
        phtread_mutex_tは反発量のロックであり,複数のスレッドが1つしか臨界リソースにアクセスできない.インターネット開発では,一貫性がそれほど高くなければ,読み書きロックを多く用いて最適化でき,同時性能がよりよい.
いくつかのビジネスシーンでは、例えばcpu消費型の場合、spinlockスピンロックでcpuを忙しくさせるなど、cpu間の切り替えの代価を省くことができます(memcacheプロセス、使用率SpinLock()の方法など、memcacheプロセス、公式ドキュメントのデータQPSは20%近く増加しました.linuxユーザー状態では、while(1)tryLock(lock)という簡単なspinlock()を自分で実現することができます.
11、c++初期化リスト
初期化リストでプライベートメタに初期値を付与することが多く,構造関数では初期値を付与するのではないので,できるだけ初期化リストで行う.
12、スマートポインタ
        std::auto_ptr/std::shared_ptr/boost::shared_ptrはいずれもスマートポインタですが、boostを強くお勧めします.standard c++だけならstd::shared_ptr,auto_ptrは、autop 1=autpp 2などの値を付与できないポインタであり、暗黙の権利移転が発生し、標準stlコンテナに使用できない.std::share_ptrは値付けおよび値付けが可能であり、stl容器に入れることができる.
続きはデータ構造の最適化、メモリプールの最適化、ネットワークI/Oモデルの最適化など、お楽しみに!!