有没有更快的方法来按位连接两个整数?

Is there a faster way to concatenate two integers bit-wise?

对于我的 C++ 程序,我想按位将两个 32 位无符号整数连接成一个 64 位无符号整数。类似的问题已被问过多次,答案大多与此类似:

#include <cstdint>
#include <iostream>

int main()
{
    std::uint32_t leftHalf = 1;
    std::uint32_t rightHalf = 2;
    
    std::uint64_t concatenated = ((std::uint64_t) leftHalf << 32) | secondHalf;
    
    std::cout << "left=" << leftHalf << " and right=" << rightHalf << " concatenated into " << concatenated << std::endl;
}

Try it online

因为我必须在我的程序中经常执行这种连接,所以我需要它非常高效。使用转换、移位和按位 |,使它看起来像另一种技术(例如使用 memcpy)可能会更快。

有没有比强制转换、移位和按位 | 更快的连接两个整数的方法?

为了完整起见,我的编译方法:

#include <cstdint>
#include <iostream>

int main()
{
    std::uint32_t leftHalf = 1;
    std::uint32_t rightHalf = 2;
    
    std::uint64_t concatenated;

    std::uint32_t *halfIt = &concatenated;

    *halfIt = leftHalf;
    ++halfIt;
    *halfIt = rightHalf;
    
    std::cout << "left=" << leftHalf << " and right=" << rightHalf << " concatenated into " << concatenated << std::endl;
}

如果您找到一种有效的方法来将一个值的一部分(视为 bitset)复制到另一个值的一部分(视为 bitset),则可以额外提高速度。但我想这会有点老套。

顺便说一句,在下面的代码中,函数 concat1 在编译后的代码中比 concat2.

少了一个命令
#include <iostream>
using namespace std;

std::uint64_t concat1(const std::uint32_t& leftHalf, const std::uint32_t& rightHalf){
    std::uint64_t concatenated = leftHalf;
    concatenated <<= 32;
    concatenated |= rightHalf;
    
    return concatenated;
}

std::uint64_t concat2(const std::uint32_t& leftHalf, const std::uint32_t& rightHalf){
    std::uint64_t concatenated = (static_cast<std::uint64_t>(leftHalf) << 32) | rightHalf;
    
    return concatenated;
}

int main() {
    cout << concat1(1,2) <<std::endl;
    cout << concat2(1,2) <<std::endl;
}

您可以在例如https://godbolt.org/。函数 concat1concat2 需要少 mov 次操作。但差异会很小。我估计大约 5% 的运行时间;

concat1(unsigned int const&, unsigned int const&):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        mov     QWORD PTR [rbp-32], rsi
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        mov     QWORD PTR [rbp-8], rax
        sal     QWORD PTR [rbp-8], 32
        mov     rax, QWORD PTR [rbp-32]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        or      QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        pop     rbp
        ret
concat2(unsigned int const&, unsigned int const&):
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-24], rdi
        mov     QWORD PTR [rbp-32], rsi
        mov     rax, QWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        sal     rax, 32
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-32]
        mov     eax, DWORD PTR [rax]
        mov     eax, eax
        or      rax, rdx
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        pop     rbp
        ret

discussion 的组合信息导致原始问题的以下答案:

视情况而定。此外,更快的方法很可能不会有所作为。

根据周围的代码,可以采用以下技术:

  • 可以将 32 位部分存储为数组中的两个元素,然后 memcpy 将该数组存储为 64 位整数。 (suggested by NathanOliver)
  • 根据 32 位值的存储方式,可以使用并行化,例如使用 AVX 命令 (suggested by sgorozco)

但这可能没什么区别

其他操作,例如从 [=39= 获取数据的单次获取],比 多次 bit-wise 操作需要更多 run-time(pointed out by JulianH).因此,对于当前的 CPU 设计,bit-wise 操作可能 运行 加载操作期间,下一条指令等待 time-consuming加载操作即将完成。

最后,强烈建议使用一种方法(例如 https://godbolt.org/gcc -S、perf 等分析器)来确定代码的哪些部分花费的时间最多。