0

私はカスタムプログラミング言語(連結、ソフトタイプ)のインタープリターを作成しています。そのために、中央のデータ型がありTokenます。トークンは、スカラーのものまたはベクトルのもののいずれかである、多くの異なるタイプの1つにすることができます。メモリの量を最小限に抑えるために、union最初に使用しましたが、その後はユニオンで古いデータ構造しか使用できなかったため、structすべてのフィールド(long asInteger;、、boost::shared_ptr<std::string> asString...)を使用してaを使用しました。もちろん、それはメモリ消費の観点からは悪い考えでしたが、それで仕事は終わりました。

データ型の長さはほぼ100バイトであったためToken(たとえば、1,000,000個の整数の配列をほぼ100メガバイトにする)、元の定式化は非常に不十分でした。今日、実装を改良して、コピーセマンティクスを使用して各要素に必要なメモリを動的に割り当てるようにunionしました。これにより、クラスで使用できる場合と同様の結果が得られました。

これは新しいクラス定義です:

class Token {
protected:
    TokenType tokenType_;

    template<class T>
    inline void copyToken(void * src, void * dst)
    {
      *static_cast<T*>(dst) = 
          *static_cast<T*>(src);
    };

    template<class T>
    inline void deleteValue()
    {
      delete static_cast<T*>(data);
    };

    void deleteData()
    {
        switch (tokenType_)
        {
            case T_INTEGER:   deleteValue<long>(); break; 
            case T_BOOL:      deleteValue<bool>(); break; 
            case T_FLOAT:     deleteValue<double>(); break;
            case T_STRING:    deleteValue<boost::shared_ptr<std::string>>(); break;  
            case T_ARRAY:     deleteValue<boost::shared_ptr<std::vector<Token>>>(); break;
            case T_HANDLE:    deleteValue<HandleData>(); break;
            default: ;
        }
    }

    void allocate(const TokenType tokenType)
    {       
        switch (tokenType)
        {
            case T_INTEGER:   data = new long; break; 
            case T_BOOL:      data = new bool;  break; 
            case T_FLOAT:     data = new double; break;
            case T_STRING:    data = new boost::shared_ptr<std::string>; break;  
            case T_ARRAY:     data = new boost::shared_ptr<std::vector<Token>>; break;
            case T_HANDLE:    data = new HandleData; break;
            default: data = NULL;
        }     
    };

    void * data;

public:

    void set_type(const TokenType tokenType)
    {
        deleteData();
        tokenType_ = tokenType;
        allocate(tokenType);
    };

    Token() : tokenType_ (T_EMPTY) { data = NULL; }; 

    Token(const TokenType tokenType) : tokenType_ (tokenType)  
    {
        allocate(tokenType);
    };

    Token(const Token& old_token)  
    {
        tokenType_ = old_token.tokenType_;
        allocate(old_token.tokenType_);
        switch (old_token.tokenType_)
        {
            case T_INTEGER:   copyToken<long>(old_token.data, data); break; 
            case T_BOOL:      copyToken<bool>(old_token.data, data); break; 
            case T_FLOAT:     copyToken<double>(old_token.data, data); break;
            case T_STRING:    copyToken<boost::shared_ptr<std::string>>(old_token.data, data); break; 
            case T_ARRAY:     copyToken<boost::shared_ptr<std::vector<Token>>>(old_token.data, data); break;
            case T_HANDLE:    copyToken<HandleData>(old_token.data, data); break;
            default: ;
        }       
    };

    template<class T>
    T& retreive()
    {
        return *static_cast<T*>(data);
    };

    template<class T>
    const T& retreive() const
    {
        return *static_cast<T*>(data);
    };

    void operator=(const Token &rhs) 
    {
        fileName = rhs.fileName;
        lineNum  = rhs.lineNum;
        set_type(rhs.tokenType_);
        switch (rhs.tokenType_)
        {
            case T_INTEGER:   copyToken<long>(rhs.data, data); break; 
            case T_BOOL:      copyToken<bool>(rhs.data, data); break; 
            case T_FLOAT:     copyToken<double>(rhs.data, data); break;
            case T_STRING:    copyToken<boost::shared_ptr<std::string>>(rhs.data, data); break; 
            case T_ARRAY:     copyToken<boost::shared_ptr<std::vector<Token>>>(rhs.data, data); break;
            case T_HANDLE:    copyToken<HandleData>(rhs.data, data); break;
            default: ;
        }       
    };

    ~Token()
    {
       deleteData();
    };
};

次に、でトークンを作成します

Token newToken(T_INTEGER);
newToken.retreive<long>() = 42;

これで、上記のコードは機能しますが、非常に遅くなります(unionを使用した以前の実装よりも200%遅くなります)。newプロファイラーは、実行時間のほぼ半分がとに費やされていることを示していfree()ます。配置構文を使用して、オブジェクトのchar data[50]スペースがToken該当する場合にスタックに割り当てられ、すべての場合に1回だけ割り当てられるようにしました。これにより、速度は大幅に向上しますが、元の構文ほど速くはなりません(約20%遅くなります)。

私の質問は次のとおりです。この頻繁な小さなオブジェクトの割り当てをよりスリムで迅速にするにはどうすればよいでしょうか。

これが通常まったく異なる方法で行われる場合は、その方法を教えてください。私は通常、堅牢で高速ですが、「スマート」(自動変換など)ではないバリアント型が必要です。これには、独自のフレームワークがあるためです。一般的には、必要なメモリを最小限に抑えながら、メモリ割り当てコストを最小限に抑えたいと思います。

助けてくれてありがとう!

4

1 に答える 1

0

私はこれが何年も前のものであることを知っています.あなたはあなたの答えを見つけたようですが、あなたはそれを投稿したことがないようです.

連合

はい、回避したのと同じデータ構造が使用された可能性があります。これにより、データ量が大幅に削減されます。boolintfloat、および の4 つのタイプがあるとしdoubleます。ユニオンのサイズは常に最大になりますが、これは最適ではありませんが、すべての要素のサイズよりは優れています。今、あなたは言った " ...私は共用体で単純な古いデータ構造しか使えない... " これは本当ですが、非ジェネリック型へのポインターを取ることができます。これにより、メモリの問題のほとんどを解決できますが、ポインタを使用する場合は使用する必要があるため、これらnewdeleteために実行速度が遅くなります。ユニオンを使用することのもう 1 つの欠点は、使用されている型を追跡する必要があることです。

Boost::バリアント

ブースト ライブラリにアクセスできる場合は、間違いなくこれを使用します。ユニオンほど多くのメモリを節約するとは思いませんが、すべての値を保持するよりもはるかに節約できます。これにより、非ジェネリック型も可能になります。また、どのタイプが使用されているかを追跡する必要はありません。追跡しなければならないのは、型を書いた順序だけです。

Boost::variantを使用すると言いますが、union を使用すると可能です。

于 2014-11-19T22:22:22.827 に答える