Я использую устаревший код C, который передает множество необработанных указателей. Чтобы взаимодействовать с кодом, мне нужно передать функцию формы:

const int N = ...;

T * func(T * x)  {
    // TODO Put N elements in x
    return x + N;
}

Где эта функция должна записать результат в x, а затем вернуть x.

Внутри этой функции я широко использую Eigen для выполнения некоторых вычислений. Затем я записываю результат обратно в исходный указатель, используя класс Map. Вот простой пример, который имитирует то, что я делаю:

const int N = 5;
T * func(T * x)  {

    // Do a lot of operations that result in some matrices like
    Eigen::Matrix<T, N, 1 > A = ... 
    Eigen::Matrix<T, N, 1 > B = ... 

    Eigen::Map<Eigen::Matrix<T, N, 1 >> constraint(x);
    constraint = A - B;

    return x + N;
}

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

Теперь проблема в том, что когда я профилирую этот код с помощью Callgrind, а затем просматриваю результаты с помощью KCachegrind, строки

constraint = A - B;

Почти всегда являются узким местом. Это в некотором роде понятно, потому что такие строки могут / потенциально делают три вещи:

  1. Построение объекта Map
  2. Выполнение расчета
  3. Запись результата в указатель

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

Так есть ли лучший способ записать результат в необработанный указатель? Или это та идиома, которую я должен использовать?

В глубине души мне интересно, поможет ли мне использование синтаксиса placement new здесь что-нибудь.

Примечание. Этот код критически важен и должен работать в режиме реального времени, поэтому мне действительно нужно выжать из него каждую каплю скорости. Например, получение этого вызова из времени выполнения от 0,12 секунды до 0,1 секунды было бы для нас огромным. Но разборчивость кода также является огромной проблемой, поскольку мы постоянно настраиваем модель, используемую во внутренних вычислениях.

1
bremen_matt 23 Апр 2018 в 12:47

1 ответ

Лучший ответ

Эти две строки кода:

Eigen::Map<Eigen::Matrix<T, N, 1 >> constraint(x);
constraint = A - B;

По существу скомпилированы Eigen как:

for(int i=0; i<N; ++i)
  x[i] = A[i] - B[i];

Реальность немного сложнее из-за явного развертывания и явной векторизации (оба зависят от T), но по сути это все. Таким образом, создание объекта Map по сути является бездействующим (он оптимизирован любым компилятором), и нет, здесь не происходит дополнительной копии.

На самом деле, если ваш профилировщик может сказать вам, что узкое место кроется в этом простом выражении, то это, скорее всего, означает, что этот фрагмент кода не был встроен, что означает, что вы не включили флаги оптимизации компилятора (например, -O3 с помощью gcc / clang).

1
ggael 23 Апр 2018 в 11:06