我有一个管理结构数组的用例。这看起来像一个 SQL 表。它还与基于数组中结构的各种属性的查询相关联。类似于SQL查询。
我想知道 SQLITE 内存数据库是否可以取代我的结构数组,从而为我提供更好的速度和更少的代码,因为我可以重用 SQLITE 功能(例如查询),而不是自己编写函数来遍历数据结构。我希望 SQLITE 的索引能够为我带来额外的速度,而无需重新发明轮子。
但是,在我的初始测试中,SQLITE 内存数据库几乎比 C++ 数据结构慢 12-30 倍,仅用于插入。
我从这个问题中尝试了一些优化,但似乎没有多大帮助。 提高 SQLite 的每秒插入性能
我的问题是 SQLITE 能否击败 C++ 数据结构?
这是两种情况下的示例代码。
#include <iostream>
#include <map>
#include <cstdlib>
#include <ctime>
struct item_t {
unsigned int a;
unsigned int b;
bool prop1;
bool prop2;
bool prop3;
bool prop4;
bool prop5;
};
typedef std::map<unsigned int, item_t> items_t;
int main() {
srand(RAND_SEED);
items_t data;
unsigned int delta = 0x100000000 / ITERATIONS, randInt;
unsigned int count;
// Create `ITERATIONS segments with random properties
for (unsigned int i = 0, a = 0; i < ITERATIONS; ++i, a += delta) {
randInt = rand();
data[a] = {
a, a + delta - 1,
bool(randInt & 16),
bool(randInt & 8),
bool(randInt & 4),
bool(randInt & 2),
bool(randInt & 1)
};
}
printf("Inserted %d entries\n", data.size());
return 0;
}
#include <stdio.h>
#include <ctime>
#include <cstdlib>
#include <sqlite3.h>
#include <cassert>
#include <string.h>
static int sqliteExecCallback(void *unused, int count, char **data, char **columns)
{
for (int idx = 0; idx < count; idx++) {
printf("%s\n", data[idx]);
}
return 0;
}
int main() {
srand(RAND_SEED);
unsigned int delta = 0x100000000 / ITERATIONS, randInt;
sqlite3 *db;
char *zErrMsg = 0;
int rc;
rc = sqlite3_open(":memory:", &db);
if (rc) {
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
return(0);
}
rc = sqlite3_exec(
db,
"PRAGMA synchronous = OFF;"
"PRAGMA journal_mode = MEMORY",
NULL, NULL, &zErrMsg
);
if (rc) {
fprintf(stderr, "Can't set PRAGMAs: %s\n", sqlite3_errmsg(db));
return(0);
}
rc = sqlite3_exec(
db,
"CREATE TABLE IF NOT EXISTS DATA("
"a INTEGER PRIMARY KEY,"
"b INTEGER,"
"prop1 INTEGER,"
"prop2 INTEGER,"
"prop3 INTEGER,"
"prop4 INTEGER,"
"prop5 INTEGER"
");"
"CREATE INDEX DATA_prop1_idx on DATA(prop1);"
"CREATE INDEX DATA_prop2_idx on DATA(prop2);"
"CREATE INDEX DATA_prop3_idx on DATA(prop3);"
"CREATE INDEX DATA_prop4_idx on DATA(prop4);"
"CREATE INDEX DATA_prop5_idx on DATA(prop5);",
0, 0, &zErrMsg
);
if (rc) {
fprintf(stderr, "Can't execute create table: %s\n", sqlite3_errmsg(db));
return(0);
}
char sql[512] = "INSERT INTO DATA (a, b, prop1, prop2, prop3, prop4, prop5)";
int len = strlen(sql);
for (unsigned int i = 0, a = 0; i < ITERATIONS; ++i, a += delta) {
randInt = rand();
sprintf(
sql + len,
" VALUES (%u, %u, %d, %d, %d, %d, %d);",
a, a + delta - 1,
bool(randInt & 16),
bool(randInt & 8),
bool(randInt & 4),
bool(randInt & 2),
bool(randInt & 1)
);
rc = sqlite3_exec(db, sql, 0, 0, &zErrMsg);
}
rc = sqlite3_exec(db, "SELECT COUNT(*) FROM DATA", sqliteExecCallback, 0, &zErrMsg);
if (false && rc) {
fprintf(stderr, "Can't execute SELECT COUNT(*): %s\n", sqlite3_errmsg(db));
}
return 0;
}
运行
g++ -std=c++20 -O3 -march=native -DITERATIONS=2000000 -DRAND_SEED=1 sqlite.cpp -l sqlite3 > comp.log 2>&1
/bin/time -f "sqlite,%S,%U,%e,%M,%X" --append -o metrics.csv ./a.out > run.log 2>&1
g++ -std=c++20 -O3 -march=native -DITERATIONS=2000000 -DRAND_SEED=1 cpp.cpp -l sqlite3 > comp.log 2>&1
/bin/time -f "cpp,%S,%U,%e,%M,%X" --append -o metrics.csv ./a.out > run.log 2>&1
- SysTime UserTime RealTime MaxMem ExitStatus
cpp 0.09 0.62 0.71 128928.0 0.0
sqlite 0.06 25.01 25.07 196864.0 0.0
注意:我无法批量处理 SQL 查询,因为请求是外部的,但可以接受其他优化。
更新,使用
sqlite3_prepare_v2
我能够获得 8 倍的改进,并且运行时间现在已降至 3-4 秒。
它总是归结为正在执行的内容。您的代码不是最佳的,因为您的
" VALUES (%u, %u, %d, %d, %d, %d, %d);"
line 确保每个插入都会单独发生。而不是像
这样的命令insert into sometable(...)
values(...);
你会需要
insert into sometable(...)
values
(...),
(...),
(...),
...
(...);
原因很简单。您运行的每个插入都会转换为一个事务,然后该事务将作为一个事务处理,因此,如果您插入 1000 行,您将有 1000 个单独的事务需要处理,加起来,请参阅 https://sqlite .org/forum/info/f832398c19d30a4a
相反,您应该构建一个要插入的项目队列,并定期构建一批要插入的记录。您可以决定是否希望周期与时间相关(例如每分钟)和/或与大小相关(例如 100 条记录),但由于插入是此处的出血点,因此首先转换您的代码运行批量插入。
然后与C++进行比较。我对内存中 SQLite 是否会比任何其他类型的数据结构更快持怀疑态度,但你绝对可以让它比目前更快。
“内存数据库”可能有更快的原因,但这种情况很少见。
首先,最重要的是,你增加了复杂性。也许不是为了你自己,而是为了代码的执行。
现在,当您在 C++ 程序中存储数据时,您通常会查看数据以及需要执行的操作,并选择合适的数据结构来保存它。这通常会给你带来高效的处理——甚至可能非常高效,但通常不值得付出努力来榨取最后一点性能。
数据库并不神奇;如果您的 C++ 代码已经很高效,那么增加大量复杂性并不会提高它。数据库非常灵活,但这是有代价的。您的 C++ 代码不会那么灵活——您必须对每个查询进行硬编码——但它会变得简单明了。有可能,当您的 C++ 代码返回结果时,数据库仍在分析您的查询...
我不知道您的数据库引擎是否可以执行准备好的语句 - 但您是否注意到您正在为每个插入创建一个数据库命令,数据库必须首先解析,然后才能考虑如何执行实际插入?因此,您正在打印一个字符串,数据库会分析该字符串,而在此过程中,C++ 代码已经插入了数据。我们甚至还没有了解数据库表的复杂性。
如果您非常非常懒,将数据库引擎放入您的程序中可以帮助您。就像你只想将你的东西放入一个巨大的 std::vector 并循环它来查找东西;类似的东西可能会帮助您使数据库引擎看起来更好。