Как написать производительный `vec3::operator[]` без UB на C++?C++

Программы на C++. Форум разработчиков
Ответить Пред. темаСлед. тема
Anonymous
 Как написать производительный `vec3::operator[]` без UB на C++?

Сообщение Anonymous »

Заранее отказ от ответственности: этот вопрос больше похож на вопрос лингвиста или попытку определить границы возможностей компиляторов, поэтому, пожалуйста, не зацикливайтесь слишком на необходимости следующих требований.< /p>
Предположим, я хочу написать собственный класс vec3, который хранит три числа с плавающей запятой в виде массива (или вектора в математическом смысле) на C++, и я ожидаю, что он будет соответствовать следующим спецификациям:
  • Его отдельные члены должны быть доступны напрямую, как v.x, а не через средства доступа, такие как v.x(), поскольку я хочу использовать его взаимозаменяемо с, скажем, glm::vec3.
  • Он должен иметь оператор[], который возвращает соответствующий элемент в этом измерении, а возвращаемое значение должно иметь ссылочный тип float&, поскольку Я хочу использовать его в общем контексте, который принимает любой динамически индексируемый тип, подобный массиву.
  • Его оператор[] реализация должна быть максимально производительной, поскольку ее можно вызывать чертовски много раз каждую миллисекунду.
  • Это должно быть тривиально- копируемые, а x, y и z должны иметь последовательные адреса, так как я хочу напрямую скопировать массив vec3 в память графического процессора и использовать их в шейдере или что-то в этом роде.
  • Его реализация оператора[] не должна вызывать UB.
Я попробовал несколько реализаций, которые я мог бы подумать (https://godbolt.org/z/5fYWb8ET8), но ни один из них не соответствует всем приведенным выше спецификациям: наиболее производительные из них напрямую нарушают некоторые требования языка C++, а остальные не столь производительны. Идеальный сгенерированный код должен напрямую вычислять возвращаемый адрес с использованием адресации базового смещения ( на x64) с this в качестве базы и 4*i в качестве смещения, как и в случае с idx0 и idx1, но их реализация зашла в тупик. область UB:
Реализация:

Код: Выделить всё

struct vec3 {
union {
struct {
float x;
float y;
float z;
};
float xyz[3];
};

[[gnu::used]] constexpr float& idx0(size_t i) {
// UB as violating the strict aliasing rule
return reinterpret_cast(this)[i];
}

[[gnu::used]] constexpr float& idx1(size_t i) {
// The xyz variant is only used here
// UB as accessing union from potentially non-active member
return xyz[i];
}
};
Сгенерирован asm на Clang:

Код: Выделить всё

vec3::idx0(unsigned long):
lea     rax, [rdi + 4*rsi]
ret

vec3::idx1(unsigned long):
lea     rax, [rdi + 4*rsi]
ret
Другие реализации, в том числе ручная диспетчеризация с использованием if, switch, индексация по абсолютным адресам и индексация по относительным адресам, все привели к гораздо менее эффективным ассемблерным кодам:

Код: Выделить всё

struct vec3 {
// same data members as above

[[gnu::used]] constexpr float& idx2(size_t i) {
if (i == 0) {
return x;
} else if (i == 1) {
return y;
} else if (i == 2) {
return z;
} else {
std::unreachable();
}
}

[[gnu::used]] constexpr float& idx3(size_t i) {
switch (i) {
case 0:
return x;
case 1:
return y;
case 2:
return z;
default:
std::unreachable();
}
}

[[gnu::used]] constexpr float& idx4(size_t i) {
auto addrs = std::array{&x, &y, &z};
return *addrs[i];
}

[[gnu::used]] constexpr float& idx5(size_t i) {
auto accessors = std::array{&vec3::x, &vec3::y, &vec3::z};
return this->*accessors[i];
}
};
Сгенерирован asm на Clang:

Код: Выделить всё

vec3::idx2(unsigned long):
mov     rax, rdi
test    rsi, rsi
je      .LBB2_4
cmp     rsi, 2
jne     .LBB2_2
add     rax, 8
.LBB2_4:
ret
.LBB2_2:
add     rax, 4
ret

vec3::idx3(unsigned long):
mov     rax, rdi
test    rsi, rsi
je      .LBB3_4
cmp     rsi, 2
jne     .LBB3_2
add     rax, 8
.LBB3_4:
ret
.LBB3_2:
add     rax, 4
ret

vec3::idx4(unsigned long):
mov     qword ptr [rsp - 24], rdi
lea     rax, [rdi + 4]
mov     qword ptr [rsp - 16], rax
add     rdi, 8
mov     qword ptr [rsp - 8], rdi
mov     rax, qword ptr [rsp + 8*rsi - 24]
ret

vec3::idx5(unsigned long):
mov     rax, rdi
lea     rcx, [rip + .L__const.vec3::idx5(unsigned long).accessors]
add     rax, qword ptr [rcx + 8*rsi]
ret

.L__const.vec3::idx5(unsigned long).accessors:
.quad   0
.quad   4
.quad   8
Я также пытался предоставить компилятору дополнительную информацию (используя [[assume]] или встроенные функции), которая могла бы помочь ему проанализировать числовые отношения между i и возвращенный адрес, но ни один из них не сработал. Итак, вот настоящие вопросы:
  • Есть ли какой-нибудь способ проинструктировать компиляторы генерировать идеальный код, с реализацией, написанной на «стандартном» C++ без UB? Возможно, я просто не нашел правильный способ сообщить составителям дополнительную информацию.
  • Если нет, то это из-за ограничения языковых стандартов или это из-за отсутствия в компиляторах определенных методов оптимизации? В случае с idx5 компиляторы могли бы определить, что загруженное смещение всегда имеет то же значение, что и 4*i.
  • Есть ли какое-либо определенное изменение (или какое-либо существующее предложение) в стандарте C++, которое могло бы помочь в этой ситуации? Самое близкое, что я могу придумать, это следующее: решат ли они проблему?

    Разрешение доступа неактивным членам профсоюза, чьи все члены представляют собой тривиально копируемые типы без дополнений.
  • Дождитесь реализации std::start_lifetime_as и используйте это для реализации оператора[] как idx6.


Подробнее здесь: https://stackoverflow.com/questions/792 ... rator-in-c
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение
  • Как написать производительный и свободный от UB vec3::operator[] на C++?
    Anonymous » » в форуме C++
    0 Ответы
    15 Просмотры
    Последнее сообщение Anonymous
  • Glm :: vec3 to glm :: quat .... glm :: quat to glm :: vec3
    Anonymous » » в форуме C++
    0 Ответы
    11 Просмотры
    Последнее сообщение Anonymous
  • Glm :: vec3 to glm :: quat .... glm :: quat to glm :: vec3
    Anonymous » » в форуме C++
    0 Ответы
    24 Просмотры
    Последнее сообщение Anonymous
  • Glm :: vec3 to glm :: quat .... glm :: quat to glm :: vec3
    Anonymous » » в форуме C++
    0 Ответы
    18 Просмотры
    Последнее сообщение Anonymous
  • Clang не удается создать экземпляр `operator!=()` из `operator==()` с типом возвращаемого значения `auto` для объекта кл
    Anonymous » » в форуме C++
    0 Ответы
    40 Просмотры
    Последнее сообщение Anonymous

Вернуться в «C++»