首先,许多库功能的实现强烈依赖于编译器优化。删除容器中的对象可以调用destroy,而对于易碎对象而言,destroy不会做任何事情。如果不执行任何操作,则编译器将优化所有逻辑。破坏STL just take a look中的对象涉及很多逻辑。从本质上讲,destroy被调用以确保其处理所有情况,包括自定义分配器。它必须编译,因此对于琐碎的类型,它必须解析为已定义的内容,而仍然不执行任何操作。只是为了使代码尽可能干净。单一责任,deallocator决定如何以及是否需要销毁对象。
关于您的主要问题,您是否使用优化?这是第一个也是最重要的问题。任何未经优化的代码都可以保证正常工作。对于未优化的代码,即使参考提供的复杂性也可能不同。您可以清楚地看到,第一次重新分配所花的时间几乎是原来的两倍,其余时间则相当稳定。
使用此类型的其他操作,您的时间是否更好?您是否尝试将其与普通阵列的性能进行比较?
,
感谢@Snild Dolkow,@ MaciejZałucki和@Andy Jewell
最终问题出在优化级别
https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
如果您使用CMake
,请使用此代码
https://stackoverflow.com/a/45333618/5709159
target_compile_options(opende PRIVATE
"$<$<CONFIG:RELEASE>:-O3>"
"$<$<CONFIG:DEBUG>:-O3>"
)
但是选择所需的优化级别
如果您使用Application.mk
,请使用此代码
https://stackoverflow.com/a/18433696/5709159
,
在Maciej的答案和Andy的评论中,让我们检查生成的代码。
使用此Makefile:
CXX = $(NDKPATH)/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++
CC = $(NDKPATH)/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++
INC = -I$(NDKPATH)/cxx-stl/llvm-libc++/include/
LIB = -L$(NDKPATH)/cxx-stl/llvm-libc++/lib/
CXXFLAGS = -ggdb -O$(OPTLEVEL)
.PHONY: all clean dump
all: dump
dump: test
$(NDKPATH)/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/objdump -d -C test | gawk '/<big|<small|::resize/ {p=1} /^$$/ {p=0} {if (p) print $0}'
clean:
$(RM) test.o test
test: test.o
...和一个非常简单的test.cpp:
#include <vector>
using std::vector;
void big(vector<int>& v) {
v.resize(10000000);
}
void small(vector<int>& v) {
v.resize(0);
}
int main() {
return 0;
}
未经优化的编译(-O0
),请注意big()
和small()
都如何调用resize()
,这会在循环中完成很多工作(如您所也可以在源代码中找到。)
ndk-vector-speed$ export NDKPATH=~/.androidsdk/ndk-bundle
ndk-vector-speed$ make clean && OPTLEVEL=0 make dump
rm -f test.o test
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++ -ggdb -O0 -c -o test.o test.cpp
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++ test.o -o test
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/objdump -d -C test | gawk '/<big|<small|::resize/ {p=1} /^$/ {p=0} {if (p) print }'
0000000000000f04 <big(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)>:
f04: d10083ff sub sp,sp,#0x20
f08: a9017bfd stp x29,x30,[sp,#16]
f0c: 910043fd add x29,#0x10
f10: d292d001 mov x1,#0x9680 // #38528
f14: f2a01301 movk x1,#0x98,lsl #16
f18: f90007e0 str x0,#8]
f1c: f94007e0 ldr x0,#8]
f20: 94000013 bl f6c <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)>
f24: a9417bfd ldp x29,#16]
f28: 910083ff add sp,#0x20
f2c: d65f03c0 ret
0000000000000f30 <small(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)>:
f30: d10083ff sub sp,#0x20
f34: a9017bfd stp x29,#16]
f38: 910043fd add x29,#0x10
f3c: d2800001 mov x1,#0x0 // #0
f40: f90007e0 str x0,#8]
f44: f94007e0 ldr x0,#8]
f48: 94000009 bl f6c <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)>
f4c: a9417bfd ldp x29,#16]
f50: 910083ff add sp,#0x20
f54: d65f03c0 ret
0000000000000f6c <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)>:
f6c: d100c3ff sub sp,#0x30
f70: a9027bfd stp x29,#32]
f74: 910083fd add x29,#0x20
f78: f81f83a0 stur x0,[x29,#-8]
f7c: f9000be1 str x1,#16]
f80: f85f83a0 ldur x0,#-8]
f84: f90003e0 str x0,[sp]
f88: 94000020 bl 1008 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::size() const>
f8c: f90007e0 str x0,#8]
f90: f94007e0 ldr x0,#8]
f94: f9400be1 ldr x1,#16]
f98: eb01001f cmp x0,x1
f9c: 1a9f27e8 cset w8,cc
fa0: 37000048 tbnz w8,#0,fa8 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)+0x3c>
fa4: 14000007 b fc0 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)+0x54>
fa8: f9400be8 ldr x8,#16]
fac: f94007e9 ldr x9,#8]
fb0: eb090101 subs x1,x8,x9
fb4: f94003e0 ldr x0,[sp]
fb8: 9400001e bl 1030 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::__append(unsigned long)>
fbc: 14000010 b ffc <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)+0x90>
fc0: f94007e8 ldr x8,#8]
fc4: f9400be9 ldr x9,#16]
fc8: eb09011f cmp x8,x9
fcc: 1a9f97ea cset w10,hi
fd0: 3700004a tbnz w10,fd8 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)+0x6c>
fd4: 1400000a b ffc <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::resize(unsigned long)+0x90>
fd8: b27e03e8 orr x8,xzr,#0x4
fdc: f94003e9 ldr x9,[sp]
fe0: f9400129 ldr x9,[x9]
fe4: f9400bea ldr x10,#16]
fe8: 9b0a7d08 mul x8,x10
fec: 8b080128 add x8,x9,x8
ff0: f94003e0 ldr x0,[sp]
ff4: aa0803e1 mov x1,x8
ff8: 94000054 bl 1148 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::__destruct_at_end(int*)>
ffc: a9427bfd ldp x29,#32]
1000: 9100c3ff add sp,#0x30
1004: d65f03c0 ret
借助-O2
,编译器可以为我们做很多优化。
首先,resize()
完全消失了;它已被删除,因为没有人需要它。
big()
内联了resize()
中的需求,直接调用了__append()
,并且看起来比我们之前调用的完整resize()
函数更简单。由于我尚未运行此代码,因此无法就此对速度有多大帮助做出任何主张。
small()
现在没有函数调用,没有循环,并且只有五个指令(我在下面手动注释了)。它实际上已变成if (v.begin != v.end) v.end = v.begin
。这当然会很快。
ndk-vector-speed$ make clean && OPTLEVEL=2 make dump
rm -f test.o test
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++ -ggdb -O2 -c -o test.o test.cpp
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang++ test.o -o test
/home/snild/.androidsdk/ndk-bundle/toolchains/llvm/prebuilt/linux-x86_64/aarch64-linux-android/bin/objdump -d -C test | gawk '/<big|<small|::resize/ {p=1} /^$/ {p=0} {if (p) print }'
0000000000000e64 <big(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)>:
e64: a9402408 ldp x8,[x0]
e68: 5292d00a mov w10,#0x9680 // #38528
e6c: 72a0130a movk w10,lsl #16
e70: cb080129 sub x9,x8
e74: 9342fd2b asr x11,#2
e78: eb0a017f cmp x11,x10
e7c: 54000062 b.cs e88 <big(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)+0x24>
e80: cb0b0141 sub x1,x10,x11
e84: 14000011 b ec8 <std::__ndk1::vector<int,std::__ndk1::allocator<int> >::__append(unsigned long)>
e88: 528b400a mov w10,#0x5a00 // #23040
e8c: 72a04c4a movk w10,#0x262,lsl #16
e90: eb0a013f cmp x9,x10
e94: 540000a0 b.eq ea8 <big(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)+0x44>
e98: 528b4009 mov w9,#0x5a00 // #23040
e9c: 72a04c49 movk w9,lsl #16
ea0: 8b090108 add x8,x9
ea4: f9000408 str x8,[x0,#8]
ea8: d65f03c0 ret
0000000000000eac <small(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)>:
eac: a9402408 ldp x8,[x0] // load the first two values (begin and end) from v
eb0: eb08013f cmp x9,x8 // compare them
eb4: 54000040 b.eq ebc <small(std::__ndk1::vector<int,std::__ndk1::allocator<int> >&)+0x10>
// skip to 'ret' if they were equal
eb8: f9000408 str x8,#8] // write v.begin to v.end
ebc: d65f03c0 ret // return.
结论:Maciej和Andy是正确的;您没有在启用优化的情况下进行构建。