Std::vector <uint8_t> ручное копирование вместо вызова memcpy при включенном С++ 11/14

Используя gcc 4.9, кросс-компиляцию для ARM с помощью инструментальной привязки Linaro, я нашел скомпилированный результат изменений vector.assign() при добавлении -std=c++14, что создает проблемы с производительностью значимости.

Я пробовал несколько разных способов сделать это распределение + копия, но все они имеют эту проблему с производительностью, если я использую std::vector для этого.

Я могу воспроизвести проблему с этим примером игрушек:

VectorTest.h

#include <stdint.h>
#include <stddef.h>
#include <vector>

struct VectorWrapper_t
{
    VectorWrapper_t(uint8_t const* pData, size_t length);
    std::vector<uint8_t> data;
};

VectorTest.cpp

#include "VectorTest.h"

VectorWrapper_t::VectorWrapper_t(uint8_t const* pData, size_t length)
{
    data.assign(pData, pData + length);
}

gcc flags:

-std=c++14 \
-mthumb -march=armv7-a -mtune=cortex-a9 \
-mlittle-endian -mfloat-abi=hard -mfpu=neon -Wa,-mimplicit-it=thumb \
-O2 -g

Просмотр сборки, я могу понять, почему исходная версия (С++ 03, я предполагаю?) вызывает memmove, тогда как версия С++ 14 вместо этого добавляет дополнительный цикл, который, по-видимому, копирует данные вручную. Глядя на теги .loc, gcc добавляет с помощью -fverbose-asm, инструкции в этом цикле исходят от stl_construct.h и stl_uninitialized.h.

Переход на gcc 5.2.1 (с С++ 14), он почти точно сопоставляется с примером С++ 03, за исключением memcpy вместо memmove.

Я могу обойти эту проблему, используя std::unique_ptr<uint8_t[]> вместо vector здесь. Тем не менее, я хотел бы разобраться в этом вопросе, чтобы выяснить, могут ли проблемы с использованием vector использовать другие места, и как их можно исправить (обновление до gcc 5.2 нецелесообразно).

Итак, мой вопрос: Почему он компилируется по-другому в С++ 11/14?

Для справки, gcc --version сообщает:
arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2014.12) 4.9.3 20141205 (prerelease).

Вот сборка gcc сгенерирована:

# C++03, gcc 4.9

    push    {r3, r4, r5, r6, r7, lr}    @
    movs    r3, #0  @ tmp118,
    mov r4, r0  @ this, this
    str r3, [r0]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_start
    mov r5, r2  @ length, length
    str r3, [r0, #4]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_finish
    str r3, [r0, #8]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_end_of_storage
    cbnz    r2, .L19    @ length,
    mov r0, r4  @, this
    pop {r3, r4, r5, r6, r7, pc}    @
.L19:
    mov r0, r2  @, length
    mov r6, r1  @ pData, pData
    bl  _Znwj   @
    mov r2, r5  @, length
    mov r1, r6  @, pData
    mov r7, r0  @ D.13516,
    bl  memmove @
    ldr r0, [r4]    @ D.13515, MEM[(struct vector *)this_1(D)].D.11902._M_impl._M_start
    cbz r0, .L3 @ D.13515,
    bl  _ZdlPv  @
.L3:
    add r5, r5, r7  @ D.13515, D.13516
    str r7, [r4]    @ D.13516, MEM[(struct vector *)this_1(D)].D.11902._M_impl._M_start
    str r5, [r4, #4]    @ D.13515, MEM[(struct vector *)this_1(D)].D.11902._M_impl._M_finish
    mov r0, r4  @, this
    str r5, [r4, #8]    @ D.13515, MEM[(struct vector *)this_1(D)].D.11902._M_impl._M_end_of_storage
    pop {r3, r4, r5, r6, r7, pc}    @
.L6:
    ldr r0, [r4]    @ D.13515, MEM[(struct _Vector_base *)this_1(D)]._M_impl._M_start
    cbz r0, .L5 @ D.13515,
    bl  _ZdlPv  @
.L5:
    bl  __cxa_end_cleanup   @

# C++14, gcc 4.9

    push    {r3, r4, r5, r6, r7, lr}    @
    movs    r3, #0  @ tmp157,
    mov r6, r0  @ this, this
    str r3, [r0]    @ tmp157, MEM[(struct _Vector_impl *)this_1(D)]._M_start
    mov r5, r2  @ length, length
    str r3, [r0, #4]    @ tmp157, MEM[(struct _Vector_impl *)this_1(D)]._M_finish
    str r3, [r0, #8]    @ tmp157, MEM[(struct _Vector_impl *)this_1(D)]._M_end_of_storage
    cbnz    r2, .L25    @ length,
    mov r0, r6  @, this
    pop {r3, r4, r5, r6, r7, pc}    @
.L25:
    mov r0, r2  @, length
    mov r4, r1  @ pData, pData
    bl  _Znwj   @
    adds    r3, r4, r5  @ D.20345, pData, length
    mov r7, r0  @ __result,
    cmp r4, r3  @ pData, D.20345
    ittt    ne
    addne   r1, r4, #-1 @ ivtmp.76, pData,
    movne   r3, r0  @ __result, __result
    addne   r4, r0, r5  @ D.20346, __result, length
    beq .L26    @,
.L7:
    ldrb    r2, [r1, #1]!   @ zero_extendqisi2  @ D.20348, MEM[base: _48, offset: 0]
    cbz r3, .L6 @ __result,
    strb    r2, [r3]    @ D.20348, MEM[base: __result_23, offset: 0B]
.L6:
    adds    r3, r3, #1  @ __result, __result,
    cmp r3, r4  @ __result, D.20346
    bne .L7 @,
.L8:
    ldr r0, [r6]    @ D.20346, MEM[(struct vector *)this_1(D)].D.18218._M_impl._M_start
    cbz r0, .L5 @ D.20346,
    bl  _ZdlPv  @
.L5:
    str r7, [r6]    @ __result, MEM[(struct vector *)this_1(D)].D.18218._M_impl._M_start
    mov r0, r6  @, this
    str r4, [r6, #4]    @ D.20346, MEM[(struct vector *)this_1(D)].D.18218._M_impl._M_finish
    str r4, [r6, #8]    @ D.20346, MEM[(struct vector *)this_1(D)].D.18218._M_impl._M_end_of_storage
    pop {r3, r4, r5, r6, r7, pc}    @
.L26:
    adds    r4, r0, r5  @ D.20346, __result, length
    b   .L8 @
.L11:
    ldr r0, [r6]    @ D.20346, MEM[(struct _Vector_base *)this_1(D)]._M_impl._M_start
    cbz r0, .L10    @ D.20346,
    bl  _ZdlPv  @
.L10:
    bl  __cxa_end_cleanup   @

# C++14, gcc 5.2

    push    {r3, r4, r5, r6, r7, lr}    @
    movs    r3, #0  @ tmp118,
    mov r4, r0  @ this, this
    str r3, [r0]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_start
    str r3, [r0, #4]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_finish
    str r3, [r0, #8]    @ tmp118, MEM[(struct _Vector_impl *)this_1(D)]._M_end_of_storage
    cbnz    r2, .L19    @ length,
    mov r0, r4  @, this
    pop {r3, r4, r5, r6, r7, pc}    @
.L19:
    mov r0, r2  @, length
    mov r6, r1  @ pData, pData
    mov r5, r2  @ length, length
    bl  _Znwj   @
    mov r2, r5  @, length
    mov r1, r6  @, pData
    mov r7, r0  @ D.20824,
    bl  memcpy  @
    ldr r0, [r4]    @ D.20823, MEM[(struct vector *)this_1(D)].D.18751._M_impl._M_start
    cbz r0, .L3 @ D.20823,
    bl  _ZdlPv  @
.L3:
    add r5, r5, r7  @ D.20823, D.20824
    str r7, [r4]    @ D.20824, MEM[(struct vector *)this_1(D)].D.18751._M_impl._M_start
    str r5, [r4, #4]    @ D.20823, MEM[(struct vector *)this_1(D)].D.18751._M_impl._M_finish
    mov r0, r4  @, this
    str r5, [r4, #8]    @ D.20823, MEM[(struct vector *)this_1(D)].D.18751._M_impl._M_end_of_storage
    pop {r3, r4, r5, r6, r7, pc}    @
.L6:
    ldr r0, [r4]    @ D.20823, MEM[(struct _Vector_base *)this_1(D)]._M_impl._M_start
    cbz r0, .L5 @ D.20823,
    bl  _ZdlPv  @
.L5:
    bl  __cxa_end_cleanup   @

Ответ 1

Это ошибка GCC в версии 4.9.2, см. PR 64476. Разница между режимом -std=gnu++03 по умолчанию и -std=c++14 заключается в том, что для С++ 11 и более поздних версий возможно иметь тривиальные типы, которые не могут быть назначены (поскольку они могут иметь удаленный оператор присваивания), что вызывает реализацию std::uninitialized_copy для выбора другого (более медленного) кода. Проверка на назначение была неправильной, что означает, что мы взяли медленный путь, когда нам не нужно.

Я установил его два года назад для GCC 4.9.3, но ваш компилятор основан на моментальном снимке, сделанном между версиями 4.9.2 и 4.9.3, и на несколько недель слишком стар, чтобы иметь исправление.

Вы можете попросить Linaro обновить компилятор GCC 4.9 до 4.9.4 или, по крайней мере, применить исправление, исправляющее эту ошибку.