2017-02-01 32 views
11

Używanie gcc 4.9, cross-kompilacja dla ARM przy użyciu toolchain Linaro, Znalazłem skompilowany wynik vector.assign() zmian podczas dodawania -std=c++14, w sposób, który tworzy problemy z wydajnością.std :: vector <uint8_t> ręczne kopiowanie zamiast wywoływania memcpy, gdy włączono C++ 11/14

Próbowałem kilku różnych sposobów wykonywania tej alokacji + kopiowania, ale wszystkie z nich mają ten problem z wydajnością, o ile używam do tego celu std::vector.

mogę odtworzyć problemu z tym przykładzie zabawki:

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 flagi:

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

Przeglądając zestawienie, widzę, dlaczego oryginalna wersja (C++ 03, zakładam?) Wywołuje memmove, podczas gdy wersja C++ 14 zamiast tego dodaje dodatkową pętlę, która wygląda jak to jest kopiowanie danych ręcznie. Patrząc na tagi .loc gcc z -fverbose-asm, instrukcje w tej pętli pochodzą z stl_construct.h i stl_uninitialized.h.

Zmiana na gcc 5.2.1 (z C++ 14), kompiluje się prawie identycznie jak w przykładzie C++ 03, z wyjątkiem memcpy zamiast memmove.

Jestem w stanie obejść ten problem, używając std::unique_ptr<uint8_t[]> zamiast tutaj vector. Chciałbym jednak przejść do sedna tego problemu, aby dowiedzieć się, czy inne miejsca używające vector mogą mieć problemy z wydajnością i jak je naprawiać (aktualizacja do gcc 5.2 jest niepraktyczna).

Moje pytanie brzmi: Dlaczego kompiluje się inaczej w C++ 11/14?

Dla porównania, gcc --version raporty:
arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2014.12) 4.9.3 20141205 (prerelease).

Oto gcc montaż generowane:

# 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 @ 
+9

Prawdopodobnie nie zrobi dużej różnicy, ale dlaczego przypisać zamiast zainicjować? Te ostatnie mogą uniknąć jednej alokacji. 'VectorWrapper_t (uint8_t const * pData, size_t length): data (pData, pData + długość) {}' – juanchopanza

+0

To pytanie wydaje się lepiej pasować do raportu o błędach Linaro. – ildjarn

+3

2 możliwości: kod zaczyna się tak samo, ale gcc-5 + wykrywa, że ​​pętla jest odpowiednikiem memcpy i zastępuje je wywołaniem memcpy, lub libstdC++ zyskał nową specjalną ścieżkę, która wywołuje memcpy. Spróbuj przekazać '-fdump-tree-all' i spojrzeć na niektóre wczesne/późne zrzuty, aby ustalić, co się dzieje. –

Odpowiedz

10

Był to bug GCC w wersji 4.9.2, zobacz PR 64476. Różnica między domyślnym trybem -std=gnu++03 a polega na tym, że w C++ 11 i późniejszych możliwe są tryby trywialne, których nie można przypisać (ponieważ mogą one mieć usuniętego operatora przypisania), co powoduje, że implementacja std::uninitialized_copy przybiera inną (wolniej) ścieżka kodowa. Sprawdzanie możliwości przypisania było błędne, co oznacza, że ​​podjęliśmy powolną ścieżkę, kiedy nie musieliśmy.

Naprawiłem to dwa lata temu dla GCC 4.9.3, ale twój kompilator jest oparty na migawce utworzonej między wersjami 4.9.2 i 4.9.3 i jest za kilka tygodni za stary, żeby naprawić.

Możesz poprosić Linaro o zaktualizowanie ich kompilatora GCC 4.9 do wersji 4.9.4, lub przynajmniej o zastosowanie poprawki naprawiającej ten błąd.