一段奇怪的汇编代码
Table of Contents
1. 问题出现
下面代码在profile的时候发现,每次循环的时候rax在和奇怪的地址进行比较 0x16181e0
template <typename T> inline uint32_t FixedLengthColumnBase<T>::serialize(size_t idx, uint8_t* pos) { strings::memcpy_inlined(pos, &_data[idx], sizeof(T)); return sizeof(T); } template <typename T> void FixedLengthColumnBase<T>::serialize_batch(uint8_t* __restrict__ dst, Buffer<uint32_t>& slice_sizes, size_t chunk_size, uint32_t max_one_row_size) { for (size_t i = 0; i < chunk_size; ++i) { slice_sizes[i] += serialize(i, dst + i * max_one_row_size + slice_sizes[i]); } }
其中rax这里在和某个奇怪的地址进行比较。一个 `奇怪` 的怀疑是判断地址空间是否越界,如果越界的话会去resize空间大小。编译器没有办法确定slice_sizes大小是可以保证不会产生越界情况。
2. 避免使用vector
改成下面这样的写法,似乎也不太行,那个奇怪的地址比较还在。不过代码好像清爽不少。
template <typename T> inline uint32_t FixedLengthColumnBase<T>::serialize(size_t idx, uint8_t* pos) { strings::memcpy_inlined(pos, &_data[idx], sizeof(T)); return sizeof(T); } template <typename T> void FixedLengthColumnBase<T>::serialize_batch(uint8_t* __restrict__ dst, Buffer<uint32_t>& slice_sizes, size_t chunk_size, uint32_t max_one_row_size) { uint32_t* sizes = slice_sizes.data(); for (size_t i = 0; i < chunk_size; ++i) { sizes[i] += serialize(i, dst + i * max_one_row_size + sizes[i]); } }
3. 反汇编 0x16181e0 地址
使用命令 `objdump -S –start-address=0x16181e0 –stop-address=0x16281e0 output/be/lib/starrocks_be` 可以看到这个奇怪地址对应的汇编/代码,就是这个memcpy_inlined,并且是针对长度为1的特定代码。
我觉得 `call *rax` 那个部分代码,是编译器认为如果长度不是1的话,那么就会跳转到原始的memcpy实现上。但是纯粹从C++代码来看,这个size是可以确定为 `sizeof(T)` 并且完全不会变的,不太清楚为什么编译器没有做这个优化,或者是完成这个推理。
output/be/lib/starrocks_be: file format elf64-x86-64 Disassembly of section .text: 00000000016181e0 <_ZN9starrocks10vectorized21FixedLengthColumnBaseIaE9serializeEmPh>: // parameter of memcpy is a constant. switch (size) { case 0: break; case 1: memcpy(dst, src, 1); 16181e0: 48 8b 47 10 mov 0x10(%rdi),%rax 16181e4: 0f b6 04 30 movzbl (%rax,%rsi,1),%eax 16181e8: 88 02 mov %al,(%rdx) template <typename T> inline uint32_t FixedLengthColumnBase<T>::serialize(size_t idx, uint8_t* pos) { strings::memcpy_inlined(pos, &_data[idx], sizeof(T)); return sizeof(T); } 16181ea: b8 01 00 00 00 mov $0x1,%eax 16181ef: c3 retq 00000000016181f0 <_ZN9starrocks10vectorized21FixedLengthColumnBaseIaE17serialize_defaultEPh>: 16181f0: c6 06 00 movb $0x0,(%rsi) template <typename T> uint32_t FixedLengthColumnBase<T>::serialize_default(uint8_t* pos) { ValueType value{}; strings::memcpy_inlined(pos, &value, sizeof(T)); return sizeof(T); } 16181f3: b8 01 00 00 00 mov $0x1,%eax 16181f8: c3 retq 16181f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
4. 手工使用memcpy
template <typename T> void FixedLengthColumnBase<T>::serialize_batch(uint8_t* __restrict__ dst, Buffer<uint32_t>& slice_sizes, size_t chunk_size, uint32_t max_one_row_size) { uint32_t* sizes = slice_sizes.data(); T* __restrict__ src = _data.data(); for (size_t i = 0; i < chunk_size; ++i) { memcpy(dst + i * max_one_row_size + sizes[i], src + i, sizeof(T)); } for (size_t i = 0; i < chunk_size; i++) { sizes[i] += sizeof(T); } }
可以看到最后生成的代码就没有这个奇怪的比较了。