Интерпретатор C++ концептуальная проблема

Я создал интерпретатор в C++ для языка, созданного мной.

Одна основная проблема в дизайне состояла в том, что у меня было два различных типов на языке: число и строка. Таким образом, я должен раздать структуру как:

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

Объекты этого класса являются розданным миллионом раз секунда во время, например: цикл обратного отсчета на моем языке.

На профилирование указывают: 85% производительности ест функция выделения строкового шаблона.

Это довольно ясно мне: Мой интерпретатор имеет плохой дизайн и не использует указатели достаточно. Все же у меня нет опции: Я не могу использовать указатели в большинстве случаев, поскольку я просто должен сделать копии.

Как сделать что-то против этого? Является класс как это лучшей идеей?

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

Таким образом, класс только знает то, что вводит его, представляет и положение в таблице

Это однако снова имеет недостатки: я должен был бы добавить временные ценности к таблице векторов строки/интервала и затем удалить их снова, это съест большую производительность снова.

  • Справка, как интерпретаторам языков нравятся Python или Ruby, делает это? Им так или иначе нужна структура, которая представляет значение на языке как что-то, что может или быть интервалом или строкой.
7
задан Jan Wilkins 17 April 2010 в 23:59
поделиться

4 ответа

Я подозреваю, что многие значения не являются строками. Итак, первое, что вы можете сделать, это избавиться от объекта 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(); }
};

Вы уловили идею.

3
ответ дан 7 December 2019 в 12:17
поделиться

Я думаю, что некоторые динамические языки кэшируют все эквивалентные строки во время выполнения с поиском по хешу и хранят только указатели. Таким образом, на каждой итерации цикла, где строка остается неизменной, будет только присвоение указателя или, самое большее, функция хеширования строки. Я знаю, что некоторые языки (кажется, Smalltalk?) Делают это не только со строками, но и с небольшими числами. См. Модель наилегчайшего веса .

IANAE по этому поводу. Если это не помогает, вы должны указать код цикла и объяснить, как он интерпретируется.

1
ответ дан 7 December 2019 в 12:17
поделиться

И в 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 с настраиваемыми механизмами полиморфизма. Но в принципе нет оснований предполагать, что «виртуальный» означает «производительность вне окна».

Тем не менее, я полагаю, что у них обоих, вероятно, есть некоторые умные оптимизации для определенного использования целых чисел, в том числе в качестве счетчиков циклов. Но если вы в настоящее время видите, что большая часть вашего времени тратится на копирование пустых строк, тогда вызовы виртуальных функций путем сравнения почти мгновенны.

Настоящее беспокойство заключается в том, как вы собираетесь управлять ресурсами - в зависимости от ваших планов в отношении интерпретируемого языка, сборка мусора может доставить больше проблем, чем вы хотите.

1
ответ дан 7 December 2019 в 12:17
поделиться

Самый простой способ решить эту проблему - сделать его указателем на строку и выделять его только при создании строкового значения. Вы также можете использовать объединение для экономии памяти.

class myInterpreterValue
{
 myInterpreterType type;
 union {
  int asInt;
  string* asString;
 } value;
}
0
ответ дан 7 December 2019 в 12:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: