C++ 将字节转换为无符号整数的最快方法

C++ 将字节转换为无符号整数的最快方法,c++,performance,byte,memory-bandwidth,C++,Performance,Byte,Memory Bandwidth,我有一个字节数组(无符号字符*),必须将其转换为整数。整数由三个字节表示。这就是我所做的 //bytes array is allocated and filled //allocating space for intBuffer (uint32_t) unsigned long i = 0; uint32_t number; for(; i<size_tot; i+=3){ uint32_t number = (bytes[i]<<16) | (bytes[i+1]&

我有一个字节数组(
无符号字符*
),必须将其转换为整数。整数由三个字节表示。这就是我所做的

//bytes array is allocated and filled
//allocating space for intBuffer (uint32_t)
unsigned long i = 0;
uint32_t number;
for(; i<size_tot; i+=3){
    uint32_t number = (bytes[i]<<16) | (bytes[i+1]<<8) | bytes[i+2];
    intBuffer[number]++;
}
//字节数组已分配和填充
//为intBuffer(uint32\t)分配空间
无符号长i=0;
uint32_t编号;

对于(;i假设您要对所有不同的值进行计数(您的代码:
intBuffer[number]++;
)(intBuffer有2^24个项),您可以尝试执行以下操作:

而不是:

for(; i<size_tot; i+=3){
    uint32_t number = (bytes[i]<<16) | (bytes[i+1]<<8) | bytes[i+2];
    intBuffer[number]++;
}

for(;i首先确保编译器优化已转到最高级别

我想我会尝试一下:

unsigned char* pBytes = bytes;
uint32_t number;

for(unsigned long i = 0; i<size_tot; i+=3){
    number = *pBytes << 16;
    ++pBytes;
    number = number | (*pBytes << 8);
    ++pBytes;
    number = number | *pBytes;
    ++pBytes;

    ++intBuffer[number];
}
无符号字符*pBytes=字节;
uint32_t编号;

对于(无符号长i=0;i而言,正确答案几乎总是:

编写正确的代码,启用优化,信任您的编译器

鉴于:

void count_values(std::array<uint32_t, 256^3>& results,
                  const unsigned char* from,
                  const unsigned char* to)
{
    for(; from != to; from  = std::next(from, 3)) {
        ++results[(*from << 16) | (*std::next(from, 1) << 8) | *(std::next(from,2))];
    }
}
请注意,没有必要偏离标准构造或标准调用。编译器生成完美的代码

为了进一步证明这一点,让我们疯狂地编写一个自定义迭代器,它允许我们将函数简化为:

void count_values(std::array<uint32_t, 256^3>& results,
                  byte_triple_iterator from,
                  byte_triple_iterator to)
{
    assert(iterators_correct(from, to));
    while(from != to) {
        ++results[*from++];
    }
}
回答:没什么-它同样有效


教训?不是真的!相信你的编译器!!!

尝试一次读取一个单词,然后提取所需的值。这应该比逐字节读取效率更高

下面是64位little endian系统上的一个示例实现,它将一次读取3个64位值

void count(uint8_t* bytes, int* intBuffer, uint32_t size_tot)
{
    assert(size_tot > 7);
    uint64_t num1, num2, num3;
    uint8_t *bp = bytes;
    while ((uintptr_t)bp % 8) // make sure that the pointer is properly aligned
    {
        num1 = (bp[2] << 16) | (bp[1] << 8) | bp[0];
        intBuffer[num1]++;
        bp += 3;
    }

    uint64_t* ip = (uint64_t*)bp;
    while ((uint8_t*)(ip + 2) < bytes + size_tot)
    {
        num1 = *ip++;
        num2 = *ip++;
        num3 = *ip++;

        intBuffer[num1 & 0xFFFFFF]++;
        intBuffer[(num1 >> 24) & 0xFFFFFF]++;
        intBuffer[(num1 >> 48) | ((num2 & 0xFF) << 16)]++;
        intBuffer[(num2 >> 8) & 0xFFFFFF]++;
        intBuffer[(num2 >> 32) & 0xFFFFFF]++;
        intBuffer[(num2 >> 56) | ((num3 & 0xFFFF) << 8)]++;
        intBuffer[(num3 >> 16) & 0xFFFFFF]++;
        intBuffer[num3 >> 40]++;
    }

    bp = (uint8_t*)ip;
    while (bp < bytes + size_tot)
    {
        num1 = (bp[2] << 16) | (bp[1] << 8) | bp[0];
        intBuffer[num1]++;
        bp += 3;
    }
}

但是为了获得更高的性能,您可能需要寻求SSE或AVX之类的SIMD解决方案,您确定每次都要覆盖
number
,并且只有3个字节是一个整数吗?除非您在没有缓存和预取器的CPU上运行此解决方案,否则此代码将不会生成大量实际内存读取。有什么您不知道的吗欠我们的债?(比如说你实际上并没有把
数字
重写10万次?)还有,转换后你还需要字节数据吗?这是一个非常奇怪的循环加法。问题很可能不是读取或转换,而是随机写入。@Gernot1976错了(对于little/big-endian体系结构)你是在建议一种循环展开吗?我认为这是由硬件优化或编译器完成的,我不知道……我不想多说,因为我不是这方面的专家;)@J.kol-是的,这是我在回答中说的:)不确定编译器是否会自动执行此操作,因为您每次都在重复使用
number
。您还可以使用编译器和数据进行快速测试。(当然也取决于cpu)@J.kol-但请记住,在您的代码中,您正在制作某种柱状图。如果您需要所有整数的列表,则必须更改代码。(但您可能正在读取RGB值,因此柱状图在这里可能有意义)。@J.kol-“蛋糕的证明在于吃”:我很想知道这对你的系统有没有影响。我认为编译器不会自己“展开”数字。速度增益将取决于您拥有的cpu类型(以及编译的目的)。仅供参考:我使用g++5.1,-O3对您的循环展开进行了计时测试。这可能有点帮助,但区别在于测量噪音。我认为你的答案基本上是正确的,但“相信你的编译器”有点夸张了。虽然这种情况非常罕见,但我发现很多情况下,一些非直接代码比直接代码更快。更正确的说法可能是“不要假设你能做一些可以提高性能的技巧。”@VaughnCato我听到了,当然在编写代码的30年中,我有时也不得不手工编写代码。但大部分时间都是15年前。如今,这是最后的手段——当正确的算法被选择、优雅而正确地实现时,就没有其他可能的性能瓶颈(如I/O、缓存未命中、错过并行化机会等),用户仍然告诉我程序很慢。。。只有这样,你才能卷起袖子,对编译器进行二次猜测。如果我们不需要,为什么要支付自定义代码的维护费用呢?“相信你的编译器!!!”-同意,但由于我遇到
uint-var/2
uint-var>>1
(几年前……)慢,我失去了一点信心。当编译器越来越好时,有时我们可能想尝试帮助他们一点(在某些情况下甚至不允许编译器优化某些部分)。@Danny_ds使用开源编译器的好处在于,如果性能可以提高,我们要么提交错误报告,要么提交补丁。通过这种方式,编译器很快就变得非常好。你提到的情况确实令人惊讶。自从我在80年代第一次学习C语言以来,编译器一直在优化乘法和二次幂除法。它还会发生吗?嗯。。我有点执着于Windows(计划尽快在Linux上进行一些编程)-它是用Visual Studio和标准MS编译器实现的。我也很惊讶,看着集合,它就在那里:师!(在启用优化的情况下)。不过现在似乎已经修好了。“现在编译器所能做的事情确实令人惊讶。”好奇的家伙没有注意到that@L在未粘贴的指针上,这可能是编译器错误。在这里,
%4
&3
应该比任何地方都快得多(好吧,也许您的编译器优化已经做到了这一点)
void count_values(std::array<uint32_t, 256^3>& results,
                  byte_triple_iterator from,
                  byte_triple_iterator to)
{
    assert(iterators_correct(from, to));
    while(from != to) {
        ++results[*from++];
    }
}
struct byte_triple_iterator
{
    constexpr byte_triple_iterator(const std::uint8_t* p)
    : _ptr(p)
    {}

    std::uint32_t operator*() const noexcept {
        return (*_ptr << 16) | (*std::next(_ptr, 1) << 8) | *(std::next(_ptr,2));
    }

    byte_triple_iterator& operator++() noexcept {
        _ptr = std::next(_ptr, 3);
        return *this;
    }

    byte_triple_iterator operator++(int) noexcept {
        auto copy = *this;
        _ptr = std::next(_ptr, 3);
        return copy;
    }

    constexpr const std::uint8_t* byte_ptr() const {
        return _ptr;
    }

private:

    friend bool operator<(const byte_triple_iterator& from, const byte_triple_iterator& to)
    {
        return from._ptr < to._ptr;
    }

    friend bool operator==(const byte_triple_iterator& from, const byte_triple_iterator& to)
    {
        return from._ptr == to._ptr;
    }

    friend bool operator!=(const byte_triple_iterator& from, const byte_triple_iterator& to)
    {
        return not(from == to);
    }

    friend std::ptrdiff_t byte_difference(const byte_triple_iterator& from, const byte_triple_iterator& to)
    {
        return to._ptr - from._ptr;
    }

    const std::uint8_t* _ptr;
};

bool iterators_correct(const byte_triple_iterator& from,
                       const byte_triple_iterator& to)
{
    if (not(from < to))
        return false;
    auto dist = to.byte_ptr() - from.byte_ptr();
    return dist % 3 == 0;
}
    .globl  __Z12count_valuesRNSt3__15arrayIjLm259EEE20byte_triple_iteratorS3_
    .align  4, 0x90
__Z12count_valuesRNSt3__15arrayIjLm259EEE20byte_triple_iteratorS3_: ## @_Z12count_valuesRNSt3__15arrayIjLm259EEE20byte_triple_iteratorS3_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp3:
    .cfi_def_cfa_offset 16
Ltmp4:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp5:
    .cfi_def_cfa_register %rbp
    jmp LBB1_2
    .align  4, 0x90
LBB1_1:                                 ## %.lr.ph
                                        ##   in Loop: Header=BB1_2 Depth=1
    movzbl  (%rsi), %eax
    shlq    $16, %rax
    movzbl  1(%rsi), %ecx
    shlq    $8, %rcx
    orq %rax, %rcx
    movzbl  2(%rsi), %eax
    orq %rcx, %rax
    incl    (%rdi,%rax,4)
    addq    $3, %rsi
LBB1_2:                                 ## %.lr.ph
                                        ## =>This Inner Loop Header: Depth=1
    cmpq    %rdx, %rsi
    jne LBB1_1
## BB#3:                                ## %._crit_edge
    popq    %rbp
    retq
    .cfi_endproc
void count(uint8_t* bytes, int* intBuffer, uint32_t size_tot)
{
    assert(size_tot > 7);
    uint64_t num1, num2, num3;
    uint8_t *bp = bytes;
    while ((uintptr_t)bp % 8) // make sure that the pointer is properly aligned
    {
        num1 = (bp[2] << 16) | (bp[1] << 8) | bp[0];
        intBuffer[num1]++;
        bp += 3;
    }

    uint64_t* ip = (uint64_t*)bp;
    while ((uint8_t*)(ip + 2) < bytes + size_tot)
    {
        num1 = *ip++;
        num2 = *ip++;
        num3 = *ip++;

        intBuffer[num1 & 0xFFFFFF]++;
        intBuffer[(num1 >> 24) & 0xFFFFFF]++;
        intBuffer[(num1 >> 48) | ((num2 & 0xFF) << 16)]++;
        intBuffer[(num2 >> 8) & 0xFFFFFF]++;
        intBuffer[(num2 >> 32) & 0xFFFFFF]++;
        intBuffer[(num2 >> 56) | ((num3 & 0xFFFF) << 8)]++;
        intBuffer[(num3 >> 16) & 0xFFFFFF]++;
        intBuffer[num3 >> 40]++;
    }

    bp = (uint8_t*)ip;
    while (bp < bytes + size_tot)
    {
        num1 = (bp[2] << 16) | (bp[1] << 8) | bp[0];
        intBuffer[num1]++;
        bp += 3;
    }
}
intBuffer[num1 >> 8]++;
intBuffer[((num1 & 0xFF) << 16) | (num2 >> 16)]++;
intBuffer[((num2 & 0xFFFF) << 8) | (num3 >> 24)]++;
intBuffer[num3 & 0xFFFFFF]++;