2014-03-03 58 views
0

我正在研究用於標記文件的C和SQL終端應用程序。文件/標籤關係保存在SQLite 3數據庫中。直到現在,我一直使用靜態分配的準備語句,這些語句在每次調用封裝函數時都會被重置和綁定。最近我用valgrind測試了內存泄漏等問題,並且這個問題非常抱怨,因爲在程序終止時,封裝器中的靜態sqlite3_stmt顯然不會被釋放。這也會導致數據庫不能正確關閉(因爲未完成的語句)。SQLite C中的靜態和非靜態準備狀態API

這樣做的最初原因是性能。我在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?會是醜陋/奇怪嗎?

回答

1
  1. 必須關閉數據庫之前完成所有語句。

    如果在tag_file之類的函數之外不知道這些語句,則這是不可能的。 您應該擁有一個全局語句列表,並讓prepare_or_reset函數動態地將該語句添加到該列表。

  2. 準備的陳述很小;沒有比較所有由GUI庫分配的東西。很可能你甚至不會注意到他們的內存使用情況。

    但是,如果您不經常執行準備語句,以至於準備時間顯而易見,則準備好的語句不會提高速度。所以這可能不重要。 ☺

1

你提到了兩個問題。

  1. 第一個基本上是如何管理你準備好的陳述。

    這裏似乎很適合重新設計你的int tag_file(const char *file, const char *tag)函數。

    要麼你保持stmt高一級,並把它傳遞給每次函數。然後你可以在程序的最後清理。

    另一種方式可以定義你的函數的語義的方式,你給它特殊的定點函數(可能只是(NULL, NULL)),使函數免費的語句。

  2. 第二個問題是堆大小問題。只要你沒有成百上千的準備好的stmts,我想你可以一直擁有它,因爲它可能只花費幾個字節。