Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: sql/connection.cc

Issue 1349863003: [sql] Use memory-mapped I/O for sql::Connection. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sql/connection.h ('k') | sql/connection_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
}
« no previous file with comments | « sql/connection.h ('k') | sql/connection_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698