小. 快速. 可靠.
选择任意三个.
SQLite中的动态内存分配

Overview

SQLite使用动态内存分配来获取用于存储各种对象的内存(例如: 数据库连接预处理语句 ),并为数据库文件构建内存缓存并保存查询结果. 为了使SQLite的动态内存分配子系统可靠,可预测,健壮,安全和高效,已经付出了很多努力.

本文档概述了SQLite中的动态内存分配. 目标受众是软件工程师,他们正在调整其SQLite的使用以在苛刻环境中实现最佳性能. 使用SQLite不需要任何文档知识. SQLite的默认设置和配置将在大多数应用程序中正常运行. 但是,本文档中包含的信息对于调整SQLite以符合特殊要求或在异常情况下运行的工程师可能有用.

1. Features

SQLite核心及其内存分配子系统提供以下功能:

2. Testing

SQLite源代码树中的大多数代码纯粹用于测试和验证 . 可靠性对SQLite很重要. 测试基础结构的任务之一是确保SQLite不会滥用动态分配的内存,确保SQLite不会泄漏内存,并且SQLite能够正确响应动态内存分配失败.

测试基础结构通过使用专门检测的内存分配器来验证SQLite不会滥用动态分配的内存. 在编译时使用SQLITE_MEMDEBUG选项启用检测的内存分配器. 检测的内存分配器比默认内存分配器慢得多,因此在生产中不建议使用它. 但是,在测试过程中启用该功能后,检测的内存分配器将执行以下检查:

无论是否使用检测的内存分配器,SQLite都会跟踪当前检出的内存量. 有数百种用于测试SQLite的测试脚本. 在每个脚本的末尾,所有对象均被销毁,并进行了测试以确保已释放所有内存. 这就是检测内存泄漏的方式. 请注意,内存泄漏检测在测试构建和生产构建期间始终有效. 每当开发人员之一运行任何单独的测试脚本时,内存泄漏检测就处于活动状态. 因此,可以快速检测并修复在开发过程中确实发生的内存泄漏.

使用可模拟内存故障的专用内存分配器覆盖层测试了SQLite对内存不足(OOM)错误的响应. 覆盖层是插入到内存分配器和其余SQLite之间的一层. 叠加层将大多数内存分配请求直接传递给基础分配器,并将结果传递回请求者. 但是可以将覆盖设置为导致第N个内存分配失败. 要运行OOM测试,首先将覆盖设置为在第一次分配尝试时失败. 然后运行一些测试脚本,并验证是否正确捕获并处理了分配. 然后,将覆盖设置为第二次分配失败,然后重复测试. 故障点每次将继续推进一次分配,直到整个测试过程运行完毕而不会遇到内存分配错误. 整个测试序列运行两次. 在第一遍中,覆盖设置为仅第N个分配失败. 在第二遍,覆盖设置为第N次及所有后续分配失败.

请注意,即使正在使用OOM覆盖,内存泄漏检测逻辑也继续起作用. 这可以验证SQLite即使遇到内存分配错误也不会泄漏内存. 还要注意,OOM覆盖可以与任何基础内存分配器一起使用,包括检查内存分配滥用的已检测内存分配器. 通过这种方式,可以验证OOM错误不会引起其他类型的内存使用错误.

最后,我们观察到检测到的内存分配器和内存泄漏检测器都可以在整个SQLite测试套件中工作, TCL测试套件提供了超过99%的语句测试覆盖率,并且TH3测试工具提供了100%的分支测试覆盖率 ,而没有泄漏泄漏. 这有力的证据表明,动态内存分配在SQLite中的任何地方都可以正确使用.

2.1. Use of reallocarray()

reallocarray()接口是OpenBSD社区的一项最新创新(大约于2014年),其工作是通过避免内存分配大小计算中的32位整数算术溢出来防止下一个"令人讨厌的"错误 . reallocarray()函数同时具有单位大小和计数参数. 为了分配足够的内存以容纳每个X字节大小的N个元素的数组,一个调用" reallocarray(0,X,N)". 这优于调用" malloc(X * N)"的传统技术,因为reallocarray()消除了X * N乘法将溢出并导致malloc()返回与应用程序大小不同的缓冲区的风险预期.

SQLite不使用reallocarray(). 原因是reallocarray()对SQLite没用. 事实证明,SQLite从未执行过两个整数的简单乘积的内存分配. 相反,SQLite会以" X + C"或" N * X + C"或" M * N * X + C"或" N * X + M * Y + C"的形式进行分配,依此类推. 在这些情况下,reallocarray()接口无助于避免整数溢出.

但是,SQLite希望解决内存分配大小计算中的整数溢出问题. 为了防止出现问题,所有SQLite内部内存分配都使用带有签名64位整数大小参数的精简包装函数进行. 审核SQLite源代码以确保也使用64位带符号整数执行所有大小的计算. SQLite将拒绝一次分配超过2GB的内存. (通常,SQLite一次很少分配超过8KB的内存,因此2GB的分配限制不是负担.)因此64位大小参数为检测溢出提供了很大的空间. 验证所有大小的计算均以64位有符号整数完成的同一审核还验证在计算期间不可能溢出64位整数.

在每个SQLite版本之前,都会重复执行用于确保内存分配大小计算不会在SQLite中溢出的代码审核.

3. Configuration

SQLite中的默认内存分配设置适用于大多数应用程序. 但是,具有异常或特别严格要求的应用程序可能需要调整配置,以使SQLite更加符合他们的需求. 编译时和启动时配置选项均可用.

3.1. Alternative low-level memory allocators

SQLite源代码包括几个不同的内存分配模块,这些模块可以在编译时选择,也可以在启动时有限地选择.

3.1.1. The default memory allocator

默认情况下,SQLite使用标准C库中的malloc(),realloc()和free()例程来满足其内存分配需求. 这些例程被一个精简包装程序所包围,该包装程序还提供一个" memsize()"函数,该函数将返回现有分配的大小. 需要memsize()函数来准确计数未清内存的字节数; memsize()确定释放分配时要从未清计数中删除多少个字节. 默认分配器通过始终在每个malloc()请求上分配8个额外的字节并将分配的大小存储在该8字节的标头中来实现memsize().

对于大多数应用程序,建议使用默认的内存分配器. 如果您没有迫切需要使用备用内存分配器,请使用默认值.

3.1.2. The debugging memory allocator

如果使用SQLITE_MEMDEBUG编译时选项编译SQLite,则围绕系统malloc(),realloc()和free()使用不同的重包装器. 重包装器每次分配大约分配100个字节的额外空间. 多余的空间用于将哨兵值放置在返回到SQLite核心的分配的两端. 释放分配后,将检查这些标记以确保SQLite内核没有在任一方向上溢出缓冲区. 当系统库是GLIBC时,重包装器还利用GNU backtrace()函数检查堆栈并记录malloc()调用的祖先函数. 当运行SQLite测试套件时,繁重的包装器还会记录当前测试用例的名称. 后两个功能对于跟踪测试套件检测到的内存泄漏源很有用.

设置SQLITE_MEMDEBUG时使用的重包装器还可以确保在将分配返回给调用者之前,每个新分配都被无用数据填充. 一旦分配是免费的,它就会再次被废话数据所填充. 这两个动作有助于确保SQLite核心不会对新分配的内存状态做出假设,并且确保释放内存后不使用内存分配.

SQLITE_MEMDEBUG使用的重包装器仅适用于SQLite的测试,分析和调试. 繁重的包装程序具有显着的性能和内存开销,因此可能不应该在生产中使用.

3.1.3. The Win32 native memory allocator

If SQLite is compiled for Windows with the SQLITE_WIN32_MALLOC compile-time option, then a different, thin wrapper is used around HeapAlloc(), HeapReAlloc(), and HeapFree(). The thin wrapper uses the configured SQLite heap, which will be different from the default process heap if the SQLITE_WIN32_HEAP_CREATE compile-time option is used. In addition, when an allocation is made or freed, HeapValidate() will be called if SQLite is compiled with assert() enabled and the SQLITE_WIN32_MALLOC_VALIDATE compile-time option.

3.1.4. Zero-malloc memory allocator

使用SQLITE_ENABLE_MEMSYS5选项编译SQLite时, 构建中将包含一个不使用malloc()的备用内存分配器. SQLite开发人员将此替代内存分配器称为" memsys5". 即使将其包含在构建中,memsys5也会默认禁用. 要启用memsys5,应用程序必须在启动时调用以下SQLite接口:

sqlite3_config(SQLITE_CONFIG_HEAP, pBuf, szBuf, mnReq);

在上面的调用中,pBuf是指向大量连续内存空间的指针,SQLite将使用该内存空间来满足其所有内存分配需求. pBuf可能指向静态数组,也可能是从其他一些特定于应用程序的机制获得的内存. szBuf是一个整数,它是pBuf指向的内存空间的字节数. mnReq是另一个整数,它是分配的最小大小. 对N小于mnReq的sqlite3_malloc(N)的任何调用将四舍五入为mnReq. mnReq必须为2的幂. 稍后我们将看到mnReq参数对于减小n的值非常重要,因此对于Robson证明中的最小内存大小要求也很重要.

memsys5分配器设计用于嵌入式系统,尽管没有什么可以阻止其在工作站上使用. szBuf通常在几百KB到几十MB之间,具体取决于系统要求和内存预算.

memsys5使用的算法可以称为"二乘幂,优先拟合". 所有内存分配请求的大小均四舍五入为2的幂,并且该请求由pBuf中足够大的第一个空闲插槽满足. 使用伙伴系统合并相邻的释放分配. 当适当地使用,这种算法提供了对破碎和击穿数学担保,进一步描述如下 .

3.1.5. Experimental memory allocators

用于零malloc内存分配器的名称" memsys5"表示存在多个可用的内存分配器,并且确实存在. 默认的内存分配器是" memsys1". 调试内存分配器是" memsys2". 这些已经被覆盖.

If SQLite is compiled with SQLITE_ENABLE_MEMSYS3 then another zero-malloc memory allocator, similar to memsys5, is included in the source tree. The memsys3 allocator, like memsys5, must be activated by a call to sqlite3_config(SQLITE_CONFIG_HEAP,...). Memsys3 uses the memory buffer supplied as its source for all memory allocations. The difference between memsys3 and memsys5 is that memsys3 uses a different memory allocation algorithm that seems to work well in practice, but which does not provide mathematical guarantees against memory fragmentation and breakdown. Memsys3 was a predecessor to memsys5. The SQLite developers now believe that memsys5 is superior to memsys3 and that all applications that need a zero-malloc memory allocator should use memsys5 in preference to memsys3. Memsys3 is considered both experimental and deprecated and will likely be removed from the source tree in a future release of SQLite.

Memsys4和memsys6是实验性的内存分配器,于2007年左右推出,随后在没有明显增加新价值的情况下于2008年左右从源树中删除.

其他实验性内存分配器可能会在SQLite的未来版本中添加. 可以预期它们将被称为memsys7,memsys8等.

3.1.6. Application-defined memory allocators

New memory allocators do not have to be part of the SQLite source tree nor included in the sqlite3.c amalgamation. Individual applications can supply their own memory allocators to SQLite at start-time.

为了使SQLite使用新的内存分配器,应用程序只需调用:

sqlite3_config(SQLITE_CONFIG_MALLOC, pMem);

在上面的调用中,pMem是指向sqlite3_mem_methods对象的指针,该对象定义了到特定于应用程序的内存分配器的接口. sqlite3_mem_methods对象实际上只是一个结构,其中包含指向实现各种内存分配原语的函数的指针.

在多线程应用程序中,仅在启用SQLITE_CONFIG_MEMSTATUS的情况下,对sqlite3_mem_methods的访问才被序列化. 如果禁用了SQLITE_CONFIG_MEMSTATUS,sqlite3_mem_methods中的方法必须照顾自己的序列化需求.

3.1.7. Memory allocator overlays

应用程序可以在SQLite核心和基础内存分配器之间插入层或"覆盖". 例如,SQLite的内存不足测试逻辑使用可以模拟内存分配失败的覆盖.

可以使用

sqlite3_config(SQLITE_CONFIG_GETMALLOC, pOldMem);

接口以获取指向现有内存分配器的指针. 现有分配器由叠加层保存,并用作后备以进行实际内存分配. 然后, 如上所述使用sqlite3_configSQLITE_CONFIG_MALLOC ,...)插入覆盖层,以代替现有的内存分配器.

3.1.8. No-op memory allocator stub

如果使用SQLITE_ZERO_MALLOC选项编译SQLite,则默认内存分配器将被省略,并由一个从不分配任何内存的存根内存分配器代替. 对存根内存分配器的任何调用都将报告没有可用的内存.

无操作内存分配器本身没有用. 它仅作为占位符存在,因此SQLite可以在其标准库中可能没有malloc(),free()或realloc()的系统上链接一个内存分配器. 在开始使用SQLite之前,使用SQLITE_ZERO_MALLOC编译的应用程序将需要与SQLITE_CONFIG_MALLOCSQLITE_CONFIG_HEAP一起使用sqlite3_config()来指定新的备用内存分配器.

3.2. Page cache memory

在大多数应用程序中,SQLite中的数据库页面缓存子系统使用的动态分配内存要比SQLite的所有其他部分所组合的更多. 看到数据库页面缓存消耗的内存比其余SQLite的总和多10倍以上,这并不罕见.

可以将SQLite配置为从固定大小的插槽的单独且不同的内存池中进行页面缓存内存分配. 这可以具有两个优点:

The page-cache memory allocator is disabled by default. An application can enable it at start-time as follows:

sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N);

pBuf参数是一个指向连续字节范围的指针,SQLite将使用这些字节来进行页面缓存内存分配. 缓冲区的大小必须至少为sz * N个字节. " sz"参数是每个页面缓存分配的大小. N是可用分配的最大数量.

如果SQLite需要一个大于" sz"字节的页面缓存条目,或者如果它需要超过N个条目,则退回使用通用内存分配器.

3.3. Lookaside memory allocator

SQLite 数据库连接会进行许多小型且短暂的内存分配. 这种情况发生时最常使用编译SQL语句时sqlite3_prepare_v2()运行时,而且在较小程度上准备的语句使用sqlite3_step() . 这些小的内存分配用于保存诸如表和列的名称,解析树节点,单个查询结果值以及B-Tree游标对象之类的东西. 因此,对malloc()和free()的调用很多-如此多的调用使malloc()和free()最终占用分配给SQLite的CPU时间的很大一部分.

SQLite 版本3.6.1 (2008-08-06)引入了后备内存分配器,以帮助减少内存分配负载. 在后备分配器中,每个数据库连接都会预分配一个大内存块(通常在60到120 KB范围内),并将该内存块分成每个约100到1000字节的固定大小的"小插槽". 这将成为后备内存池. 此后,使用后备池插槽之一而不是通过调用通用内存分配器,可以满足与数据库连接关联的并且不太大的内存分配. 较大的分配继续使用通用内存分配器,当所有后备池插槽都被签出时,分配也会继续使用. 但是,在许多情况下,内存分配足够小,并且几乎没有足够的未完成操作可以从后备池中满足新的内存请求.

由于后备分配的大小始终相同,因此分配和释放算法很快. 无需合并相邻的空闲插槽或搜索特定大小的插槽. 每个数据库连接维护一个未使用插槽的单链接列表. 分配请求只是拉出该列表的第一个元素. 取消分配只是将元素推回列表的前面. 此外,假定每个数据库连接已经在单个线程中运行(已经存在强制执行此操作的互斥锁),因此不需要附加的互斥即可序列化对后备插槽空闲列表的访问. 因此,后备内存分配和释放非常快. 在Linux和Mac OS X工作站上的速度测试中,SQLite已将整体性能提高了10%和15%,具体取决于工作负载的配置方式和后备方式.

后备内存池的大小具有全局默认值,但也可以基于逐个连接进行配置. option. 要在编译时更改后备内存池的默认大小,请使用-DSQLITE_DEFAULT_LOOKASIDE = 选项. 要在启动时更改后备内存池的默认大小,请使用sqlite3_config()接口:

sqlite3_config(SQLITE_CONFIG_LOOKASIDE, sz, cnt);

" sz"参数是每个后备插槽的大小(以字节为单位). " cnt"参数是每个数据库连接的后备内存插槽总数. 分配给每个数据库连接的后备内存总量为sz * cnt字节.

可以使用以下调用为单个数据库连接 " db"更改后备池:

sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, pBuf, sz, cnt);

" pBuf"参数是指向将用于后备内存池的内存空间的指针. 如果pBuf为NULL,则SQLite将使用sqlite3_malloc()获得其自己的内存池空间. " sz"和" cnt"参数分别是每个后备插槽的大小和插槽数. 如果pBuf不为NULL,则它必须至少指向sz * cnt字节的内存.

仅当数据库连接没有未完成的后备分配时,后备配置才能更改. 因此,应在使用sqlite3_open() (或等效方法)创建数据库连接之后并在评估连接上的任何SQL语句之前立即设置配置.

3.3.1. Two-Size Lookaside

从SQLite版本3.31.0(2020-01-22)开始,后备支持两个内存池,每个内存池具有不同的大小插槽. 小插槽池使用128字节的插槽,大插槽池使用SQLITE_DBCONFIG_LOOKASIDE指定的任何大小(默认为1200字节). 像这样将池分成两部分,可以使后备存储器更频繁地覆盖内存分配,同时将每个数据库连接堆的使用量从120KB减少到48KB.

如上所述,配置继续使用带有参数" sz"和" cnt"的SQLITE_DBCONFIG_LOOKASIDE或SQLITE_CONFIG_LOOKASIDE配置选项. 用于后备的总堆空间仍然是sz * cnt字节. 但是,空间是在小时隙后备空间和大时隙后备空间之间分配的,优先考虑小时隙后备空间. 插槽的总数通常会超过" cnt",因为" sz"通常比128字节的小插槽大小大得多.

默认的后备配置已从每个1200字节(120KB)的100个插槽更改为每个1200字节(48KB)的40个插槽. 该空间最终分配为93个插槽,每个插槽128个字节,每个30个插槽,每个1200字节. 因此,可以使用更多后备插槽,但使用的堆空间要少得多.

默认的后备配置,小插槽的大小以及如何在小插槽和大插槽之间分配堆空间的详细信息都可能从一个发行版更改到另一个发行版.

3.4. Memory status

By default, SQLite keeps statistics on its memory usage. These statistics are useful in helping to determine how much memory an application really needs. The statistics can also be used in high-reliability system to determine if the memory usage is coming close to or exceeding the limits of the Robson proof and hence that the memory allocation subsystem is liable to breakdown.

大多数内存统计信息是全局的,因此必须使用互斥锁对统计信息进行跟踪. 默认情况下,统计信息处于打开状态,但是存在禁用统计信息的选项. 通过禁用内存统计信息,SQLite避免了在每个内存分配和释放过程中进入和离开互斥锁. 在互斥操作昂贵的系统上,这种节省是显而易见的. 要禁用内存统计信息,请在启动时使用以下界面:

sqlite3_config(SQLITE_CONFIG_MEMSTATUS, onoff);

" onoff"参数为true表示启用内存统计信息跟踪,值为false表示禁用统计信息跟踪.

假设启用了统计信息,则可以使用以下例程来访问它们:

sqlite3_status(verb, &current, &highwater, resetflag);

"动词"参数确定要访问的统计信息. 定义了各种动词 . 随着sqlite3_status()接口的成熟,该列表有望增长. 所选参数的当前值被写入整数" current",而最高历史值被写入整数" highwater". 如果resetflag为true,则在调用返回后,高水位标记将重置为当前值.

使用不同的接口来查找与单个数据库连接相关的统计信息:

sqlite3_db_status(db, verb, &current, &highwater, resetflag);

该接口与之类似,不同之处在于它以指向数据库连接的指针作为第一个参数,并返回有关该对象而不是整个SQLite库的统计信息. 尽管将来可能会添加其他动词,但sqlite3_db_status()接口目前只能识别单个动词SQLITE_DBSTATUS_LOOKASIDE_USED .

每个连接的统计信息不使用全局变量,因此不需要互斥来更新或访问. 因此,即使关闭了SQLITE_CONFIG_MEMSTATUS ,每个连接的统计信息仍继续起作用.

3.5. Setting memory usage limits

sqlite3_soft_heap_limit64()接口可用于设置SQLite通用内存分配器一次允许释放的未完成内存总量的上限. 如果尝试分配的内存超出了软堆限制所指定的数量,则SQLite将首先尝试释放缓存内存,然后再继续分配请求. 软堆限制机制仅在启用了内存统计信息的情况下才有效,并且如果使用SQLITE_ENABLE_MEMORY_MANAGEMENT编译时选项编译SQLite库,则其效果最佳.

从这个意义上讲,软堆限制是"软"的:如果SQLite无法释放足够的辅助内存以保持低于限制,它将继续并分配额外的内存并超过其限制. 这是根据以下理论发生的:使用额外的内存比完全失败更好.

从SQLite 版本3.6.1 (2008-08-06)开始,软堆限制仅适用于通用内存分配器. 软堆限制不了解页面缓存内存分配器后备内存分配器或与之交互. 在将来的发行版中可能会解决此缺陷.

4. Mathematical Guarantees Against Memory Allocation Failures

JM Robson已研究了动态内存分配问题,尤其是内存分配器崩溃的问题,并将结果发布为:

JM罗布森. "有关动态存储分配的某些功能的界限". , Volume 21, Number 8, July 1974, pages 491-499. ,1974年7月,第21卷,第8期,第491-499页.

让我们使用以下表示法(与Robson的表示法相似但不同):

N 内存分配系统所需的原始内存量,以确保不会出现任何内存分配失败.
M 应用程序在任何时间点都签出的最大内存量.
n 最大内存分配与最小内存分配的比率. 我们假设每个内存分配大小都是最小内存分配大小的整数倍.

Robson证明以下结果:

N = M *(1 +(log 2 n )/ 2) -n + 1

通俗地说,在罗布森证明表明,为了保证无故障运行,任何内存分配器必须使用大小为N的存储器池超过有史以来取决于n个乘法器,最大的比例使用M的存储器的最大量到最小的分配大小. 换句话说,除非所有内存分配的大小都完全相同( n = 1),否则系统需要访问的存储空间将超过一次使用的存储空间. 此外,我们看到,随着最大分配与最小分配之比的增加,所需的剩余内存量迅速增长,因此有强烈的动机将所有分配保持在尽可能近的相同大小.

罗布森的证明是建设性的. 他提供了一种算法,用于计算分配和释放操作的序列,如果可用内存比N少一个字节,则会由于内存碎片而导致分配失败. 而且,罗布森(Robson)显示,如果可用内存为N个或更多字节,则2次幂的第一个拟合内存分配器(例如由memsys5实现)将永远不会使内存分配失败.

值M和n是应用程序的属性. 如果应用程序的构造使得M和n都已知,或者至少具有已知的上限,并且该应用程序使用memsys5内存分配器,并且使用SQLITE_CONFIG_HEAP提供了N字节的可用内存空间,那么罗布森证明没有内存分配请求将永远不会在应用程序内失败. 换句话说,应用程序开发人员可以为N选择一个值,该值将保证对任何SQLite接口的调用都不会返回SQLITE_NOMEM . 内存池永远不会变得如此分散,以致无法满足新的内存分配请求. 对于软件故障可能会导致人身伤害,物理伤害或不可替代数据丢失的应用程序,这是一个重要属性.

4.1. Computing and controlling parameters M and n

Robson证明分别适用于SQLite使用的每个内存分配器:

对于除memsys5之外的分配器 ,所有内存分配大小均相同. 因此, n = 1,因此N = M. 换句话说,内存池不必大于任何给定时刻正在使用的最大内存量.

在SQLite 3.6.1版中,页面缓存内存的使用在某种程度上更难控制,尽管计划为后续发行版设计机制,这将使控制页面缓存内存更加容易. 在引入这些新机制之前,控制pagecache内存的唯一方法是使用cache_size pragma .

对安全性有严格要求的应用程序通常会希望修改默认的后备内存配置,以便在sqlite3_open()期间分配初始后备内存缓冲区时,所得的内存分配不会太大而导致n参数太大. 为了使n受控制,最好尝试将最大的内存分配保持在2或4 KB以下. 因此,后备内存分配器的合理默认设置可能是以下任何一种:

sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 32, 32);  /* 1K */
sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 64, 32);  /* 2K */
sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 32, 64);  /* 2K */
sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 64, 64);  /* 4K */

另一种方法是首先禁用后备内存分配器:

sqlite3_config(SQLITE_CONFIG_LOOKASIDE, 0, 0);

然后,让应用程序维护一个单独的较大后备内存缓冲区池,在创建它们时可以将其分配给数据库连接 . 在通常情况下,应用程序将只有一个数据库连接 ,因此后备内存池可以由单个大缓冲区组成.

sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE, aStatic, 256, 500);

后备内存分配器实际上旨在实现性能优化,而不是确保无故障内存分配的方法,因此完全禁用后备内存分配器以进行安全性至关重要的操作并非没有道理.

通用内存分配器是最难管理的内存池,因为它支持大小不同的分配. 由于n是M的乘数,因此我们要使n尽可能小. 这表明应将memsys5的最小分配大小保持尽可能大. 在大多数应用程序中, 后备内存分配器能够处理较小的分配. 因此,将memsys5的最小分配大小设置为后备分配最大大小的2倍,4倍甚至8倍是合理的. 最小分配大小为512是合理的设置.

除了使n较小之外,还希望将最大内存分配的大小保持在控制之下. 对通用内存分配器的大请求可能来自以下几个来源:

  1. 包含大字符串或BLOB的SQL表行.
  2. 复杂的SQL查询可编译为大型的准备好的语句 .
  3. sqlite3_prepare_v2()内部使用的SQL解析器对象.
  4. 数据库连接对象的存储空间.
  5. 页面缓存内存分配溢出到通用内存分配器中.
  6. 数据库连接的后备缓冲区分配.

如上所述,可以通过配置页面缓存内存分配器后备内存分配器来控制和/或消除最后两个分配. 数据库连接对象所需的存储空间在某种程度上取决于数据库文件名的长度,但是在32位系统上很少超过2KB. (由于指针的增加,在64位系统上需要更多空间.)每个解析器对象使用大约1.6KB的内存. 因此,可以容易地控制上述元件3至7以将最大存储器分配大小保持在2KB以下.

如果应用程序旨在管理小块数据,则数据库不应包含任何大字符串或BLOB,因此,上面的元素1不应成为因素. 如果数据库确实包含大字符串或BLOB,则应使用增量BLOB I / O读取它们,并且包含大字符串或BLOB的行不应通过增量BLOB I / O以外的任何方式进行更新. 否则, sqlite3_step()例程将需要在某个时刻将整行读取到连续内存中,这将涉及至少一个大内存分配.

The final source of large memory allocations is the space to hold the prepared statements that result from compiling complex SQL operations. Ongoing work by the SQLite developers is reducing the amount of space required here. But large and complex queries might still require prepared statements that are several kilobytes in size. The only workaround at the moment is for the application to break complex SQL operations up into two or more smaller and simpler operations contained in separate prepared statements.

考虑所有因素,应用程序通常应该能够将其最大内存分配大小保持在2K或4K以下. 这给出log 2 ( n )的值为2或3.这会将N限制为2到2.5乘以M.

应用程序所需的最大通用内存量由以下因素确定,这些因素包括应用程序使用多少个同时打开的数据库连接准备好的语句对象,以及准备好的语句的复杂性. 对于任何给定的应用程序,这些因素通常是固定的,可以使用SQLITE_STATUS_MEMORY_USED通过实验确定. 一个典型的应用程序可能只使用大约40KB的通用内存. N的值约为100KB.

4.2. Ductile failure

如果将SQLite中的内存分配子系统配置为无故障运行,但实际内存使用量超出了Robson证明所设置的设计限制,则SQLite通常将继续正常运行. 页面缓存内存分配器后备内存分配器会自动故障转移到memsys5通用内存分配器. 通常情况下,即使M和/或n超过Robson证明施加的限制, memsys5内存分配器也将继续运行而不会产生碎片. Robson证明表明,在这种情况下,内存分配有可能崩溃并失败,但是这种失败需要特别卑鄙的分配和取消分配序列-从未观察到遵循SQLite的序列. 因此,在实践中,通常情况下,Robson所施加的限制可以被相当大的幅度超出,而不会产生不良影响.

尽管如此,建议应用程序开发人员监视内存分配子系统的状态,并在内存使用率接近或超过Robson限制时发出警报. 这样,该应用程序将在发生故障之前很好地为操作员提供丰富的警告. SQLite的内存统计信息接口为应用程序提供了完成此任务的监视部分所需的所有机制.

5. Stability Of Memory Interfaces

更新:从SQLite版本3.7.0(2010-07-21)开始,所有SQLite内存分配接口都被认为是稳定的,并且在将来的版本中将受支持.

by  ICOPY.SITE