Можно ли упростить эту векторную математическую операцию на основе ветвлений? - PullRequest
1 голос
/ 15 января 2010

Я пытаюсь добиться чего-то вроде следующего в C ++:

class MyVector; // 3 component vector  class

MyVector const kA = /* ... */;
MyVector const kB = /* ... */;

MyVector const kC = /* ... */;
MyVector const kD = /* ... */;


// I'd like to shorten the remaining lines, ideally making it readable but less code/operations.
MyVector result = kA;

MyVector const kCMinusD = kC - kD;

if(kCMinusD.X <= 0)
{
    result.X = kB.X;
}

if(kCMinusD.Y <= 0)
{
    result.Y = kB.Y;
}

if(kCMinusD.Z <= 0)
{
    result.Z = kB.Z;
}

Перефразируя код на английский, у меня есть четыре «известных» вектора. Два из векторов имеют значения, которые я могу или не могу хотеть в своем результате, и то, хочу я их или нет, зависит от ветви, основанной на компонентах двух других векторов.

Я чувствую, что смогу упростить этот код с помощью некоторой математической математики и маскировки, но я не могу обернуться вокруг него.

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

Edit:

В отношении комментария Марка я объясню, что я пытаюсь сделать здесь.

Этот код является выдержкой из некоторой весенней физики, над которой я работаю. Компоненты следующие:

kC - это длина пружины, а kD - минимальная длина пружины.

кА и кБ - два набора пружинных натяжений, каждый компонент которых может быть уникальным для каждого компонента (то есть, различное натяжение пружины вдоль X, Y или Z). kA - это натяжение пружин, если оно не полностью сжато, а kB - это натяжение пружин, если оно полностью сжато.

Я бы хотел создать результирующий «вектор», который просто представляет собой объединение kC и kD, в зависимости от того, сжимается ли пружина или нет.

Ответы [ 4 ]

2 голосов
/ 15 января 2010

В зависимости от используемой платформы, компилятор может оптимизировать операторы типа

result.x = (kC.x > kD.x) ? kA.x : kB.x;
result.y = (kC.y > kD.y) ? kA.y : kB.y;
result.z = (kC.z > kD.z) ? kA.z : kB.z;

с использованием инструкций fsel ( select с плавающей запятой ) или условных перемещений. Лично я думаю, что код выглядит лучше и лаконичнее, но это субъективно.

Если код действительно критичен к производительности, и вы не возражаете изменить свой векторный класс на 4 числа с плавающей точкой вместо 3, вы можете использовать SIMD (например, SSE на платформах Intel, VMX на PowerPC) для сравнения и выбрать ответы. Если вы пошли дальше, это будет выглядеть так: (в псевдокоде)

// Set each component of mask to be either 0x0 or 0xFFFFFFFF depending on the comparison
MyVector4 mask = vec_compareLessThan(kC, kD);

// Sets each component of result to either kA or kB's component, depending on whether the bits are set in mask
result = vec_select(kA, kb, mask);

Это требует некоторого времени, чтобы привыкнуть, и поначалу оно может быть менее читабельным, но в конечном итоге вы привыкнете думать в режиме SIMD.

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

1 голос
/ 15 января 2010

Если вы ищете чистое выражение в источнике больше, чем оптимизацию во время выполнения, вы можете решить эту проблему с точки зрения «набора инструментов». Итак, допустим, что в MyVector вы определили sign, gt (больше-чем) и le (меньше-или-равно). Затем в две строки:

MyVector const kSignCMinusD = (kC - kD).sign();
result = kSignCMinusD.gt(0) * kA + kSignCMinusD.le(0) * kB;

С перегрузкой оператора:

MyVector const kSignCMinusD = (kC - kD).sign();
result = (kSignCMinusD > 0) * kA + (kSignCMinusD <= 0) * kB;

Для вдохновения вот справка по MatLab . И, очевидно, есть много векторных библиотек C ++ на выбор с такими функциями.

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

1 голос
/ 15 января 2010

Если ваши векторные элементы являются целыми числами, вы можете сделать:

MyVector result;
MyVector const kCMinusD = kC - kD;
int mask = kCMinusD.X >> 31;  // either 0 or -1
result.X = (kB.X & mask) | (kCMinusD.X & ~mask)
mask = kCMinusD.Y >> 31;
result.X = (kB.Y & mask) | (kCMinusD.Y & ~mask)
mask = kCMinusD.Z >> 31;
result.X = (kB.Z & mask) | (kCMinusD.Z & ~mask)

(обратите внимание, что случай == 0 обрабатывается по-разному, не уверен, что вам не все равно)

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

0 голосов
/ 15 января 2010

Поскольку вы делаете только вычитание, вы переписываете, как показано ниже:

MyVector result;
result.x = kD.x > kC.x ? kB.x : kA.x;
result.y = kD.y > kC.y ? kB.y : kA.y;
result.z = kD.z > kC.z ? kB.z : kA.z;
...