Using gcc 4.9, cross-compiling for ARM using the Linaro toolchain, I have found the compiled result of vector.assign()
changes when adding -std=c++14
, in a way that creates significance performance problems.
I have tried several different ways of doing this allocation + copy, but all of them have this performance problem as long as I'm using std::vector
to do it.
I can reproduce the problem with this toy example:
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
Viewing the assembly, I can see why - the original version (C++03, I'm assuming?) calls memmove, whereas the C++14 version instead adds an extra loop that it looks like is copying the data manually. Looking at the .loc
tags gcc adds with -fverbose-asm
, the instructions in this loop come from stl_construct.h
and stl_uninitialized.h
.
Changing to gcc 5.2.1 (with C++14), it compiles nearly identically to the C++03 example, except with memcpy instead of memmove.
I'm able to get around this problem by using std::unique_ptr<uint8_t[]>
instead of a vector here. However, I would like to get to the bottom of this issue to figure out if other places using vectors could have performance problems and how to potentially fix them (updating to gcc 5.2 isn't practical).
So my question is: Why does it compile differently under C++11/14?
For reference, gcc --version
reports:
arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2014.12) 4.9.3 20141205 (prerelease)
.
Here is the assembly gcc generated:
# 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 @
Aucun commentaire:
Enregistrer un commentaire