我正在使用C和SQL中的终端应用程序来标记文件。文件/标记关系保存在SQLite 3数据库中。到目前为止,我一直在使用静态分配的预处理语句,这些语句在每次调用包装函数时都会被重置和绑定。最近我用valgrind
测试了内存泄漏等,并且它抱怨很多,因为显然包装器中的静态sqlite3_stmt
在程序终止时没有被释放。这也会导致数据库无法正常关闭(因为未完成的语句)。
这样做的最初原因是性能。我在the C interface intro中读到,重复使用准备好的语句非常有利于提高性能。
在现有的预准备语句上使用sqlite3_reset()而不是创建新的预准备语句可以避免对sqlite3_prepare()进行不必要的调用。在许多SQL语句中,运行sqlite3_prepare()所需的时间等于或超过sqlite3_step()所需的时间。因此,避免调用sqlite3_prepare()可以显着提高性能。
以下是一些显示我正在做的事情的代码:
int tag_file(const char *file, const char *tag)
{
static sqlite3_stmt *sql_prep = NULL;
static const char *sql_str = "INSERT OR IGNORE INTO Tag VALUES (?, ?);";
// Prepare if null, else reset
prepare_or_reset(&sql_prep, sql_str);
if (sqlite3_bind_text(sql_prep, 1, file, -1, SQLITE_STATIC) != SQLITE_OK ||
sqlite3_bind_text(sql_prep, 2, tag, -1, SQLITE_STATIC) != SQLITE_OK)
return ERROR;
if (sqlite3_step(sql_prep) != SQLITE_DONE)
return ERROR;
return SUCCESS;
}
我尝试过的,它将满足valgrind
,但完全错过重用预准备语句的性能提升,使sql_prep
非静态,并在结束时调用sqlite3_finalize(sql_prep)
功能,但我想这是更糟糕的表现。我可以只用一次优雅的方式准备一次没有内存泄漏的声明吗?
我开始担心的另一件事是内存消耗。在未来,我计划为这个应用程序创建一个GUI,这将使所有这些在内存中保留更长时间。性能提升可能会更大,但该语句只是从第一次调用到退出时就位于堆上。这是公平的space-time tradoff吗?
编辑:将全部“静态”预处理语句保存在全局数组中会不会很奇怪/奇怪,我可以免费atexit
。
答案 0 :(得分:1)
你提到了两个问题。
第一个基本上是如何管理准备好的陈述。
这里似乎有必要重新设计一下int tag_file(const char *file, const char *tag)
函数。
要么将stmt维持在一个更高的级别,并且每次都将它传递给该函数。然后你可以在程序的最后清理。
另一种方法是以一种方式定义你的函数的语义,你可以给它特殊的哨兵函数(可能只是(NULL, NULL)
),这使得函数可以自由发表声明。
第二个问题是堆大小问题。只要你没有数百或数千个准备好的stmts,我认为你可以随时拥有它,因为它可能只花费几个字节。
答案 1 :(得分:1)
您必须在关闭数据库之前完成所有语句。
如果在tag_file
等函数之外不知道语句,则无法执行此操作。
你应该有一个全局的语句列表,让prepare_or_reset
函数动态地将语句添加到该列表中。
准备好的陈述很小;没有与GUI库分配的所有内容进行比较。很可能你甚至都没有注意到它们的内存使用情况。
但是,如果您不经常执行准备语句,准备时间很明显,那么准备好的语句不会提高速度。所以这两种方式都无关紧要。 ☺