Прежде всего, причина, по которой ваша оригинальная реализация from
не может быть оптимизирована, заключается в том, что вы передаете аргументы по ссылке и по указателю. Таким образом, компилятор должен учитывать возможность того, что оба они указывают на один и тот же адрес (или, по крайней мере, на то, что они перекрываются). Поскольку у вас есть 8 последовательных операций чтения и записи по (потенциально) одному и тому же адресу, правило «как будто» *1003* здесь не может быть применено.
Обратите внимание, что просто удалив &
из сигнатуры функции, очевидно, GCC уже рассматривает это как доказательство , что bytes
не указывает на x
, и, таким образом, это можно безопасно оптимизировать. , Однако для Clang этого недостаточно .
Технически, конечно, bytes
может указывать на стековую память from
(или x
), но я думаю, что это будет неопределенное поведение, и поэтому Clang просто пропускает эту оптимизацию.
Ваша реализация to
не страдает от этой проблемы, потому что вы реализовали ее таким образом, что сначала вы читаете все значения bytes
и , а затем Вы делаете одно большое задание на x
. Таким образом, даже если x
и bytes
указывают на один и тот же адрес, так как вы сначала выполняете все чтение, а затем всю запись (вместо того, чтобы смешивать чтение и запись, как вы делаете в from
), это можно оптимизировать.
Ответ Флавио Торибио работает, потому что он делает именно это: сначала читает все значения, а затем записывает их в место назначения.
Однако, есть менее сложные способы достичь этого:
void from(uint64_t x, uint8_t* dest) {
uint8_t bytes[8];
bytes[7] = uint8_t(x >> 8*7);
bytes[6] = uint8_t(x >> 8*6);
bytes[5] = uint8_t(x >> 8*5);
bytes[4] = uint8_t(x >> 8*4);
bytes[3] = uint8_t(x >> 8*3);
bytes[2] = uint8_t(x >> 8*2);
bytes[1] = uint8_t(x >> 8*1);
bytes[0] = uint8_t(x >> 8*0);
*(uint64_t*)dest = *(uint64_t*)bytes;
}
компилируется в
mov qword ptr [rsi], rdi
ret
на младшем порядке и до
rev x8, x0
str x8, [x1]
ret
на старом порядке байтов.
Обратите внимание, что даже если вы передадите x
по ссылке, Clang сможет оптимизировать это. Однако это привело бы к еще одной инструкции:
mov rax, qword ptr [rdi]
mov qword ptr [rsi], rax
ret
и
ldr x8, [x0]
rev x8, x8
str x8, [x1]
ret
соответственно.
Также обратите внимание, что вы можете улучшить реализацию to
с помощью подобного трюка: вместо передачи результата по неконстантной ссылке, используйте более естественный подход и просто верните его из функции:
uint64_t to(const uint8_t* bytes) {
return
(uint64_t(bytes[7]) << 8*7) |
(uint64_t(bytes[6]) << 8*6) |
(uint64_t(bytes[5]) << 8*5) |
(uint64_t(bytes[4]) << 8*4) |
(uint64_t(bytes[3]) << 8*3) |
(uint64_t(bytes[2]) << 8*2) |
(uint64_t(bytes[1]) << 8*1) |
(uint64_t(bytes[0]) << 8*0);
}
Резюме:
- Не передавайте аргументы по ссылке.
- Сначала прочитайте все, затем все.
Вот лучшие решения, которые я смог найти для little endian и big endian . Обратите внимание, что to
и from
являются действительно обратными операциями, которые могут быть оптимизированы для запрета операций, если они выполняются одна за другой.