35% Faster Than The Filesystem
35% Faster Than The Filesystem
1.总结
1.1. 注意事项
1.2. 相关研究
2.如何进行这些测量
2.1. 阅读性能测量
2.2. 写性能测量
2.3. 变化
3.一般调查结果
4.其他注意事项
4.1. 在Android上编译和测试
1.总结
SQLite使用fread()或fwrite()读取和写入小块(例如缩略图)比使用fread()或fwrite()从磁盘上的单个文件读取或写入相同的块的速度快35%。
此外,保存10千字节blob的单个SQLite数据库使用的磁盘空间比单个文件中存储blob少20%。
性能上的差异出现了(我们相信),因为从SQLite数据库进行工作时,open()和close()系统调用只会被调用一次,而在使用blob中存储的blob时,会为每个blob调用open()和close单个文件。看起来调用open()和close()的开销大于使用数据库的开销。大小的减少是由于单个文件被填充到文件系统块大小的下一个倍数,而blob被更紧密地打包到SQLite数据库中。
本文中的测量是在2017-06-05的一周内使用3.19.2和3.20.0之间的SQLite版本进行的。您可能期望SQLite的未来版本能够更好地执行。
1.1. 注意事项
¹35%以上的数字是近似值。实际时间因硬件,操作系统和实验细节以及实际硬件上的随机性能波动而异。有关更多详细信息,请参阅下文。亲自尝试实验。向邮件列表报告重大偏差。
这35%的数据是基于作者轻松掌握的每台机器上的运行测试。本文的一些评论者报告说SQLite比他们的系统上的直接I / O具有更高的延迟。我们还不了解其中的差异。我们还看到有迹象显示,当使用冷文件系统缓存运行实验时,SQLite的性能不如直接I / O。
因此,让您的收获如此:SQLite的读取/写入延迟与磁盘上单个文件的读取/写入延迟相当具有竞争力。通常SQLite更快。有时SQLite几乎一样快。无论采用哪种方法,本文都反驳了常见的假设,即关系数据库必须比直接文件系统I / O慢。
1.2. 相关研究
Jim Gray和其他人研究了BLOB与Microsoft SQL Server的文件I / O的读取性能,发现从数据库中读取BLOB的速度要快于BLOB大小小于250KiB和1MiB。(纸)。在该研究中,即使内容保存在单独的文件中,数据库仍会保存内容的文件名。因此,即使只提取文件名,也会为每个BLOB查询数据库。在本文中,BLOB的关键是文件名,因此不需要初步的数据库访问。由于本文从阅读单个文件中读取内容时从不使用数据库,因此直接文件I / O变得更快的阈值小于Gray的论文中的阈值。
本网站的内部和外部BLOB文章是早期调查(大约2011年),它使用与Jim Gray论文相同的方法 - 将blob文件名存储为数据库中的条目 - 但是用于SQLite而不是SQL Server。
2.如何进行这些测量
使用SQLite源代码树中的kvtest.c程序测量I / O性能。为了编译这个测试程序,首先将kvtest.c源文件收集到SQLite合并源文件“sqlite3.c”和“sqlite3.h”的目录中。然后在unix上运行如下命令:
gcc -Os -I. -DSQLITE_DIRECT_OVERFLOW_READ \
kvtest.c sqlite3.c -o kvtest -ldl -lpthread
Or on Windows with MSVC:
cl -I. -DSQLITE_DIRECT_OVERFLOW_READ kvtest.c sqlite3.c
下面显示了针对Android编译的说明。
使用生成的“kvtest”程序,使用如下命令生成一个包含100,000个随机不可压缩blob的测试数据库,每个随机大小介于8,000和12,000字节之间:
./kvtest init test1.db --count 100k --size 10k --variance 2k
如果需要,您可以通过运行以下命令来验证新数据库:
./kvtest stat test1.db
接下来,使用如下命令将所有blob复制到目录中的单个文件中:
./kvtest export test1.db test1.dir
此时,您可以测量test1.db数据库使用的磁盘空间量以及test1.dir目录及其所有内容使用的空间量。在标准的Ubuntu Linux桌面上,数据库文件大小为1,024,512,000字节,test1.dir目录将使用1,228,800,000字节的空间(根据“du -k”),大约比数据库多20%。
上面创建的“test1.dir”目录将所有blob放入一个文件夹中。据推测,当一个目录包含100,000个对象时,某些操作系统的性能会很差。为了测试这个,kvtest程序还可以将Blob存储在文件夹层次结构中,每个文件夹的文件和/或子目录不超过100个。可以使用“export”命令的--tree命令行选项来创建Blob的备选磁盘表示形式,如下所示:
./kvtest export test1.db test1.tree --tree
test1.dir目录将包含名称为“000000”,“000001”,“000002”等的100,000个文件,但test1.tree目录将包含子目录中的相同文件,如“00/00/00”,“00 / 00/01“等。test1.dir和test1.test目录占用大约相同的空间量,但由于额外的目录条目,test1.test略微大一些。
以下所有实验都与“test1.dir”或“test1.tree”相同。在任何情况下,无论操作系统如何,性能差异都很小。
使用以下命令测量从数据库和单个文件中读取blob的性能:
./kvtest run test1.db --count 100k --blob-api
./kvtest run test1.dir --count 100k --blob-api
./kvtest run test1.tree --count 100k --blob-api
根据您的硬件和操作系统,您应该看到test1.db数据库文件的读取速度比从test1.dir或test1.tree文件夹中的单个文件读取速度快大约35%。由于缓存的不同,结果可能因运行而异,因此建议您多次运行测试,并根据您的要求采取平均或最差情况或最佳情况。
数据库读取测试的--blob-api选项会导致kvtest使用SQLite的sqlite3_blob_read()功能加载blob的内容,而不是运行纯SQL语句。这有助于SQLite在读取测试时运行得更快一些。您可以省略该选项来比较SQLite运行SQL语句的性能。在这种情况下,SQLite仍然会执行直接读取,但不如使用sqlite3_blob_read()时那么多。对于从单个磁盘文件读取的测试,将忽略--blob-api选项。
通过添加--update选项来衡量写入性能。这会导致斑点被另一个大小完全相同的随机斑点覆盖。
./kvtest run test1.db --count 100k --update
./kvtest run test1.dir --count 100k --update
./kvtest run test1.tree --count 100k --update
上面的写作测试并不完全公平,因为SQLite正在进行电源安全交易,而直接写入磁盘的交易则不是。为了使测试更加平等,在SQLite中添加--nosync选项来禁止调用fsync()或FlushFileBuffers()来强制内容到磁盘,或者使用--fsync选项来实现直接到达磁盘测试强制他们在更新磁盘文件时调用fsync()或FlushFileBuffers()。
默认情况下,kvtest在单个事务中运行数据库I / O测量。使用--multitrans选项在单独的事务中运行每个blob读取或写入。--multitrans选项使得SQLite变得更慢,并且与直接磁盘I / O无竞争力。这个选项再次证明,为了从SQLite中获得最高的性能,您应该在单个事务中尽可能多地分组数据库交互。
还有很多其他的测试选项,可以通过运行命令来查看:
./kvtest help
2.1。阅读性能测量
下面的图表显示了在五个不同系统上使用kvtest.c收集的数据:
Win7
:大约2009年戴尔Inspiron笔记本电脑,奔腾双核2.30GHz,4GiB内存,Windows7。
Win10
:2016年联想YOGA 910,2.70GHz的Intel i7-7500,16GiB RAM,Windows10。
Mac
:2015Mac
Book Pro,3.1GHz Intel Core i7,16GiB RAM,Mac
OS 10.12.5
Ubuntu
:使用Intel i7-4770K(3.50GHz,32GiB RAM,Ubuntu
16.04.2 LTS)构建的桌面
Android
下载应用程序类别Galaxy S3,ARMv7,2GiB RAM
除Win7以外,所有机器都使用SSD,硬盘驱动器。测试数据库是大小均匀分布在8K和12K之间的100K blob,总共大约1 GB的内容。数据库页面大小为4KiB。所有这些测试都使用了-DSQLITE_DIRECT_OVERFLOW_READ编译时选项。测试运行多次。第一次运行是用来预热高速缓存,并放弃它的时间。
下面的图表显示了直接从文件系统读取blob的平均时间与从SQLite数据库读取同一个blob所需的时间。实际的时间因系统而异(Ubuntu桌面比Galaxy S3手机要快得多)。此图表显示了从文件中读取Blob所需的时间除以来自数据库所需时间的比率。图表中最左边的列是从数据库读取的标准化时间,以供参考。
在该图表中,一次准备了SQL语句(“SELECT v FROM kv WHERE k =?1”)。然后,对于每个blob,blob键值都绑定到?1参数,并对语句进行评估以提取blob内容。
该图表显示,在Windows10上,可以从SQLite数据库读取的内容比从磁盘直接读取快5倍。在Android上,SQLite仅比从磁盘读取快35%。
图1:相对于直接文件系统读取的SQLite读取延迟。
100K blob,平均每个10KB,使用SQL的随机顺序
通过绕过SQL层并直接使用sqlite3_blob_read()接口读取blob内容,可以稍微改进性能,如下图所示:
图2:相对于直接文件系统读取的SQLite读取延迟。
100K blob,平均大小10KB,随机顺序
using sqlite3_blob_read().
通过使用SQLite的内存映射I / O功能可以进一步提高性能。在下一个图表中,整个1GB数据库文件是内存映射的,使用sqlite3_blob_read()接口读取blob(以随机顺序)。通过这些优化,SQLite的速度是Android或MacOS-X的两倍,比Windows快10倍以上。
图3:与直接文件系统读取相关的SQLite读取延迟。
100K blob,平均大小10KB,随机顺序
using sqlite3_blob_read() from a memory-mapped database.
第三个图表显示,从SQLite中读取blob内容的速度是从Mac和Android的磁盘上的单个文件读取速度的两倍,而Windows的速度却快了十倍。
2.2. 写性能测量
写入较慢。在所有使用直接I / O和SQLite的系统上,写入性能比读取速度慢5到15倍。
写性能测量是通过用不同的blob替换(覆盖)整个blob来完成的。所有这些实验中的斑点都是随机的,不可压缩的。由于写入速度比读取速度慢得多,数据库中只有10,000个数据块被替换。要被替换的斑点是随机选择的,并没有特定的顺序。
直接到磁盘的写操作使用fopen()/ fwrite()/ fclose()来完成。默认情况下,在下面显示的所有结果中,OS文件系统缓冲区永远不会使用fsync()或FlushFileBuffers()刷新到持久存储。换句话说,没有试图使直接到磁盘写入交易或电力安全。我们发现,在写入的每个文件上调用fsync()或FlushFileBuffers()会导致直接到磁盘的存储速度比写入SQLite慢10倍或更多。
下一个图表比较了WAL模式下的SQLite数据库更新与磁盘上单独文件的原始直接到磁盘覆盖。PRAGMA同步设置为NORMAL。所有数据库写入都在单个事务中。数据库写入的计时器在事务提交后但在运行检查点之前停止。请注意,与直接到磁盘写入不同,SQLite写入是事务性和电源安全的,但由于同步设置是NORMAL而不是FULL,因此事务不会持久。
图4:与直接文件系统写入相关的SQLite写入延迟。
10K blob,平均大小10KB,随机顺序,
具有同步NORMAL的WAL模式,
不包括检查点时间
写入实验的android性能数字被忽略,因为Galaxy S3上的性能测试非常随机。完全相同的实验连续两次运行会产生截然不同的时间。而且,公平地说,android上的SQLite性能比直接写入磁盘要慢一些。
下图显示了在禁用事务时(PRAGMA journal_mode = OFF)和PRAGMA同步设置为OFF时SQLite与直接到磁盘的性能比较。这些设置使SQLite与直接到磁盘的写入处于同等地位,这就是说,由于系统崩溃和电源故障,它们使数据容易受损。
图5:与直接文件系统写入相关的SQLite写入延迟。
10K blob,平均大小10KB,随机顺序,
日志禁用,同步OFF。
在所有写入测试中,在运行直接磁盘性能测试之前禁用防病毒软件非常重要。我们发现反病毒软件的直接到磁盘的速度减慢了一个数量级,而它对SQLite的写入影响很小。这可能是由于直接到磁盘更改了数千个单独的文件,这些文件都需要通过防病毒进行检查,而SQLite写入仅更改单个数据库文件。
2.3. 变化
编译时选项-DSQLITE_DIRECT_OVERFLOW_READ会导致SQLite在从溢出页面读取内容时绕过它的页面缓存。这有助于10K blob的数据库读取速度提高一点,但速度并不快。与没有SQLITE_DIRECT_OVERFLOW_READ编译时选项的直接文件系统读取相比,SQLite仍然具有速度优势。
其他编译时选项(如使用-O3代替-Os或使用-DSQLITE_THREADSAFE = 0和/或其他一些推荐的编译时选项)可能有助于SQLite相对于直接文件系统读取运行得更快。
测试数据中斑点的大小会影响性能。对于较大的blob,文件系统通常会更快,因为open()和close()的开销会分摊到I / O的更多字节上,而数据库在平均blob大小下降时速度和空间都会更高效。
3.一般调查结果
- 与读取和写入时相比,SQLite与存储在磁盘上单独文件中的斑点相比具有竞争性,并且通常更快。
- 在启用防病毒保护功能时,SQLite比在Windows上直接写入磁盘要快得多。由于防病毒软件在Windows中默认情况下处于打开状态,这意味着SQLite通常比Windows上的直接磁盘写入快得多。
- 对于所有系统以及SQLite和直接到磁盘的I / O,读取速度比写入速度快一个数量级。
- I / O性能在很大程度上取决于操作系统和硬件。在得出结论之前进行自己的测量。
- 其他一些SQL数据库引擎建议开发人员将blob存储在单独的文件中,然后将文件名存储在数据库中。在这种情况下,在打开和读取文件之前必须首先查阅数据库以查找文件名,只需将整个blob存储在数据库中,就可以使用SQLite更快地读写数据。有关更多信息,请参阅内部对外部BLOB文章。
4.其他注意事项
4.1。在Android上编译和测试
kvtest程序在Android上进行编译和运行,如下所示。首先安装Android SDK和NDK。然后准备一个名为“android-gcc”的脚本,大致如下所示:
#!/bin/sh
#
NDK=/home/drh/Android/Sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-16/arch-arm
ABIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin
GCC=$ABIN/arm-linux-androideabi-gcc
$GCC --sysroot=$SYSROOT -fPIC -pie $*
使该脚本可执行并将其放在$ PATH中。然后编译kvtest程序如下:
android-gcc -Os -I. kvtest.c sqlite3.c -o kvtest-android
接下来,将生成的kvtest-android可执行文件移动到Android设备上:
adb push kvtest-android /data/local/tmp
最后使用“adb shell”在Android设备上获取shell提示符,cd到/ data / local / tmp目录中,并像其他任何unix主机一样开始运行测试。
SQLite在公共领域。