Ответ 1
Кажется, трудно воспроизвести, поэтому, возможно, специфичный для компилятора /libc.
Мое лучшее предположение здесь:
Когда вы вызываете malloc
, вы получаете карту памяти в пространство процесса, что не означает, что ОС уже взяла необходимые страницы из своего пула свободной памяти, но что она просто добавила записи в некоторые таблицы.
Теперь, когда вы пытаетесь получить доступ к памяти там, ваш CPU/MMU вызовет ошибку - и ОС может поймать это и проверить, принадлежит ли этот адрес категории "уже в памяти", но еще не на самом деле выделенных для процесса ". Если это так, необходимая свободная память будет найдена и отображена в пространство памяти вашего процесса.
Теперь современные ОС часто имеют встроенную опцию "обнулить" страницы до (повторного) использования. Если вы это сделаете, операция memset(,0,)
становится ненужной. В случае систем POSIX, если вы используете calloc
вместо malloc
, память обнуляется.
Другими словами, ваш компилятор мог заметить это и полностью опустил memset(,0,)
, когда ваша ОС поддерживает это. Это означает, что в момент, когда вы пишете страницы в process()
, это первый момент, когда они получают доступ, - и это вызывает механизм "отображения на лету" вашей ОС.
Конечно, memset(,42,)
не может быть оптимизирован, поэтому в этом случае страницы будут предварительно выделены и вы не увидите время, затраченное на функцию process()
.
Вы должны использовать /usr/bin/time
для фактического сравнения всего времени выполнения с временем, проведенным в process
- мое подозрение подразумевает, что время, сохраненное в process
, фактически потрачено на main
, возможно, в контексте ядра.
ОБНОВЛЕНИЕ. Протестировано с отличным Godbolt Compiler Explorer: Да, с -O2
и -O3
, современный gcc просто пропускает нуль-memsetting (или, скорее, просто вставляет его в calloc
, который равен malloc
с обнулением):
#include <cstdlib>
#include <cstring>
int main(int argc, char ** argv) {
char *p = (char*)malloc(10000);
if(argc>2) {
memset(p,42,10000);
} else {
memset(p,0,10000);
}
return (int)p[190]; // had to add this for the compiler to **not** completely remove all the function body, since it has no effect at all.
}
Становится для x86_64 на gcc6.3
main:
// store frame state
push rbx
mov esi, 1
// put argc in ebx
mov ebx, edi
// Setting up call to calloc (== malloc with internal zeroing)
mov edi, 10000
call calloc
// ebx (==argc) compared to 2 ?
cmp ebx, 2
mov rcx, rax
// jump on less/equal to .L2
jle .L2
// if(argc > 2):
// set up call to memset
mov edx, 10000
mov esi, 42
mov rdi, rax
call memset
mov rcx, rax
.L2: //else case
//notice the distinct lack of memset here!
// move the value at position rcx (==p)+190 into the "return" register
movsx eax, BYTE PTR [rcx+190]
//restore frame
pop rbx
//return
ret
Кстати, если вы удалите return p[190]
,
}
return 0;
}
тогда нет причин для компилятора вообще сохранить тело функции - его возвращаемое значение легко определить во время компиляции и не имеет побочного эффекта. Затем вся программа компилируется в
main:
xor eax, eax
ret
Обратите внимание: A xor A
0
для каждого A
.