Как синхронизировать библиотеки C & C ++ с минимальными потерями производительности? - PullRequest
1 голос
/ 13 ноября 2008

У меня есть библиотека C с многочисленными математическими процедурами для работы с векторами, матрицами, кватернионами и так далее. Это должно остаться в C, потому что я часто использую это для встроенной работы и как расширение Lua. Кроме того, у меня есть оболочки класса C ++ для более удобного управления объектами и перегрузки операторов для математических операций с использованием C API. Оболочка состоит только из заголовочного файла, и максимально возможное использование при вставке.

Есть ли заметное наказание за перенос кода C по сравнению с переносом и встраиванием реализации непосредственно в класс C ++? Эта библиотека используется в приложениях, критичных ко времени. Итак, компенсирует ли усиление за счет устранения косвенности головную боль при обслуживании двух портов?

Пример интерфейса C:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

Пример оболочки C ++:

class Vector3
{
private:
    VECTOR3 v_;

public:
    // copy constructors, etc...

    Vector3& operator+=(const Vector3& rhs)
    {
        v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
        return *this;
    }

    Vector3 operator+(const Vector3& rhs) const
    {
        Vector3 tmp(*this);
        tmp += rhs;
        return tmp;
    }

    // more methods...
};

Ответы [ 6 ]

4 голосов
/ 13 ноября 2008

Если вы просто заключаете вызовы библиотеки C в функции класса C ++ (другими словами, функции C ++ не делают ничего, кроме вызова функций C), то компилятор оптимизирует эти вызовы, чтобы не снизить производительность.

3 голосов
/ 13 ноября 2008

Как и в случае с любым вопросом о производительности, вам будет предложено измерить, чтобы получить ответ (и это строго правильный ответ).

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

Однако, даже если ваши методы-обертки не были встроены, я подозреваю, что вы не заметите никакого снижения производительности - даже измеримого - если бы метод обертки не вызывался в каком-то критическом цикле. Даже тогда это, вероятно, было бы измеримо, только если сама упакованная функция не выполняла много работы.

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

2 голосов
/ 13 ноября 2008

Ваша оболочка будет встроена, однако ваши вызовы методов к библиотеке C обычно не выполняются. (Это потребовало бы оптимизации времени соединения, которая технически возможна, но в лучшем случае для AFAIK в современных инструментах)

Как правило, вызов функции как таковой не очень дорогой. Стоимость цикла значительно снизилась за последние годы, и ее можно легко предсказать, поэтому штраф за вызов как таковой незначителен.

Однако встраивание открывает двери для дополнительных оптимизаций: если у вас есть v = a + b + c, ваш класс-оболочка вызывает генерацию переменных стека, тогда как для встроенных вызовов большая часть данных может храниться в FPU стек. Кроме того, встроенный код позволяет упростить инструкции, учитывая постоянные значения и многое другое.

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


Типичное решение состоит в том, чтобы привести реализацию C в формат, который можно использовать либо как встроенные функции, либо как тело "C":

// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
    // here you maintain the actual implementations
    // ...
}

// C header
#define V3DECL 
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

// C body
#include "V3impl.inl"


// CPP Header
#define V3DECL inline
namespace v3core {
  #include "V3impl.inl"
} // namespace

class Vector3D { ... }

Это, вероятно, имеет смысл только для избранных методов со сравнительно простыми телами. Я бы переместил методы в отдельное пространство имен для реализации C ++, так как они вам обычно не нужны напрямую.

(Обратите внимание, что inline - это просто подсказка компилятора, он не заставляет метод быть встроенным. Но это хорошо: если размер кода внутреннего цикла превышает кеш инструкций, встраивание легко снижает производительность)

Возможность разрешения передачи / возврата по ссылке зависит от силы вашего компилятора, я видел много где foo (X * out) заставляет переменные стека, тогда как X foo () сохраняет значения в регистрах.

2 голосов
/ 13 ноября 2008

Как обычно, со всем, что связано с оптимизацией, ответ заключается в том, что вы должны сами измерить производительность, прежде чем узнаете, стоит ли оптимизация.

  • Оцените две разные функции: одна вызывает функции в стиле C напрямую, а другая - через обертку. Посмотрите, какой из них работает быстрее, или если разница находится в пределах погрешности вашего измерения (что означает, что нет разницы, которую вы можете измерить).
  • Посмотрите код сборки, сгенерированный двумя функциями на предыдущем шаге (в gcc используйте -S или -save-temps). Посмотрите, сделал ли компилятор что-то глупое, или у ваших упаковщиков есть какая-то ошибка в производительности.

Если разница в производительности не слишком велика в пользу неиспользования обертки, повторная реализация не является хорошей идеей, поскольку вы рискуете ввести ошибки (которые могут даже привести к результатам, которые выглядят вменяемыми, но ошибочными). Даже если разница велика, было бы проще и менее рискованно помнить, что C ++ очень совместим с C и использовать вашу библиотеку в стиле C даже в коде C ++.

1 голос
/ 13 ноября 2008

Если вы обеспокоены тем, что накладные расходы на вызов функций замедляют вас, почему бы не проверить встроенный код C или превратить его в макросы?

Кроме того, почему бы не улучшить правильность const кода C, пока вы в нем - const_cast действительно следует использовать экономно, особенно на интерфейсах, которыми вы управляете.

1 голос
/ 13 ноября 2008

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

Я пишу для DS и некоторых других устройств ARM, а плавающие точки - это зло ... Мне пришлось печатать дефиницию float до FixedPoint <16,8>

...