Chromium Code Reviews| Index: sql/connection.cc |
| diff --git a/sql/connection.cc b/sql/connection.cc |
| index 3f37b90cc71fd059b4eef51ad6a79d47608a1b4b..bbca3afbf90af9095b1c7ff5206c6f0982e7c7a5 100644 |
| --- a/sql/connection.cc |
| +++ b/sql/connection.cc |
| @@ -466,6 +466,74 @@ void Connection::Preload() { |
| } |
| } |
| +// SQLite keeps unused pages associated with a connection in a cache. It asks |
| +// the cache for pages by an id, and if the page is present and the database is |
| +// unchanged, it considers the content of the page valid and doesn't read it |
| +// from disk. When memory-mapped I/O is enabled, on read SQLite uses page |
| +// structures created from the memory map data before consulting the cache. On |
| +// write SQLite creates a new in-memory page structure, copies the data from the |
| +// memory map, and later writes it, releasing the updated page back to the |
| +// cache. |
| +// |
| +// This means that in memory-mapped mode, the contents of the cached pages are |
| +// not re-used for reads, but they are re-used for writes if the re-written page |
| +// is still in the cache. The implementation of sqlite3_db_release_memory() as |
| +// of SQLite 3.8.7.4 frees all pages from pcaches associated with the |
| +// connection, so it should free these pages. |
| +// |
| +// Unfortunately, the zero page is also freed. That page is never accessed |
| +// using memory-mapped I/O, and the cached copy can be re-used after verifying |
| +// the file change counter on disk. Also, fresh pages from cache receive some |
| +// pager-level initialization before they can be used. Since the information |
| +// involved will immediately be accessed in various ways, it is unclear if the |
| +// additional overhead is material, or just moving processor cache effects |
| +// around. |
|
Scott Hess - ex-Googler
2015/09/16 22:46:16
Apologies for the mega-comment. I'm not entirely
|
| +// |
| +// TODO(shess): It would be better to release the pages immediately when they |
| +// are no longer needed. This would basically happen after SQLite commits a |
| +// transaction. I had implemented a pcache wrapper to do this, but it involved |
| +// layering violations, and it had to be setup before any other sqlite call, |
| +// which was brittle. Also, for large files it would actually make sense to |
| +// maintain the existing pcache behavior for blocks past the memory-mapped |
| +// segment. I think drh would accept a reasonable implementation of the overall |
| +// concept for upstreaming to SQLite core. |
| +// |
| +// TODO(shess): Another possibility would be to set the cache size small, which |
| +// would keep the zero page around, plus some pre-initialized pages, and SQLite |
| +// can manage things. The downside is that updates larger than the cache would |
| +// spill to the journal. That could be compensated by setting cache_spill to |
| +// false. The downside then is that it allows open-ended use of memory for |
| +// large transactions. |
| +// |
| +// TODO(shess): The TrimMemory() trick of bouncing the cache size would also |
| +// work. There could be two prepared statements, one for cache_size=1 one for |
| +// cache_size=goal. |
| +void Connection::ReleaseCacheMemoryIfNeeded(bool assume_changed) { |
| + // If memory-mapping is not enabled, the page cache helps performance. |
| + if (!mmap_enabled_) |
| + return; |
| + |
| + // On caller request, force the change comparison to fail. Done before the |
| + // transaction-nesting test so that the signal can carry to transaction |
| + // commit. |
| + if (assume_changed) |
| + total_changes_--; |
| + |
| + // Cached pages may be re-used within the same transaction. |
| + if (transaction_nesting()) |
| + return; |
| + |
| + // If no changes have been made, skip flushing. This allows the first page of |
| + // the database to remain in cache across multiple reads. |
| + int current_changes = sqlite3_total_changes(db_); |
| + if (current_changes == total_changes_) |
| + return; |
| + |
| + total_changes_ = current_changes; |
| + sqlite3_db_release_memory(db_); |
| +} |
| + |
| +// NOTE(shess): When memory-mapped mode is solid, this will be a noop. |
| void Connection::TrimMemory(bool aggressively) { |
| if (!db_) |
| return; |
| @@ -676,6 +744,8 @@ bool Connection::Delete(const base::FilePath& path) { |
| std::string wal_str = AsUTF8ForSQL(wal_path); |
| std::string path_str = AsUTF8ForSQL(path); |
| + InitializeSqlite(); |
| + |
| sqlite3_vfs* vfs = sqlite3_vfs_find(NULL); |
| CHECK(vfs); |
| CHECK(vfs->xDelete); |
| @@ -773,6 +843,9 @@ bool Connection::CommitTransaction() { |
| RecordCommitTime(delta); |
| RecordOneEvent(EVENT_COMMIT); |
| + // Release dirty cache pages after the transaction closes. |
| + ReleaseCacheMemoryIfNeeded(false); |
| + |
| return ret; |
| } |
| @@ -863,6 +936,12 @@ int Connection::ExecuteAndReturnErrorCode(const char* sql) { |
| const base::TimeDelta delta = Now() - before; |
| RecordTimeAndChanges(delta, read_only); |
| } |
| + |
| + // Most calls to Execute() modify the database. The main exceptions would be |
| + // calls such as CREATE TABLE IF NOT EXISTS which could modify the database |
| + // but sometimes don't. |
| + ReleaseCacheMemoryIfNeeded(true); |
| + |
| return rc; |
| } |
| @@ -1227,6 +1306,17 @@ bool Connection::OpenInternal(const std::string& file_name, |
| // secure_delete. |
| ignore_result(Execute("PRAGMA journal_mode = TRUNCATE")); |
| + // Enable memory-mapped access. This value will be capped by |
| + // SQLITE_MAX_MMAP_SIZE, which could be different between 32-bit and 64-bit |
| + // platforms. |
| + mmap_enabled_ = false; |
| + ignore_result(Execute("PRAGMA mmap_size = 2147483648")); // 2GB. |
| + { |
| + Statement s(GetUniqueStatement("PRAGMA mmap_size")); |
| + if (s.Step() && s.ColumnInt64(0) > 0) |
| + mmap_enabled_ = true; |
| + } |
| + |
| const base::TimeDelta kBusyTimeout = |
| base::TimeDelta::FromSeconds(kBusyTimeoutSeconds); |
| @@ -1270,6 +1360,9 @@ void Connection::DoRollback() { |
| RecordUpdateTime(delta); |
| RecordOneEvent(EVENT_ROLLBACK); |
| + // The cache may have been accumulating dirty pages for commit. |
| + ReleaseCacheMemoryIfNeeded(false); |
| + |
| needs_rollback_ = false; |
| } |