为什么malloc+memset比calloc慢?

2023-11-26

据了解calloc不同于malloc因为它初始化分配的内存。和calloc,内存设置为零。和malloc,内存没有被清除。

所以在日常工作中,我认为calloc as malloc+memset。 顺便说一句,为了好玩,我编写了以下代码作为基准测试。

结果令人困惑。

Code 1:

#include<stdio.h>
#include<stdlib.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
        int i=0;
        char *buf[10];
        while(i<10)
        {
                buf[i] = (char*)calloc(1,BLOCK_SIZE);
                i++;
        }
}

代码 1 的输出:

time ./a.out  
**real 0m0.287s**  
user 0m0.095s  
sys 0m0.192s  

代码2:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define BLOCK_SIZE 1024*1024*256
int main()
{
        int i=0;
        char *buf[10];
        while(i<10)
        {
                buf[i] = (char*)malloc(BLOCK_SIZE);
                memset(buf[i],'\0',BLOCK_SIZE);
                i++;
        }
}

代码 2 的输出:

time ./a.out   
**real 0m2.693s**  
user 0m0.973s  
sys 0m1.721s  

更换memset with bzero(buf[i],BLOCK_SIZE)代码 2 产生相同的结果。

我的问题是: Why is malloc+memsetcalloc?怎么能calloc去做?


简短版本:始终使用calloc()代替malloc()+memset()。在大多数情况下,它们是相同的。在某些情况下,calloc()会做更少的工作,因为它可以跳过memset()完全。在其他情况下,calloc()甚至可以作弊而不分配任何内存!然而,malloc()+memset()总是会做足量的工作。

要理解这一点,需要简单浏览一下记忆系统。

快速浏览记忆

这里有四个主要部分:程序、标准库、内核和页表。你已经知道你的程序了,所以...

内存分配器如malloc() and calloc()主要用于进行小型分配(从 1 字节到 100 KB 的任何内容)并将它们分组到更大的内存池中。例如,如果分配 16 个字节,malloc()首先会尝试从其中一个池中获取 16 字节,然后当池耗尽时向内核请求更多内存。但是,由于您询问的程序会立即分配大量内存,malloc() and calloc()只会直接从内核请求该内存。此行为的阈值取决于您的系统,但我见过 1 MiB 用作阈值。

内核负责为每个进程分配实际的 RAM,并确保进程不会干扰其他进程的内存。这就是所谓的内存保护,自 20 世纪 90 年代以来,这种情况就很常见,这就是为什么一个程序可能崩溃而不会导致整个系统瘫痪的原因。因此,当程序需要更多内存时,它不能仅仅获取内存,而是使用系统调用向内核请求内存,例如mmap() or sbrk()。内核会通过修改页表的方式将RAM分配给每个进程。

页表将内存地址映射到实际的物理 RAM。您的进程的地址(32 位系统上的 0x00000000 到 0xFFFFFFFF)不是真实内存,而是以下地址虚拟内存。处理器将这些地址划分为 4 KiB 页,每个页可以通过修改页表分配给不同的物理 RAM。只有内核才被允许修改页表。

怎么不起作用

以下是分配 256 MiB 的方法not work:

  1. 您的流程调用calloc()并要求 256 MiB。

  2. 标准库调用mmap()并要求 256 MiB。

  3. 内核找到 256 MiB 未使用的 RAM,并通过修改页表将其提供给您的进程。

  4. 标准库将 RAM 归零memset()并从返回calloc().

  5. 您的进程最终退出,内核回收 RAM,以便其他进程可以使用它。

它实际上是如何运作的

上面的过程是可行的,但它只是不会以这种方式发生。存在三个主要差异。

  • 当您的进程从内核获取新内存时,该内存可能之前已被其他进程使用过。这是一个安全风险。如果该内存有密码、加密密钥或秘密萨尔萨食谱怎么办?为了防止敏感数据泄露,内核总是在将内存提供给进程之前对其进行清理。我们不妨通过将内存清零来清理内存,如果新内存被清零,我们不妨将其作为保证,所以mmap()保证它返回的新内存始终为零。

  • 有很多程序分配内存但并不立即使用内存。有时内存已分配但从未使用。内核知道这一点并且是懒惰的。当您分配新内存时,内核根本不会触及页表,也不会给您的进程提供任何 RAM。相反,它会在您的进程中找到一些地址空间,记下应该存放在那里的内容,并承诺如果您的程序实际使用它,它将把 RAM 放在那里。当您的程序尝试从这些地址读取或写入时,处理器会触发页面错误内核介入将 RAM 分配给这些地址并恢复您的程序。如果你从不使用内存,页面错误就永远不会发生,你的程序也永远不会真正获得RAM。

  • 有些进程分配内存,然后从中读取而不修改它。这意味着跨不同进程的内存中的许多页面可能会被从返回的原始零填充mmap()。由于这些页面都是相同的,因此内核使所有这些虚拟地址都指向一个填充了零的共享 4 KiB 内存页面。如果您尝试写入该内存,处理器会触发另一个页面错误,并且内核会介入,为您提供一个不与任何其他程序共享的新的零页面。

最终的过程看起来更像是这样的:

  1. 您的流程调用calloc()并要求 256 MiB。

  2. 标准库调用mmap()并要求 256 MiB。

  3. 内核发现 256 MiB 未使用地址空间,记录该地址空间现在的用途,然后返回。

  4. 标准库知道结果mmap()总是用零填充(或will be一旦它实际获得了一些 RAM),因此它不会触及内存,因此不会出现页面错误,并且 RAM 永远不会提供给您的进程。

  5. 您的进程最终会退出,并且内核不需要回收 RAM,因为它从来没有被分配过。

如果你使用memset()将页面归零,memset()将触发页面错误,导致 RAM 被分配,然后将其清零,即使它已经被零填充了。这是大量的额外工作,并解释了原因calloc()malloc() and memset()。如果你最终还是使用了内存,calloc()仍然比malloc() and memset()但差异并不那么荒谬。


这并不总是有效

并非所有系统都有分页虚拟内存,因此并非所有系统都可以使用这些优化。这适用于像 80286 这样非常旧的处理器以及对于复杂的内存管理单元来说太小的嵌入式处理器。

这也并不总是适用于较小的分配。通过较小的分配,calloc()从共享池获取内存而不是直接进入内核。一般来说,共享池中可能会存储来自旧内存的垃圾数据,这些垃圾数据是使用和释放的free(), so calloc()可以使用该内存并调用memset()将其清除。常见的实现将跟踪共享池的哪些部分是原始的并且仍然填充零,但并非所有实现都这样做。

纠正一些错误答案

根据操作系统的不同,内核可能会也可能不会在其空闲时间内将内存归零,以防您稍后需要获取一些归零的内存。 Linux 不会提前清零内存,并且Dragonfly BSD 最近也从其内核中删除了此功能。然而,其他一些内核提前将内存清零。无论如何,在空闲期间清零页面不足以解释巨大的性能差异。

The calloc()函数没有使用某些特殊的内存对齐版本memset(),无论如何,这不会让它变得更快。最多memset()现代处理器的实现看起来有点像这样:

function memset(dest, c, len)
    // one byte at a time, until the dest is aligned...
    while (len > 0 && ((unsigned int)dest & 15))
        *dest++ = c
        len -= 1
    // now write big chunks at a time (processor-specific)...
    // block size might not be 16, it's just pseudocode
    while (len >= 16)
        // some optimized vector code goes here
        // glibc uses SSE2 when available
        dest += 16
        len -= 16
    // the end is not aligned, so one byte at a time
    while (len > 0)
        *dest++ = c
        len -= 1

所以你可以看到,memset()速度非常快,对于大内存块来说,你实际上不会得到任何更好的东西。

事实是memset()将已经清零的内存清零确实意味着内存被清零两次,但这只能解释 2 倍的性能差异。这里的性能差异要大得多(我在我的系统上测量了三个以上的数量级malloc()+memset() and calloc()).

派对把戏

不要循环 10 次,而是编写一个分配内存直到malloc() or calloc()返回 NULL。

如果添加会发生什么memset()?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么malloc+memset比calloc慢? 的相关文章

  • 结构化绑定中缺少类型信息

    我刚刚了解了 C 中的结构化绑定 但有一件事我不喜欢 auto x y some func is that auto正在隐藏类型x and y 我得抬头看看some func的声明来了解类型x and y 或者 我可以写 T1 x T2 y
  • STL 迭代器:前缀增量更快? [复制]

    这个问题在这里已经有答案了 可能的重复 C 中的预增量比后增量快 正确吗 如果是 为什么呢 https stackoverflow com questions 2020184 preincrement faster than postinc
  • 在 xaml 中编写嵌套类型时出现设计时错误

    我创建了一个用户控件 它接受枚举类型并将该枚举的值分配给该用户控件中的 ComboBox 控件 很简单 我在数据模板中使用此用户控件 当出现嵌套类型时 问题就来了 我使用这个符号来指定 EnumType x Type myNamespace
  • std::list 线程push_back、front、pop_front

    std list 线程安全吗 我假设不是这样 所以我添加了自己的同步机制 我认为我有正确的术语 但我仍然遇到问题 每个函数都由单独的线程调用 Thread1 不能等待 它必须尽可能快 std list
  • 传递给函数时多维数组的指针类型是什么? [复制]

    这个问题在这里已经有答案了 我在大学课堂上学习了 C 语言和指针 除了多维数组和指针之间的相似性之外 我认为我已经很好地掌握了这个概念 我认为由于所有数组 甚至多维 都存储在连续内存中 因此您可以安全地将其转换为int 假设给定的数组是in
  • 人脸 API DetectAsync 错误

    我想创建一个简单的程序来使用 Microsoft Azure Face API 和 Visual Studio 2015 检测人脸 遵循 https social technet microsoft com wiki contents ar
  • ASP.NET Core 3.1登录后如何获取用户信息

    我试图在登录 ASP NET Core 3 1 后获取用户信息 如姓名 电子邮件 id 等信息 这是我在登录操作中的代码 var claims new List
  • WcfSvcHost 的跨域异常

    对于另一个跨域问题 我深表歉意 我一整天都在与这个问题作斗争 现在已经到了沸腾的地步 我有一个 Silverlight 应用程序项目 SLApp1 一个用于托管 Silverlight SLApp1 Web 的 Web 项目和 WCF 项目
  • C 编程:带有数组的函数

    我正在尝试编写一个函数 该函数查找行为 4 列为 4 的二维数组中的最大值 其中二维数组填充有用户输入 我知道我的主要错误是函数中的数组 但我不确定它是什么 如果有人能够找到我出错的地方而不是编写新代码 我将不胜感激 除非我刚去南方 我的尝
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • 如何实例化 ODataQueryOptions

    我有一个工作 简化 ODataController用下面的方法 public class MyTypeController ODataController HttpGet EnableQuery ODataRoute myTypes pub
  • 为什么 isnormal() 说一个值是正常的,而实际上不是?

    include
  • 如何在 Linq to SQL 中使用distinct 和 group by

    我正在尝试将以下 sql 转换为 Linq 2 SQL select groupId count distinct userId from processroundissueinstance group by groupId 这是我的代码
  • 编译时展开 for 循环内的模板参数?

    维基百科 here http en wikipedia org wiki Template metaprogramming Compile time code optimization 给出了 for 循环的编译时展开 我想知道我们是否可以
  • C# 中的 IPC 机制 - 用法和最佳实践

    不久前我在 Win32 代码中使用了 IPC 临界区 事件和信号量 NET环境下场景如何 是否有任何教程解释所有可用选项以及何时使用以及为什么 微软最近在IPC方面的东西是Windows 通信基础 http en wikipedia org
  • 使用特定参数从 SQL 数据库填充组合框

    我在使用参数从 sql server 获取特定值时遇到问题 任何人都可以解释一下为什么它在 winfom 上工作但在 wpf 上不起作用以及我如何修复它 我的代码 private void UpdateItems COMBOBOX1 Ite
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • 在OpenGL中,我可以在坐标(5, 5)处精确地绘制一个像素吗?

    我所说的 5 5 正是指第五行第五列 我发现使用屏幕坐标来绘制东西非常困难 OpenGL 中的所有坐标都是相对的 通常范围从 1 0 到 1 0 为什么阻止程序员使用屏幕坐标 窗口坐标如此严重 最简单的方法可能是通过以下方式设置投影以匹配渲
  • MySQL Connector C/C API - 使用特殊字符进行查询

    我是一个 C 程序 我有一个接受域名参数的函数 void db domains query char name 使用 mysql query 我测试数据库中是否存在域名 如果不是这种情况 我插入新域名 char query 400 spri
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么

随机推荐

  • 将数组保存为 xml

    array name gt text surname gt text country gt text date gt text 1 如何将此数组保存为 xml 文件 2 如何读取这个文件作为数组 save doc new DOMDocume
  • 我可以在 JavaScript 中将数组附加到“formdata”吗?

    我正在使用 FormData 上传文件 我还想发送一系列其他数据 当我只发送图像时 效果很好 当我将一些文本附加到表单数据时 它工作正常 当我尝试附加下面的 标签 数组时 其他一切都正常 但没有发送数组 FormData 和附加数组有任何已
  • 在 Windows 启动时启动 Window

    我希望我的应用程序 WPFWindow 在 Windows 启动时启动 我尝试了不同的解决方案 但似乎没有一个有效 我必须在代码中写什么才能做到这一点 当您说必须向注册表添加一个密钥时 您是正确的 添加一个键 HKEY CURRENT US
  • 如何在 Erlang 中处理 SIGINT?

    感谢 Google 和大量教程 我知道如何用 Java Python Ruby Perl 和 Lisp 创建自定义信号处理程序 我无法在线找到如何在 Erlang 中为 SIGINT SIGTERM HUP 等创建处理程序 你不能 操作系统
  • 如何修复对 inflate/deflate 函数的未定义引用?

    我正在尝试编译示例中提供的现有代码zlib 但它本身就给了我错误 nikhil nikhil Vostro 3500 zlib 1 2 8 examples gcc o zpipe g zpipe c tmp ccVZzqsb o In f
  • shell 脚本的第一行空白:解释 UID 变量的行为

    我有两个非常简单的脚本 区别仅在于第一行存在空白 cat test bash bin bash echo UID cat test blank bash bin bash echo UID 现在我跑步 无论有没有nice test bash
  • 如何让 PHP 会话在 30 分钟后过期?

    我需要让一个会话保持 30 分钟的活动状态 然后销毁它 Answer recommended by PHP Collective 您应该实现自己的会话超时 其他人提到的两个选项 会话 gc maxlifetime and session c
  • 如何解决 http 和站点行为:android 应用程序中的导航错误?

    最近我将 android 9 更新到 android 10 但不幸的是 该应用程序有时会崩溃并给出此错误 com fgapps maker E chromium ERROR cookie manager cc 137 Strict Secu
  • 智能支付按钮为 IPN 传递自定义变量

    我的网站上有两个用于每月订阅的智能按钮 运行良好 我成功收到 IPN 响应 其中付款信息已准备好添加到数据库中 但我需要通过 IPN 响应获取应用程序的 UserID 如何使用智能支付按钮传递自定义变量 以便将其传递到 IPN 如果可能的话
  • Maven 本地存储库与 leiningen 的依赖关系

    我正在开始lein newclojure 中的项目并想要使用goose文章提取库 不幸的是 我在任何公开可用的 Maven 存储库中都找不到该库的 jar 因此我开始将其添加到本地 Maven 存储库中 在项目目录中 我复制了 goose
  • 如何在 Python 中使用 BeautifulSoup 找到文本字符串后的表格?

    我正在尝试从几个网页中提取数据 这些网页的显示方式并不统一 我需要编写代码来搜索文本字符串 然后转到紧随该特定文本字符串的表 然后我想提取该表的内容 这是我到目前为止所得到的 from BeautifulSoup import Beauti
  • 如何确定 Office 加载项是否在 Excel 或 Excel Online 下运行?

    我正在编写一个 Office 加载项 以前称为 Apps for Office 我在用着office js在某些代码点中 我想检查应用程序是在 Excel 桌面软件 中运行还是在 Web 上运行 Excel Online 就像是 if Of
  • 如何为 Amazon S3 存储桶配置 SSL

    我使用 Amazon S3 存储桶通过 NET 应用程序上传和下载数据 现在我的问题是 我想使用 SSL 访问我的 S3 存储桶 是否可以为 Amazon s3 存储桶实施 SSL 您可以通过 SSL 访问您的文件 如下所示 https s
  • Firebase Cloud Messaging 开发和发布简介

    我最近从 Google Cloud Messaging 切换到 Firebase Cloud Messaging 对于 GCM 我必须选择沙箱选项 正如这里所描述的 https developers google com cloud mes
  • 使用 Google App Engine 实现“开头为”和“结尾为”查询

    我想知道是否有人可以提供一些指导 指导我如何使用 Python 对数据存储模型实现开头或结尾查询 在伪代码中 它的工作原理类似于 查询属性 P 以 X 开头的所有实体 A or 查询属性 P 以 X 结尾的所有实体 B 谢谢 马特 您可以使
  • 如何从服务器优雅地关闭套接字

    在服务器端 我试图优雅地关闭连接的套接字 我知道套接字上事件的正确顺序应该是 使用 SocketShutdown Send 选项关闭套接字 循环 等待 直到套接字接收返回 0 字节 套接字关闭 我有几个问题 如果 Socket Receiv
  • 使用连接字符串的 DocumentDB .Net 客户端

    我检查了 MSDN on DocumentDB for Net here 并找到了 3 个有效的构造函数 然而 它们都没有使用连接字符串 这对我来说听起来很奇怪 是否真的没有办法用连接字符串而不是端点 authKey组合来实例化客户端 或者
  • Spring Oauth2 Client,自动刷新过期的access_token

    让我解释一下我的用例 我需要有一个 spring boot oauth2 客户端应用程序 不是资源服务器 因为我们已经有一个单独的资源服务器 另外我还有以下要求 对于每个向资源服务器发出的请求 我们需要发送 id token 通过自定义re
  • 使用python ElementTree的itertree函数并将修改后的树写入输出文件

    我需要解析一个非常大 40GB 的 XML 文件 从中删除某些元素 然后将结果写入新的 xml 文件 我一直在尝试使用 python 的 ElementTree 中的 iterparse 但我对如何修改树然后将生成的树写入新的 XML 文件
  • 为什么malloc+memset比calloc慢?

    据了解calloc不同于malloc因为它初始化分配的内存 和calloc 内存设置为零 和malloc 内存没有被清除 所以在日常工作中 我认为calloc as malloc memset 顺便说一句 为了好玩 我编写了以下代码作为基准