8

I am testing combinations of various optimizations and for these I need a static-if as described in http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Static-If-I-Had-a-Hammer to enable and disable specific optimizations. if(const-expr) does not always work as some optimizations involve changing the data layout and this can not be done at function scope.

Basically what I want is this:

template<bool enable_optimization>
class Algo{
  struct Foo{
    int a;
    if(enable_optimization){
      int b;
    }

    void bar(){
      if(enable_optimization){
        b = 0;
      }
    }
  };
};

(Yes, the smaller memory footprint of removing b from the data layout is relevant in my case.)

Currently I am faking it using a very bad hack. I am looking for a better way of doing this.

File a.h

#ifndef A_H
#define A_H
template<class enable_optimization>
class Algo;
#include "b.h"
#endif

File b.h (this file is autogenerated from a Python script)

#define ENABLE_OPTIMIZATION 0
#include "c.h"
#undef
#define ENABLE_OPTIMIZATION 1
#include "c.h"
#undef

File c.h

template<>
class Algo<ENABLE_OPTIMIZATION>{
  struct Foo{
    int a;
    #if ENABLE_OPTIMIZATION
    int b;
    #endif

    void bar(){
      #if ENABLE_OPTIMIZATION
      b = 0;
      #endif
    }
  };
};

Does anyone know of a better way of doing this? Theoretically it can be done using template meta programming and at first I used it. At least the way I used it was a pain in the ass and lead to completely unreadable and bloated code. Using the hack above resulted in a significant productivity boost.

EDIT: I have several optimization flags and these interact.

4

3 に答える 3

6

テンプレートを使用してコードがさらに複雑になる理由はありません。

template<bool enable_optimization>
  class FooOptimized
  {
  protected:
    int b;
    void bar_optim()
    {
      b = 0;
    }
  };

template<>
  class FooOptimized<false>
  {
  protected:
    void bar_optim() { }
  };

template<bool enable_optimization>
  struct Algo
  {
    struct Foo : FooOptimized<enable_optimization>
    {
      int a;

      void bar()
      {
        this->bar_optim();
      }
    };
  };

メタプログラミングは必要なく、最適化が有効かどうかによって異なる部分を新しいタイプに分離して特化するだけです。

新しいタイプは、空の場合 (つまり、FooOptimized::bメンバーがない場合) に基本クラスとして使用されるため、スペースを占有しないため、sizeof(Algo<false>::Foo) == sizeof(int).


(この回答の残りの部分は無視してかまいません。質問に直接対処するものではありません。代わりに、さまざまなトレードオフを持つ別のアプローチを提案しています。それが「より良い」かどうかは、実際のコードの詳細に完全に依存します、質問で与えられた簡単な例には示されていません。)

関連するが別の問題として、最適化が有効かどうかに依存しない と の部分は、テンプレートAlgoパラメーターに依然として依存しているため、これらのコードを 1 回しか記述しない場合でも、コンパイラは 2 セットのオブジェクト コードを生成します。 . そのコードの作業量とその使用方法によっては、それを非テンプレート コードに変更すること、つまり静的ポリモーフィズムを動的ポリモーフィズムに置き換えることに利点があることに気付くかもしれません。たとえば、フラグをテンプレート引数ではなくランタイム コンストラクター引数にすることができます。Algo::Fooenable_optimization

struct FooImpl
{
  virtual void bar() { }
};

class FooOptimized : FooImpl
{
  int b;
  void bar()
  {
    b = 0;
  }
};

struct Algo
{
  class Foo
  {
    std::unique_ptr<FooImpl> impl;
  public:
    explicit
    Foo(bool optimize)
    : impl(optimize ? new FooOptimized : new FooImpl)
    { }

    int a;

    void bar()
    {
      impl->bar();
    }
  };
};

仮想関数のオーバーヘッドがコードの複製よりも少なく、テンプレート パラメーターに依存しないAlgoかどうかを判断するには、プロファイリングしてテストする必要があります。Algo::Foo

于 2012-07-21T14:12:02.613 に答える
1

注: 現状では、メンバーにスペースを割り当てずにクラスにメンバーを持つ方法がないように見えるため、このアプローチは機能しません。それを機能させる方法を考えている場合は、自由に編集してください。

次のようなイディオムを使用できます。

template<bool optimized, typename T> struct dbg_obj {
  struct type {
    // dummy operations so your code will compile using this type instead of T
    template<typename U> type& operator=(const U&) { return *this; }
    operator T&() { return *static_cast<T*>(0); /* this should never be executed! */ }
  };
};
template<typename T> struct dbg_obj<false, T> {
  typedef T type;
};

template<bool enable_optimization>
class Algo{
  struct Foo{
    int a;
    typename dbg_obj<enable_optimization, int>::type b;

    void bar(){
      if(enable_optimization){
        b = 0;
      }
    }
  };
};

最適化が無効になっている場合、これにより通常のintメンバーが得られます。最適化が有効になっている場合、 の型はbメンバーのない構造体であり、スペースを取りません。

テンプレートの特殊化のような明確なコンパイル時のメカニズムとは対照的に、メソッドbarはランタイムのように見えるものを使用してifにアクセスするかどうかを決定するためb、使用するすべての操作bはダミー構造体からも利用できる必要があります。関連するセクションが実行されず、コンパイラーがそれらを最適化する可能性が高い場合でも、正確性チェックが最初に行われます。そのため、行b = 0は置換型に対してもコンパイルする必要があります。これが、ダミーの割り当てとダミーのキャスト操作の理由です。コードにはどちらでも十分ですが、別の時点で役立つことが判明した場合に備えて両方を含め、必要になった場合にさらに追加する方法を示します。

于 2012-07-21T15:02:04.030 に答える
0

Jonathan Wakely によって提示された静的ソリューション (動的ソリューションではない) は、進むべき道です。

アイデアは簡単です:

  1. 「特定の」データを独自のクラスに分離します(テンプレート化され、それに応じて特化されます)
  2. そのデータへのすべてのアクセスは、特殊なクラスに完全に引き渡されます (インターフェースは統一されている必要があります)。

次に、スペースのオーバーヘッドが発生しないようにするために、この特別なクラスのいずれかの特殊化を継承することで、EBO (Empty Base Optimization) を有利に使用します。

注: 属性には少なくとも 1 バイトのスペースが必要です。基本クラスは、一部の条件 (空など) では使用できません。

あなたの場合:

  • bは特定のデータです
  • それを含む単一の操作があります(その値を設定することで構成されます)

したがって、クラスを簡単に構築できます。

template <typename T> struct FooDependent;

template <>
struct FooDependent<true> {
    int b;
    void set(int x) { b = x; }
};

template <>
struct FooDependent<false> {
    void set(int) {}
};

Fooそして、 EBO を有利に使用して、それを に注入できます。

struct Foo: FooDependent<enable_optimization> {
    int a;

    void bar() { this->set(0); }
};

注:thisテンプレート化されたコードで使用して、基本クラスのメンバーにアクセスしないと、優れたコンパイラがコードを拒否します。

于 2012-07-21T16:13:33.970 に答える