Как синхронизировать библиотеки C & C ++ с минимальным снижением производительности?

StackOverflow https://stackoverflow.com/questions/286105

Вопрос

У меня есть библиотека 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...
};
Это было полезно?

Решение

Ваша оболочка будет встроена, однако вызовы вашего метода к библиотеке 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 () сохраняет значения в регистрах.

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top