Я пишу программу (на C ++), в которой мне нужно выделить массивы, начальные адреса которых должны быть выровнены по размеру строки кэша. Когда я выделяю эти массивы, я также хочу, чтобы память была инициализирована нулем.
Прямо сейчас у меня это работает, используя функцию posix_memalign. Это хорошо работает для получения выровненных массивов памяти, но массивы неинициализированы. Есть ли лучшая функция, которую я могу использовать для обнуления массивов при их инициализации, или мне просто нужно согласиться написать отдельный цикл, чтобы сделать это для меня?
Просто позвони memset
на блоке. Убедитесь, что вы не приводите указатель на тип, который стоит установить дорого (например, char *
) перед звонком memset
, Поскольку ваш указатель будет выровнен, убедитесь, что информация не скрыта от компилятора.
Обновить: Чтобы прояснить мою точку зрения о том, чтобы не скрывать выравнивание, сравните:
char* mem_demo_1(char *j)
{ // *BAD* compiler cannot tell pointer alignment, must test
memset(j, 0, 64);
return j;
}
char* mem_demo_2(void)
{ // *GOOD* compiler can tell pointer alignment
char * j = malloc(64);
memset(j, 0, 64);
return j;
}
С GCC
, mem_demo_1
составляет до 60 строк сборки mem_demo_2
составляет до 20. Разница в производительности также огромна.
В GCC mem_demo_1 компилируется в 60 строк сборки, а mem_demo_2 — в 20. Разница в производительности также огромна.
Я решил проверить это утверждение в Linux 2.6.32 с помощью gcc 4.4.6.
Первый
mem_demo_1 компилирует до 60 строк сборки, в то время как mem_demo_2 компилирует
до 20
.
Это тест (в файле main.c):
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char* mem_demo_1(char *j)
{
// *BAD* compiler cannot tell pointer alignment, must test
memset(j, 0, 64);
return j;
}
char* mem_demo_2(void)
{
// *GOOD* compiler can tell pointer alignment
char * j = malloc(64);
memset(j, 0, 64);
return j;
}
int main()
{
char *p;
p = malloc(64);
p = mem_demo_1(p);
printf ("%p\n",p);
free (p);
p = mem_demo_2();
printf ("%p\n",p);
free (p);
return 0;
}
Когда я компилирую:
gcc -fno-inline -fno-builtin -m64 -g -O2 main.c -o main.no_inline_no_builtin
Я вижу, что в mem_demo_1 всего 8 строк:
(gdb) disassemble mem_demo_1
Dump of assembler code for function mem_demo_1:
0x00000000004005d0 <+0>: push %rbx
0x00000000004005d1 <+1>: mov $0x40,%edx
0x00000000004005d6 <+6>: mov %rdi,%rbx
0x00000000004005d9 <+9>: xor %esi,%esi
0x00000000004005db <+11>: callq 0x400470 <memset@plt>
0x00000000004005e0 <+16>: mov %rbx,%rax
0x00000000004005e3 <+19>: pop %rbx
0x00000000004005e4 <+20>: retq
End of assembler dump.
Я вижу, что в mem_demo_2 всего 11 строк:
(gdb) disassemble mem_demo_2
Dump of assembler code for function mem_demo_2:
0x00000000004005a0 <+0>: push %rbx
0x00000000004005a1 <+1>: mov $0x40,%edi
0x00000000004005a6 <+6>: callq 0x400480 <malloc@plt>
0x00000000004005ab <+11>: mov $0x40,%edx
0x00000000004005b0 <+16>: mov %rax,%rbx
0x00000000004005b3 <+19>: xor %esi,%esi
0x00000000004005b5 <+21>: mov %rax,%rdi
0x00000000004005b8 <+24>: callq 0x400470 <memset@plt>
0x00000000004005bd <+29>: mov %rbx,%rax
0x00000000004005c0 <+32>: pop %rbx
0x00000000004005c1 <+33>: retq
End of assembler dump.
Таким образом, «mem_demo_1 компилируется до 60 строк сборки, а mem_demo_2 компилируется до 20» не может быть подтверждено.
Когда я компилирую:
gcc -m64 -g -O2 main.c -o main.default
gcc использует свою собственную реализацию memset, и обе функции mem_demo_1 и mem_demo_2 больше:
mem_demo_1: 43 instructions
mem_demo_2: 48 instructions
Однако «mem_demo_1 компилируется до 60 строк сборки, а mem_demo_2 компилируется до 20» также не может быть подтверждено.
второй
«Разница в производительности также огромна»
Я расширил main.c, чтобы сделать много циклов с memset. Я также не вижу, что memset в mem_demo_1 медленнее, чем в mem_demo_2.
Это из отчетов Linux perf:
mem_demo_2 тратит 8,37% в memset:
8,37% main.perf.no_bu libc-2.12.so [.] __Memset_sse2
в то время как mem_demo_1 тратит 7,61% в memset:
7,61% main.perf.no_bu libc-2.12.so [.] __Memset_sse2
А это сами измерения:
# time ./main.perf.no_builtin_no_inline 100000000 1 0
number loops 100000000
mem_demo_1
real 0m3.483s
user 0m3.481s
sys 0m0.002s
# time ./main.perf.no_builtin_no_inline 100000000 2 0
number loops 100000000
mem_demo_2
real 0m3.503s
user 0m3.501s
sys 0m0.001s
Кстати, это как gcc -fverbose-asm -c -S -O3
показывает мне ассемблер для mem_demo_2:
char* mem_demo_2(void)
{
char * j = malloc(64);
memset(j, 0, 64);
return j;
}
.file "main.mem_demo_2.c"# GNU C (GCC) version 4.4.6 20110731 (Red Hat 4.4.6-3) (x86_64-redhat-linux)
# compiled by GNU C version 4.4.6 20110731 (Red Hat 4.4.6-3), GMP version 4.3.1, MPFR version 2.4.1.
# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# options passed: main.mem_demo_2.c -m64 -mtune=generic -auxbase-strip
# main.mem_demo_2.default.asm -g -O3 -fverbose-asm
# options enabled: -falign-loops -fargument-alias
# -fasynchronous-unwind-tables -fauto-inc-dec -fbranch-count-reg
# -fcaller-saves -fcommon -fcprop-registers -fcrossjumping
# -fcse-follow-jumps -fdefer-pop -fdelete-null-pointer-checks
# -fdwarf2-cfi-asm -fearly-inlining -feliminate-unused-debug-types
# -fexpensive-optimizations -fforward-propagate -ffunction-cse -fgcse
# -fgcse-after-reload -fgcse-lm -fguess-branch-probability -fident
# -fif-conversion -fif-conversion2 -findirect-inlining -finline
# -finline-functions -finline-functions-called-once
# -finline-small-functions -fipa-cp -fipa-cp-clone -fipa-pure-const
# -fipa-reference -fira-share-save-slots -fira-share-spill-slots -fivopts
# -fkeep-static-consts -fleading-underscore -fmath-errno -fmerge-constants
# -fmerge-debug-strings -fmove-loop-invariants -fomit-frame-pointer
# -foptimize-register-move -foptimize-sibling-calls -fpeephole -fpeephole2
# -fpredictive-commoning -freg-struct-return -fregmove -freorder-blocks
# -freorder-functions -frerun-cse-after-loop -fsched-interblock
# -fsched-spec -fsched-stalled-insns-dep -fschedule-insns2 -fsigned-zeros
# -fsplit-ivs-in-unroller -fsplit-wide-types -fstrict-aliasing
# -fstrict-overflow -fthread-jumps -ftoplevel-reorder -ftrapping-math
# -ftree-builtin-call-dce -ftree-ccp -ftree-ch -ftree-coalesce-vars
# -ftree-copy-prop -ftree-copyrename -ftree-cselim -ftree-dce
# -ftree-dominator-opts -ftree-dse -ftree-fre -ftree-loop-im
# -ftree-loop-ivcanon -ftree-loop-optimize -ftree-parallelize-loops=
# -ftree-pre -ftree-reassoc -ftree-scev-cprop -ftree-sink -ftree-sra
# -ftree-switch-conversion -ftree-ter -ftree-vect-loop-version
# -ftree-vectorize -ftree-vrp -funit-at-a-time -funswitch-loops
# -funwind-tables -fvar-tracking -fvar-tracking-assignments
# -fvect-cost-model -fverbose-asm -fzero-initialized-in-bss
# -m128bit-long-double -m64 -m80387 -maccumulate-outgoing-args
# -malign-stringops -mfancy-math-387 -mfp-ret-in-387 -mfused-madd -mglibc
# -mieee-fp -mmmx -mno-sse4 -mpush-args -mred-zone -msse -msse2
# -mtls-direct-seg-refs
mem_demo_2:
.LFB30:
.file 1 "main.mem_demo_2.c".loc 1 6 0
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
.loc 1 7 0
movl $64, %edi
call malloc
.loc 1 8 0
testb $1, %al
.loc 1 7 0
movq %rax, %rsi
.LVL0:
.loc 1 8 0
movq %rax, %rdi
movl $64, %edx
jne .L10
testb $2, %dil
jne .L11
.L3:
testb $4, %dil
jne .L12
.L4:
movl %edx, %ecx
xorl %eax, %eax
.LVL1:
shrl $3, %ecx
testb $4, %dl
mov %ecx, %ecx
rep stosq
je .L5
movl $0, (%rdi)
addq $4, %rdi
.L5:
testb $2, %dl
je .L6
movw $0, (%rdi)
addq $2, %rdi
.L6:
andl $1, %edx
je .L7
movb $0, (%rdi)
.L7:
.loc 1 10 0
movq %rsi, %rax
addq $8, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L10:
.cfi_restore_state
.loc 1 8 0
leaq 1(%rax), %rdi
movb $0, (%rax)
movb $63, %dl
testb $2, %dil
je .L3
.p2align 4,,10
.p2align 3
.L11:
movw $0, (%rdi)
addq $2, %rdi
subl $2, %edx
testb $4, %dil
je .L4
.p2align 4,,10
.p2align 3
.L12:
movl $0, (%rdi)
subl $4, %edx
addq $4, %rdi
jmp .L4
.cfi_endproc