C ++解释器概念问题

时间:2010-04-17 23:59:24

标签: c++ design-patterns interpreter

我在C ++中为我创建的语言构建了一个解释器。

设计中的一个主要问题是我在语言中有两种不同的类型:数字和字符串。所以我必须传递一个结构:

class myInterpreterValue
{
 myInterpreterType type;
 int intValue;
 string strValue;
}

这个类的对象每秒传递大约一百万次,例如:我的语言中的倒计时循环。

分析指出:85%的性能被字符串模板的分配功能吃掉。

这对我来说非常清楚:我的解释器设计糟糕,并且不足以使用指针。然而,我没有选择:在大多数情况下我不能使用指针,因为我只需要复制。

如何对付这件事?这样的课程是个更好的主意吗?

vector<string> strTable;
vector<int> intTable;
class myInterpreterValue
{
 myInterpreterType type;
 int locationInTable;
}

因此,类只知道它代表什么类型以及表中的位置

然而这也有缺点: 我必须在字符串/ int向量表中添加临时值,然后再次删除它们,这会再次消耗很多性能。

  • 帮助,Python或Ruby等语言的解释器如何做到这一点?他们不知何故需要一个表示语言中的值的结构,例如可以是int或string。

4 个答案:

答案 0 :(得分:3)

我怀疑很多值都不是字符串。所以你要做的第一件事就是在你不需要的时候摆脱string对象。把它变成一个联盟。另一件事是,你的许多字符串可能很小,因此如果在对象本身中保存小字符串,就可以摆脱堆分配。 LLVM具有SmallString模板。然后你可以使用字符串实习,正如另一个答案所说的那样。 LLVM具有StringPool类:调用intern("foo")并获取一个智能指针,引用其他myInterpreterValue对象可能使用的共享字符串。

联盟可以这样写成

class myInterpreterValue {
 boost::variant<int, string> value;
};

boost::variant为您进行类型标记。如果你没有提升,你可以像这样实现它。在C ++中无法实现对齐,因此我们将一些可能需要大量对齐的类型推送到存储联合中。

class myInterpreterValue {
 union Storage {
   // for getting alignment
   long double ld_;
   long long ll_;

   // for getting size
   int i1;
   char s1[sizeof(string)];

   // for access
   char c;
 };
 enum type { IntValue, StringValue } m_type;

 Storage m_store;
 int *getIntP() { return reinterpret_cast<int*>(&m_store.c); }
 string *getStringP() { return reinterpret_cast<string*>(&m_store.c); }


public:
  myInterpreterValue(string const& str) {
    m_type = StringValue;
    new (static_cast<void*>(&m_store.c)) string(str);
  }

  myInterpreterValue(int i) {
    m_type = IntValue;
    new (static_cast<void*>(&m_store.c)) int(i);
  }
  ~myInterpreterValue() {
    if(m_type == StringValue) {
      getStringP()->~string(); // call destructor
    }
  }
  string &asString() { return *getStringP(); }
  int &asInt() { return *getIntP(); }
};

你明白了。

答案 1 :(得分:1)

我认为一些动态语言在运行时使用哈希查找缓存所有等效字符串,并且只存储指针。因此,在循环的每次迭代中,字符串保持不变,只有一个指针赋值或最多一个字符串散列函数。我知道一些语言(Smalltalk,我认为?)不仅使用字符串而且使用小数字。请参阅Flyweight Pattern

IANAE就此而言。如果这没有用,你应该给出循环代码并告诉我们如何解释它。

答案 2 :(得分:1)

在Python和Ruby中,整数都是对象。所以这不是一个“值”是一个整数或一个字符串的问题,它可以是任何东西。此外,这两种语言中的所有内容都是垃圾收集。没有必要复制对象,指针可以在内部使用,只要它们安全地存储在垃圾收集器将看到它们的某个地方。

因此,您问题的一个解决方案是:

class myInterpreterValue {
    virtual ~myInterpreterValue() {}
    // example of a possible member function
    virtual string toString() const = 0;
};

class myInterpreterStringValue : public myInterpreterValue {
    string value;
    virtual string toString() const { return value; }
};

class myInterpreterIntValue : public myInterpreterValue {
    int value;
    virtual string toString() const {
        char buf[12]; // yeah, int might be more than 32 bits. Whatever.
        sprintf(buf, "%d", value);
        return buf;
    }
};

然后使用虚拟调用和dynamic_cast来打开或检查类型,而不是与myInterpreterType的值进行比较。

此时要做的通常是担心虚函数调用和动态强制转换可能会很慢。 Ruby和Python都使用虚拟函数调用。虽然不是C ++虚拟调用:对于这两种语言,它们的“标准”实现在C中,具有用于多态的自定义机制。但原则上没有理由认为“虚拟”意味着“在窗外表现”。

那就是说,我希望他们可能都对某些整数使用有一些巧妙的优化,包括作为循环计数器。但是,如果您目前看到大部分时间花在复制空字符串上,那么通过比较进行虚拟函数调用几乎是即时的。

真正担心的是你将如何进行资源管理 - 取决于你的解释语言的计划,垃圾收集可能比你想要的更麻烦。

答案 3 :(得分:0)

解决这个问题的最简单方法是使其成为指向字符串的指针,并且只在创建字符串值时分配它。您还可以使用union来节省内存。

class myInterpreterValue
{
 myInterpreterType type;
 union {
  int asInt;
  string* asString;
 } value;
}