On Tue, 26 Nov 2019 10:56:41 +0200
Marko Mäkelä wrote:
Hi Daniel,
On Tue, Nov 26, 2019 at 2:02 AM Daniel Black wrote:
On Mon, 25 Nov 2019 11:32:07 +0200
Marko Mäkelä wrote:
I also found a claim that POWER8 supports unaligned access,
This is correct (for the normal cacheable memory (i.e. not device IO mapped - so not applicable to mariadb))
and I seem
to remember that the latest version of the SPARC introduced support
for that as well. (IA-32 and AMD64 have always supported unaligned
access, except for some SIMD operations.)
Last, I believe that we could get some performance benefits if
include/byte_order_generic.h was rewritten in a suitable way. Ideally,
include/byte_order_generic_x86_64.h would be replaced with a portable
version of both, and compilers could simply perform the optimizations.
I have been told that replacing the + in the macros with | could
already be a good start. I would welcome patches in this area.
I've never managed to get the time to look at these however a non-aligned version for non-common arches seems a better way to model this.
I pushed my micro-optimization to 10.5:
https://github.com/MariaDB/server/commit/25e2a556de2e125784d52a0c7ccda4fa659...
If there really is no compiler flag that would allow any memcpy(),
memset(),
memcmp()
Well, actually:
-fno-builtin-mem{cmp,set,cpy}
-mmem{set,cpy}-strategy= (seems x86 only)
of 2,4,8 bytes to be translated into simple
(possibly unaligned) multi-byte instructions,
Actually gcc has put an effort into getting the optimum implementation here already. It doesn't look like a thing an end application should be trying to optimise.
$ rm -f memset_opt.o && gcc -O1 -fomit-frame-pointer -c memset_opt.c -o memset_opt.o && objdump -d memset_opt.o | grep -A 10 vmem
0000000000000000 <vmemset>:
0: c7 07 00 00 00 00 movl $0x0,(%rdi)
6: c3 retq
0000000000000007 <vmemcmp>:
7: 48 83 ec 18 sub $0x18,%rsp
b: 89 7c 24 0c mov %edi,0xc(%rsp)
f: ba 04 00 00 00 mov $0x4,%edx
14: 48 8d 74 24 0c lea 0xc(%rsp),%rsi
19: bf 00 00 00 00 mov $0x0,%edi
1e: e8 00 00 00 00 callq 23
23: 48 83 c4 18 add $0x18,%rsp
27: c3 retq
0000000000000028 <vmemstatic>:
28: b8 ff ff ff ff mov $0xffffffff,%eax
2d: c3 retq
000000000000002e <vmemcpy>:
2e: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 34
34: 89 07 mov %eax,(%rdi)
36: c3 retq
[dan@volution junk]$ cat memset_opt.c
#include
static int comp = 7;
char r[30];
void vmemset(char v[30])
{
memset(v, 0, 4);
}
int vmemcmp(int c)
{
return memcmp(&comp, &c, sizeof(c));
}
int vmemstatic()
{
return memcmp("cat", "dog", 3);
}
void vmemcpy(int *c)
{
memcpy(c, r, sizeof(*c));
}
Not sure why vmemcmp still has a memcpy call, but by vmemstatic some understanding is there.
script to test:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43052#c12
Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz (laptop)
glibc-2.29-22.fc30
gcc (GCC) 9.2.1 20190827 (Red Hat 9.2.1-1)
$ sh test_stringop 64 640000000 gcc -march=native
memcpy mode:64 size:640000000
libcall rep1 noalg rep4 noalg rep8 noalg loop noalg unrl noalg byte profiled dynamic
block size 8192000 0:00.12 0:00.12 0:00.12 0:00.13 0:00.12 0:00.12 0:00.13 0:00.13 0:00.13 0:00.13 0:00.13 0:00.50 0:00.12 0:00.12 best: 0:00.12 libcall
block size 819200 0:00.08 0:00.10 0:00.10 0:00.10 0:00.10 0:00.10 0:00.10 0:00.09 0:00.09 0:00.09 0:00.09 0:00.48 0:00.08 0:00.08 best: 0:00.08 libcall
block size 81920 0:00.04 0:00.05 0:00.05 0:00.05 0:00.04 0:00.04 0:00.05 0:00.09 0:00.08 0:00.07 0:00.08 0:00.51 0:00.04 0:00.04 best: 0:00.04 libcall
block size 20480 0:00.04 0:00.04 0:00.04 0:00.04 0:00.04 0:00.04 0:00.04 0:00.07 0:00.11 0:00.08 0:00.08 0:00.86 0:00.03 0:00.04 best: 0:00.04 libcall
block size 8192 0:00.03 0:00.03 0:00.04 0:00.04 0:00.03 0:00.03 0:00.03 0:00.06 0:00.10 0:00.06 0:00.07 0:00.48 0:00.03 0:00.03 best: 0:00.03 libcall
block size 4096 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.06 0:00.10 0:00.06 0:00.07 0:00.47 0:00.03 0:00.03 best: 0:00.03 libcall
block size 2048 0:00.03 0:00.03 0:00.03 0:00.04 0:00.03 0:00.04 0:00.03 0:00.07 0:00.10 0:00.06 0:00.07 0:00.48 0:00.03 0:00.03 best: 0:00.03 libcall
block size 1024 0:00.04 0:00.04 0:00.04 0:00.05 0:00.04 0:00.05 0:00.04 0:00.08 0:00.11 0:00.07 0:00.07 0:00.49 0:00.03 0:00.04 best: 0:00.04 libcall
block size 512 0:00.05 0:00.06 0:00.06 0:00.06 0:00.05 0:00.06 0:00.05 0:00.09 0:00.12 0:00.07 0:00.07 0:00.50 0:00.09 0:00.06 best: 0:00.05 libcall
block size 256 0:00.07 0:00.08 0:00.08 0:00.09 0:00.08 0:00.09 0:00.08 0:00.10 0:00.12 0:00.09 0:00.09 0:00.52 0:00.10 0:00.09 best: 0:00.07 libcall
block size 128 0:00.11 0:00.13 0:00.13 0:00.15 0:00.13 0:00.15 0:00.13 0:00.14 0:00.14 0:00.12 0:00.11 0:00.56 0:00.12 0:00.12 best: 0:00.11 libcall
block size 64 0:00.20 0:00.20 0:00.20 0:00.24 0:00.22 0:00.24 0:00.22 0:00.19 0:00.20 0:00.18 0:00.19 0:00.75 0:00.18 0:00.18 best: 0:00.18 unrl
block size 48 0:00.25 0:00.28 0:00.28 0:00.31 0:00.31 0:00.31 0:00.29 0:00.23 0:00.22 0:00.22 0:00.23 0:00.66 0:00.22 0:00.22 best: 0:00.22 loopnoalign
block size 32 0:00.38 0:00.40 0:00.38 0:00.44 0:00.39 0:00.45 0:00.38 0:00.30 0:00.32 0:00.30 0:00.31 0:00.93 0:00.31 0:00.31 best: 0:00.30 loop
block size 24 0:00.51 0:00.57 0:00.56 0:00.63 0:00.58 0:00.64 0:00.56 0:00.42 0:00.40 0:00.36 0:00.37 0:00.78 0:00.37 0:00.36 best: 0:00.36 unrl
block size 16 0:00.75 0:00.74 0:00.74 0:00.84 0:00.74 0:00.85 0:00.70 0:00.48 0:00.47 0:00.40 0:00.40 0:00.87 0:00.48 0:00.47 best: 0:00.40 unrl
block size 14 0:00.76 0:00.95 0:00.97 0:01.01 0:00.99 0:01.01 0:00.89 0:00.52 0:00.51 0:00.49 0:00.49 0:00.88 0:00.54 0:00.53 best: 0:00.49 unrl
block size 12 0:00.93 0:01.10 0:01.10 0:01.14 0:01.05 0:01.19 0:00.98 0:00.64 0:00.61 0:00.56 0:00.57 0:00.79 0:00.64 0:00.59 best: 0:00.56 unrl
block size 10 0:01.04 0:01.31 0:01.31 0:01.37 0:01.23 0:01.41 0:01.14 0:00.75 0:00.74 0:00.68 0:00.65 0:00.86 0:00.68 0:00.69 best: 0:00.65 unrlnoalign
block size 8 0:01.36 0:01.59 0:01.55 0:01.68 0:01.37 0:01.64 0:01.18 0:00.79 0:00.79 0:00.73 0:00.73 0:00.89 0:00.81 0:00.78 best: 0:00.73 unrl
block size 6 0:01.66 0:02.25 0:02.23 0:02.31 0:02.01 0:02.31 0:01.57 0:01.01 0:00.96 0:00.99 0:01.01 0:01.02 0:01.00 0:01.01 best: 0:00.96 loopnoalign
block size 4 0:02.68 0:03.24 0:03.47 0:03.21 0:02.65 0:01.38 0:01.36 0:01.41 0:01.34 0:01.38 0:01.35 0:01.47 0:01.26 0:01.32 best: 0:01.34 loopnoalign
block size 1 0:05.41 0:17.59 0:17.41 0:01.52 0:01.51 0:01.46 0:01.50 0:01.59 0:01.49 0:01.56 0:01.52 0:02.43 0:02.39 0:02.42 best: 0:01.46 rep8
memset
libcall rep1 noalg rep4 noalg rep8 noalg loop noalg unrl noalg byte profiled dynamic
block size 8192000 0:00.05 0:00.05 0:00.06 0:00.05 0:00.05 0:00.05 0:00.05 0:00.11 0:00.09 0:00.09 0:00.11 0:00.47 0:00.05 0:00.05 best: 0:00.05 libcall
block size 819200 0:00.05 0:00.05 0:00.05 0:00.05 0:00.05 0:00.05 0:00.05 0:00.10 0:00.08 0:00.07 0:00.08 0:00.45 0:00.06 0:00.05 best: 0:00.05 libcall
block size 81920 0:00.03 0:00.03 0:00.03 0:00.04 0:00.04 0:00.03 0:00.03 0:00.10 0:00.07 0:00.06 0:00.07 0:00.47 0:00.04 0:00.03 best: 0:00.03 libcall
block size 20480 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.08 0:00.11 0:00.07 0:00.08 0:00.51 0:00.03 0:00.03 best: 0:00.03 libcall
block size 8192 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.09 0:00.06 0:00.06 0:00.06 0:00.45 0:00.03 0:00.03 best: 0:00.03 libcall
block size 4096 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.03 0:00.04 0:00.10 0:00.06 0:00.06 0:00.06 0:00.41 0:00.03 0:00.03 best: 0:00.03 libcall
block size 2048 0:00.04 0:00.03 0:00.04 0:00.04 0:00.03 0:00.04 0:00.03 0:00.10 0:00.06 0:00.06 0:00.06 0:00.41 0:00.04 0:00.04 best: 0:00.03 rep1
block size 1024 0:00.05 0:00.04 0:00.05 0:00.05 0:00.04 0:00.05 0:00.04 0:00.10 0:00.07 0:00.07 0:00.07 0:00.41 0:00.05 0:00.05 best: 0:00.04 rep1
block size 512 0:00.07 0:00.06 0:00.06 0:00.07 0:00.06 0:00.07 0:00.06 0:00.11 0:00.07 0:00.08 0:00.07 0:00.42 0:00.07 0:00.07 best: 0:00.06 rep1
block size 256 0:00.11 0:00.08 0:00.08 0:00.10 0:00.08 0:00.10 0:00.08 0:00.12 0:00.09 0:00.09 0:00.09 0:00.44 0:00.10 0:00.11 best: 0:00.08 rep1
block size 128 0:00.15 0:00.13 0:00.13 0:00.15 0:00.13 0:00.15 0:00.12 0:00.15 0:00.12 0:00.13 0:00.13 0:00.50 0:00.14 0:00.14 best: 0:00.12 loopnoalign
block size 64 0:00.28 0:00.21 0:00.22 0:00.25 0:00.23 0:00.23 0:00.22 0:00.20 0:00.20 0:00.20 0:00.21 0:00.50 0:00.20 0:00.20 best: 0:00.20 loop
block size 48 0:00.31 0:00.27 0:00.27 0:00.30 0:00.30 0:00.29 0:00.28 0:00.24 0:00.23 0:00.24 0:00.25 0:00.59 0:00.24 0:00.24 best: 0:00.23 loopnoalign
block size 32 0:00.47 0:00.36 0:00.36 0:00.40 0:00.37 0:00.40 0:00.37 0:00.30 0:00.31 0:00.31 0:00.30 0:00.58 0:00.31 0:00.31 best: 0:00.30 loop
block size 24 0:00.62 0:00.55 0:00.55 0:00.59 0:00.56 0:00.55 0:00.52 0:00.35 0:00.35 0:00.35 0:00.36 0:00.66 0:00.35 0:00.34 best: 0:00.35 loop
block size 16 0:00.92 0:00.78 0:00.72 0:00.76 0:00.70 0:00.71 0:00.63 0:00.40 0:00.40 0:00.33 0:00.34 0:00.67 0:00.39 0:00.40 best: 0:00.33 unrl
block size 14 0:00.98 0:00.94 0:00.95 0:00.95 0:00.91 0:00.90 0:00.85 0:00.43 0:00.43 0:00.39 0:00.39 0:00.68 0:00.43 0:00.43 best: 0:00.39 unrl
block size 12 0:01.16 0:01.11 0:01.10 0:01.09 0:01.03 0:01.01 0:00.87 0:00.43 0:00.46 0:00.44 0:00.43 0:00.72 0:00.46 0:00.45 best: 0:00.43 loop
block size 10 0:01.39 0:01.33 0:01.33 0:01.29 0:01.21 0:01.17 0:00.99 0:00.49 0:00.51 0:00.50 0:00.55 0:00.84 0:00.58 0:00.57 best: 0:00.49 loop
block size 8 0:01.87 0:01.51 0:01.47 0:01.43 0:01.26 0:01.27 0:00.96 0:00.57 0:00.56 0:00.52 0:00.51 0:00.83 0:00.56 0:00.55 best: 0:00.51 unrlnoalign
block size 6 0:02.17 0:02.26 0:02.29 0:01.99 0:01.80 0:01.56 0:01.27 0:00.70 0:00.70 0:00.74 0:00.72 0:00.92 0:00.74 0:00.71 best: 0:00.70 loop
block size 4 0:03.16 0:03.16 0:03.11 0:02.47 0:02.04 0:01.02 0:00.95 0:00.92 0:00.93 0:00.91 0:00.93 0:01.09 0:00.93 0:01.08 best: 0:00.91 unrl
block size 1 0:04.64 0:17.11 0:18.85 0:01.78 0:01.79 0:01.77 0:01.76 0:01.74 0:01.79 0:01.70 0:01.68 0:02.05 0:01.27 0:02.27 best: 0:01.68 unrlnoalign
For non-x86 I modified the above script (at attached) to run the memX and compare to it with {-fno-builtin-X}
root@ozrom2:~# sh test_stringop 64 640000000 gcc -mcpu=power9 | tee out.txt
root@ozrom2:~# gcc --version
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
memcpy mode:64 size:640000000
libcall nobuiltin byte profiled
block size 8192000 0:00.04 0:00.04 0:00.04 best: 0:00.04 libcall
block size 819200 0:00.04 0:00.03 0:00.04 best: 0:00.03 nobuiltin
block size 81920 0:00.03 0:00.03 0:00.03 best: 0:00.03 libcall
block size 20480 0:00.03 0:00.03 0:00.04 best: 0:00.03 libcall
block size 8192 0:00.03 0:00.04 0:00.04 best: 0:00.03 libcall
block size 4096 0:00.04 0:00.04 0:00.04 best: 0:00.04 libcall
block size 2048 0:00.05 0:00.05 0:00.05 best: 0:00.05 libcall
block size 1024 0:00.07 0:00.07 0:00.06 best: 0:00.07 libcall
block size 512 0:00.09 0:00.09 0:00.10 best: 0:00.09 libcall
block size 256 0:00.12 0:00.12 0:00.11 best: 0:00.12 libcall
block size 128 0:00.19 0:00.20 0:00.19 best: 0:00.19 libcall
block size 64 0:00.32 0:00.32 0:00.31 best: 0:00.32 libcall
block size 48 0:00.45 0:00.44 0:00.46 best: 0:00.44 nobuiltin
block size 32 0:00.58 0:00.59 0:00.58 best: 0:00.58 libcall
block size 24 0:00.82 0:00.83 0:00.81 best: 0:00.82 libcall
block size 16 0:01.09 0:01.10 0:01.05 best: 0:01.09 libcall
block size 14 0:01.36 0:01.36 0:01.32 best: 0:01.36 libcall
block size 12 0:01.58 0:01.57 0:01.56 best: 0:01.57 nobuiltin
block size 10 0:01.88 0:01.88 0:01.84 best: 0:01.88 libcall
block size 8 0:02.14 0:02.14 0:02.03 best: 0:02.14 libcall
block size 6 0:03.24 0:03.24 0:03.01 best: 0:03.24 libcall
block size 4 0:04.27 0:04.26 0:03.90 best: 0:04.26 nobuiltin
block size 1 0:18.42 0:18.45 0:15.29 best: 0:18.42 libcall
memset
libcall nobuiltin byte profiled
block size 8192000 0:00.04 0:00.04 0:00.04 best: 0:00.04 libcall
block size 819200 0:00.04 0:00.03 0:00.04 best: 0:00.03 nobuiltin
block size 81920 0:00.04 0:00.03 0:00.03 best: 0:00.03 nobuiltin
block size 20480 0:00.04 0:00.04 0:00.03 best: 0:00.04 libcall
block size 8192 0:00.03 0:00.04 0:00.03 best: 0:00.03 libcall
block size 4096 0:00.04 0:00.04 0:00.04 best: 0:00.04 libcall
block size 2048 0:00.05 0:00.05 0:00.05 best: 0:00.05 libcall
block size 1024 0:00.07 0:00.07 0:00.06 best: 0:00.07 libcall
block size 512 0:00.09 0:00.09 0:00.10 best: 0:00.09 libcall
block size 256 0:00.13 0:00.12 0:00.11 best: 0:00.12 nobuiltin
block size 128 0:00.19 0:00.18 0:00.19 best: 0:00.18 nobuiltin
block size 64 0:00.31 0:00.32 0:00.31 best: 0:00.31 libcall
block size 48 0:00.44 0:00.45 0:00.45 best: 0:00.44 libcall
block size 32 0:00.58 0:00.58 0:00.58 best: 0:00.58 libcall
block size 24 0:00.82 0:00.82 0:00.81 best: 0:00.82 libcall
block size 16 0:01.09 0:01.09 0:01.05 best: 0:01.09 libcall
block size 14 0:01.36 0:01.36 0:01.32 best: 0:01.36 libcall
block size 12 0:01.57 0:01.58 0:01.55 best: 0:01.57 libcall
block size 10 0:01.90 0:01.90 0:01.83 best: 0:01.90 libcall
block size 8 0:02.14 0:02.15 0:02.05 best: 0:02.14 libcall
block size 6 0:03.20 0:03.20 0:03.03 best: 0:03.20 libcall
block size 4 0:04.26 0:04.27 0:03.91 best: 0:04.26 libcall
block size 1 0:18.43 0:18.44 0:15.30 best: 0:18.43 libcall
So its pretty much better or identical to use memset/cmp in all cases the ones showing up as nobuiltin are pretty much in the noise of measurement.
then we might add
further MY_ASSUME_ALIGNED() assertions here and there, to allow gcc
and clang to generate better code for POWER and ARM.
If the compiler is smart enough, it might suffice to implement an
accessor for buf_block_t or buf_block_t::frame that would
MY_ASSUME_ALIGNED(frame, 4096). Then the compiler might correctly
infer the alignment of (block->frame + some_compile_time_constant) and
enable the optimization. I would be unwilling to pepper such hints all
over the code.
Marko