如果要将uint64_t
转换为uint8_t[8]
(小端)。在小端架构上,您只能执行丑陋的reinterpret_cast<>
或memcpy()
,例如:
void from_memcpy(const std::uint64_t &x, uint8_t* bytes) {
std::memcpy(bytes, &x, sizeof(x));
}
这将产生高效的组装:
mov rax, qword ptr [rdi]
mov qword ptr [rsi], rax
ret
但是它不是便携式的。在小端机器上,它将具有不同的行为。
要将uint8_t[8]
转换为uint64_t
,有一个很好的解决方案-只需这样做:
void to(const std::uint8_t* bytes, std::uint64_t &x) {
x = (std::uint64_t(bytes[0]) << 8*0) |
(std::uint64_t(bytes[1]) << 8*1) |
(std::uint64_t(bytes[2]) << 8*2) |
(std::uint64_t(bytes[3]) << 8*3) |
(std::uint64_t(bytes[4]) << 8*4) |
(std::uint64_t(bytes[5]) << 8*5) |
(std::uint64_t(bytes[6]) << 8*6) |
(std::uint64_t(bytes[7]) << 8*7);
}
这看起来效率低下,但实际上使用Clang -O2
会生成与以前完全相同的程序集,如果在大型字节序计算机上进行编译,它将足够聪明以使用本机字节交换指令。例如。此代码:
void to(const std::uint8_t* bytes, std::uint64_t &x) {
x = (std::uint64_t(bytes[7]) << 8*0) |
(std::uint64_t(bytes[6]) << 8*1) |
(std::uint64_t(bytes[5]) << 8*2) |
(std::uint64_t(bytes[4]) << 8*3) |
(std::uint64_t(bytes[3]) << 8*4) |
(std::uint64_t(bytes[2]) << 8*5) |
(std::uint64_t(bytes[1]) << 8*6) |
(std::uint64_t(bytes[0]) << 8*7);
}
编译为:
mov rax, qword ptr [rdi]
bswap rax
mov qword ptr [rsi], rax
ret
我的问题是:是否存在用于反向转换的等效可靠优化的构造?我已经尝试过了,但是天真地编译了它:
void from(const std::uint64_t &x, uint8_t* bytes) {
bytes[0] = x >> 8*0;
bytes[1] = x >> 8*1;
bytes[2] = x >> 8*2;
bytes[3] = x >> 8*3;
bytes[4] = x >> 8*4;
bytes[5] = x >> 8*5;
bytes[6] = x >> 8*6;
bytes[7] = x >> 8*7;
}
编辑:经过一些试验,只要您使用uint8_t* __restrict__ bytes
,此代码就可以在GCC 8.1和更高版本中得到最佳编译。但是我仍然没有找到Clang会优化的形式。
答案 0 :(得分:3)
这是我根据OP评论中的讨论可以测试的内容:
void from_optimized(const std::uint64_t &x, std::uint8_t* bytes) {
std::uint64_t big;
std::uint8_t* temp = (std::uint8_t*)&big;
temp[0] = x >> 8*0;
temp[1] = x >> 8*1;
temp[2] = x >> 8*2;
temp[3] = x >> 8*3;
temp[4] = x >> 8*4;
temp[5] = x >> 8*5;
temp[6] = x >> 8*6;
temp[7] = x >> 8*7;
std::uint64_t* dest = (std::uint64_t*)bytes;
*dest = big;
}
这样看起来会使编译器更清楚,并使它假定必要的参数来对其进行优化(在GCC和带有-O2
的Clang上)。
在Clang 8.0.0(test on Godbolt)上编译为x86-64
(小端):
mov rax, qword ptr [rdi]
mov qword ptr [rsi], rax
ret
在Clang 8.0.0(test on Godbolt)上编译为aarch64_be
(大端):
ldr x8, [x0]
rev x8, x8
str x8, [x1]
ret
答案 1 :(得分:2)
首先,无法优化原始from
实现的原因是因为您要通过引用和指针传递参数。因此,编译器必须考虑它们两者都指向同一地址(或至少它们重叠)的可能性。由于您对(可能)相同的地址进行了8次连续的读取和写入操作,因此as-if rule无法在此处应用。
请注意,仅通过从函数签名中删除&
,显然GCC already considers this as proof bytes
并不指向x
,因此可以安全地对其进行优化。但是,for Clang this is not good enough。
从技术上讲,bytes
当然可以指向from
的堆栈内存(也称为x
),但是我认为这是未定义的行为,因此Clang只是错过了这种优化。
您对to
的实现不会遇到此问题,因为您以 first 的方式读取了bytes
和然后,您对x
进行了一项大任务。因此,即使x
和bytes
指向同一个地址,因为您先进行所有阅读,然后进行所有书写(而不是像在from
中那样进行读写操作),可以优化。
Flávio Toribio's answer之所以起作用,是因为它正是这样做的:它首先读取所有值,然后才写入目标。
但是,实现这一目标的方法比较简单:
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
的实现:与其通过非const引用传递结果,还可以采用“更自然”的方法,并从函数中将其返回: / p>
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
是真正的逆运算,如果一个接一个地执行,则可以优化为无操作。
答案 2 :(得分:2)
关于返回值呢? 易于推理和小型组装:
#include <cstdint>
#include <array>
auto to_bytes(std::uint64_t x)
{
std::array<std::uint8_t, 8> b;
b[0] = x >> 8*0;
b[1] = x >> 8*1;
b[2] = x >> 8*2;
b[3] = x >> 8*3;
b[4] = x >> 8*4;
b[5] = x >> 8*5;
b[6] = x >> 8*6;
b[7] = x >> 8*7;
return b;
}
和大字节序:
#include <stdint.h>
struct mybytearray
{
uint8_t bytes[8];
};
auto to_bytes(uint64_t x)
{
mybytearray b;
b.bytes[0] = x >> 8*0;
b.bytes[1] = x >> 8*1;
b.bytes[2] = x >> 8*2;
b.bytes[3] = x >> 8*3;
b.bytes[4] = x >> 8*4;
b.bytes[5] = x >> 8*5;
b.bytes[6] = x >> 8*6;
b.bytes[7] = x >> 8*7;
return b;
}
(std :: array无法用于-target aarch64_be?)
答案 3 :(得分:0)
您提供的代码过于复杂。您可以将其替换为:
void from(uint64_t x, uint8_t* dest) {
x = htole64(x);
std::memcpy(dest, &x, sizeof(x));
}
是的,它使用Linux-ism htole64()
,但是如果您在另一个平台上,则可以轻松地重新实现它。
在小端和大端平台上,Clang和GCC可以完美地优化这一点。