OS X memset 和系统跟踪
OS X memset and system trace
这是一个简化的程序:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *worker(void *data) {
size_t size = 1000000;
void *area = malloc(size);
if (area != NULL) {
memset(area, 0, size);
sleep(1);
free(area);
}
return NULL;
}
int main() {
int number_of_threads = 4;
pthread_t threads[number_of_threads];
for (int i = 0; i < number_of_threads; i++) {
if (pthread_create(&(threads[i]), NULL, worker, NULL)) {
return 0;
}
}
for (int i = 0; i < number_of_threads; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
我使用命令 iprofiler -systemtrace OSXMalloc
:
得到以下系统跟踪
为什么 memset
会产生所有这些填零事件?它们是什么意思,为什么这么多?我知道我尝试用零填充 1 MB,但为什么它不在每个线程的一次调用中执行此操作?
将有 4 个线程,每个线程都将调用 malloc 和 memset,因此 4 个实例已全部考虑在内。
但是,代码中存在错误。
将 pthread_t
数组预先设置为全零。
如果对 pthread_create
的任何调用失败,请将关联的 pthread_t
条目重新设置为 0
如果调用 pthread_create 失败则不退出程序
在调用 pthread_join
的循环中,
如果关联的 pthread_t
条目是 0
然后,不要为该条目调用 pthread_join
。
否则,当可能有活动的 pthreads 时程序正在退出。
这不是您在此处看到的memset
,而是将页面映射到内存以备后用的行为。 OS 用零填充页面以防止数据从一个应用程序泄漏到另一个应用程序。
您看到的每个零填充事件都针对每个内存页面生成一次。单个内存页只有 4K 长——4096 字节——所以你的 100 万字节的连续块跨越 245 个,可能是 246 个单页。
并非所有内存页面都需要此零填充事件。其中一些可能在空闲 CPU 期间被清零(并且 OS 保留 "ready to go" 内存页面的列表),而其他页面可能已分配但从未被使用。但是,在这种情况下,memset
本身会尝试访问每个字节,因此 OS 别无选择,只能在 memset 到达之前清除页面。
出于安全和隐私目的,内核需要保证新分配给进程的页面用零填充。否则,您可能会从其他进程获取数据,例如密码或财务信息。
页面在第一次访问时归零,有点类似于写时复制。由于 memset()
将遍历页面并将它们清零,因此内核将一次对页面进行零填充。 memset()
然后做一堆多余的工作在已经清零的页面上写零。
使用 calloc()
而不是 malloc()
后跟 memset(..., 0, ...)
会更好。由于 malloc 库知道内核将对新分配的页面进行零填充,因此它知道不需要执行显式 memset()
来满足 calloc()
的零填充约定。第一次访问时仍然会出现填零错误,但是当内存真正第一次使用时会出现。对于不需要的 memset()
.
,它们不会完成 "eagerly"
顺便说一句,并非所有通过 malloc()
完成的分配都会从内核获取新页面。有些会重用之前在您的进程中分配和释放的页面。但是,对于像您这样的大型分配,页面通常在 malloc()
期间分配并在 free()
期间释放。
这是一个简化的程序:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *worker(void *data) {
size_t size = 1000000;
void *area = malloc(size);
if (area != NULL) {
memset(area, 0, size);
sleep(1);
free(area);
}
return NULL;
}
int main() {
int number_of_threads = 4;
pthread_t threads[number_of_threads];
for (int i = 0; i < number_of_threads; i++) {
if (pthread_create(&(threads[i]), NULL, worker, NULL)) {
return 0;
}
}
for (int i = 0; i < number_of_threads; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
我使用命令 iprofiler -systemtrace OSXMalloc
:
为什么 memset
会产生所有这些填零事件?它们是什么意思,为什么这么多?我知道我尝试用零填充 1 MB,但为什么它不在每个线程的一次调用中执行此操作?
将有 4 个线程,每个线程都将调用 malloc 和 memset,因此 4 个实例已全部考虑在内。
但是,代码中存在错误。
将
pthread_t
数组预先设置为全零。如果对
pthread_create
的任何调用失败,请将关联的pthread_t
条目重新设置为0
如果调用 pthread_create 失败则不退出程序
在调用
pthread_join
的循环中, 如果关联的pthread_t
条目是0
然后,不要为该条目调用pthread_join
。
否则,当可能有活动的 pthreads 时程序正在退出。
这不是您在此处看到的memset
,而是将页面映射到内存以备后用的行为。 OS 用零填充页面以防止数据从一个应用程序泄漏到另一个应用程序。
您看到的每个零填充事件都针对每个内存页面生成一次。单个内存页只有 4K 长——4096 字节——所以你的 100 万字节的连续块跨越 245 个,可能是 246 个单页。
并非所有内存页面都需要此零填充事件。其中一些可能在空闲 CPU 期间被清零(并且 OS 保留 "ready to go" 内存页面的列表),而其他页面可能已分配但从未被使用。但是,在这种情况下,memset
本身会尝试访问每个字节,因此 OS 别无选择,只能在 memset 到达之前清除页面。
出于安全和隐私目的,内核需要保证新分配给进程的页面用零填充。否则,您可能会从其他进程获取数据,例如密码或财务信息。
页面在第一次访问时归零,有点类似于写时复制。由于 memset()
将遍历页面并将它们清零,因此内核将一次对页面进行零填充。 memset()
然后做一堆多余的工作在已经清零的页面上写零。
使用 calloc()
而不是 malloc()
后跟 memset(..., 0, ...)
会更好。由于 malloc 库知道内核将对新分配的页面进行零填充,因此它知道不需要执行显式 memset()
来满足 calloc()
的零填充约定。第一次访问时仍然会出现填零错误,但是当内存真正第一次使用时会出现。对于不需要的 memset()
.
顺便说一句,并非所有通过 malloc()
完成的分配都会从内核获取新页面。有些会重用之前在您的进程中分配和释放的页面。但是,对于像您这样的大型分配,页面通常在 malloc()
期间分配并在 free()
期间释放。