我在C ++中为我创建的语言构建了一个解释器。
设计中的一个主要问题是我在语言中有两种不同的类型:数字和字符串。所以我必须传递一个结构:
class myInterpreterValue
{
myInterpreterType type;
int intValue;
string strValue;
}
这个类的对象每秒传递大约一百万次,例如:我的语言中的倒计时循环。
分析指出:85%的性能被字符串模板的分配功能吃掉。
这对我来说非常清楚:我的解释器设计糟糕,并且不足以使用指针。然而,我没有选择:在大多数情况下我不能使用指针,因为我只需要复制。
如何对付这件事?这样的课程是个更好的主意吗?
vector<string> strTable;
vector<int> intTable;
class myInterpreterValue
{
myInterpreterType type;
int locationInTable;
}
因此,类只知道它代表什么类型以及表中的位置
然而这也有缺点: 我必须在字符串/ int向量表中添加临时值,然后再次删除它们,这会再次消耗很多性能。
答案 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;
}