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

Unified Diff: chrome/browser/sync/syncable/syncable.cc

Issue 6588119: First-time sync: asymptotic running time improvement (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/chrome/Release
Patch Set: Fix unit_tests Created 9 years, 10 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 | « chrome/browser/sync/syncable/syncable.h ('k') | chrome/browser/sync/syncable/syncable_id.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/sync/syncable/syncable.cc
diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc
index 2a8a83e5773ee3e055b9b8d46221d0d952ce50ab..17e64cc6b51b18b57cc52bf26f14ee47b364d0bc 100644
--- a/chrome/browser/sync/syncable/syncable.cc
+++ b/chrome/browser/sync/syncable/syncable.cc
@@ -126,6 +126,15 @@ class ScopedIndexUpdater {
typename Index<Indexer>::Set* const index_;
};
+// Helper function to add an item to the index, if it ought to be added.
+template<typename Indexer>
+void InitializeIndexEntry(EntryKernel* entry,
+ typename Index<Indexer>::Set* index) {
+ if (Indexer::ShouldInclude(entry)) {
+ index->insert(entry);
+ }
+}
+
} // namespace
///////////////////////////////////////////////////////////////////////////
@@ -136,22 +145,27 @@ bool ClientTagIndexer::ShouldInclude(const EntryKernel* a) {
return !a->ref(UNIQUE_CLIENT_TAG).empty();
}
-class ParentIdAndHandleIndexer::Comparator {
- public:
- bool operator() (const syncable::EntryKernel* a,
- const syncable::EntryKernel* b) const {
- int cmp = a->ref(PARENT_ID).compare(b->ref(PARENT_ID));
- if (cmp != 0)
- return cmp < 0;
+bool ParentIdAndHandleIndexer::Comparator::operator() (
+ const syncable::EntryKernel* a,
+ const syncable::EntryKernel* b) const {
+ int cmp = a->ref(PARENT_ID).compare(b->ref(PARENT_ID));
+ if (cmp != 0)
+ return cmp < 0;
- // Meta handles are immutable per entry so this is ideal.
- return a->ref(META_HANDLE) < b->ref(META_HANDLE);
- }
-};
+ int64 a_position = a->ref(SERVER_POSITION_IN_PARENT);
+ int64 b_position = b->ref(SERVER_POSITION_IN_PARENT);
+ if (a_position != b_position)
+ return a_position < b_position;
+
+ cmp = a->ref(ID).compare(b->ref(ID));
+ return cmp < 0;
+}
// static
bool ParentIdAndHandleIndexer::ShouldInclude(const EntryKernel* a) {
- return !a->ref(IS_DEL);
+ // This index excludes deleted items and the root item. The root
+ // item is excluded so that it doesn't show up as a child of itself.
+ return !a->ref(IS_DEL) && !a->ref(ID).IsRoot();
}
///////////////////////////////////////////////////////////////////////////
@@ -263,12 +277,10 @@ void Directory::InitializeIndices() {
MetahandlesIndex::iterator it = kernel_->metahandles_index->begin();
for (; it != kernel_->metahandles_index->end(); ++it) {
EntryKernel* entry = *it;
- if (!entry->ref(IS_DEL))
- kernel_->parent_id_child_index->insert(entry);
- kernel_->ids_index->insert(entry);
- if (!entry->ref(UNIQUE_CLIENT_TAG).empty()) {
- kernel_->client_tag_index->insert(entry);
- }
+ InitializeIndexEntry<ParentIdAndHandleIndexer>(entry,
+ kernel_->parent_id_child_index);
+ InitializeIndexEntry<IdIndexer>(entry, kernel_->ids_index);
+ InitializeIndexEntry<ClientTagIndexer>(entry, kernel_->client_tag_index);
if (entry->ref(IS_UNSYNCED))
kernel_->unsynced_metahandles->insert(entry->ref(META_HANDLE));
if (entry->ref(IS_UNAPPLIED_UPDATE))
@@ -381,62 +393,17 @@ EntryKernel* Directory::GetEntryByHandle(int64 metahandle,
return NULL;
}
-// An interface to specify the details of which children
-// GetChildHandles() is looking for.
-// TODO(chron): Clean this up into one function to get child handles
-struct PathMatcher {
- explicit PathMatcher(const Id& parent_id) : parent_id_(parent_id) { }
- virtual ~PathMatcher() { }
-
- typedef Directory::ParentIdChildIndex Index;
- virtual Index::iterator lower_bound(Index* index) = 0;
- virtual Index::iterator upper_bound(Index* index) = 0;
- const Id parent_id_;
- EntryKernel needle_;
-};
-
-// Matches all children.
-// TODO(chron): Unit test this by itself
-struct AllPathsMatcher : public PathMatcher {
- explicit AllPathsMatcher(const Id& parent_id) : PathMatcher(parent_id) {
- }
-
- virtual Index::iterator lower_bound(Index* index) {
- needle_.put(PARENT_ID, parent_id_);
- needle_.put(META_HANDLE, std::numeric_limits<int64>::min());
- return index->lower_bound(&needle_);
- }
-
- virtual Index::iterator upper_bound(Index* index) {
- needle_.put(PARENT_ID, parent_id_);
- needle_.put(META_HANDLE, std::numeric_limits<int64>::max());
- return index->upper_bound(&needle_);
- }
-};
-
void Directory::GetChildHandles(BaseTransaction* trans, const Id& parent_id,
Directory::ChildHandles* result) {
- AllPathsMatcher matcher(parent_id);
- return GetChildHandlesImpl(trans, parent_id, &matcher, result);
-}
-
-void Directory::GetChildHandlesImpl(BaseTransaction* trans, const Id& parent_id,
- PathMatcher* matcher,
- Directory::ChildHandles* result) {
CHECK(this == trans->directory());
result->clear();
{
ScopedKernelLock lock(this);
- // This index is sorted by parent id and metahandle.
- ParentIdChildIndex* const index = kernel_->parent_id_child_index;
typedef ParentIdChildIndex::iterator iterator;
- for (iterator i = matcher->lower_bound(index),
- end = matcher->upper_bound(index); i != end; ++i) {
- // root's parent_id is NULL in the db but 0 in memory, so
- // have avoid listing the root as its own child.
- if ((*i)->ref(ID) == (*i)->ref(PARENT_ID))
- continue;
+ for (iterator i = GetParentChildIndexLowerBound(lock, parent_id),
+ end = GetParentChildIndexUpperBound(lock, parent_id);
+ i != end; ++i) {
result->push_back((*i)->ref(META_HANDLE));
}
}
@@ -489,6 +456,8 @@ bool Directory::ReindexId(EntryKernel* const entry, const Id& new_id) {
{
// Update the indices that depend on the ID field.
ScopedIndexUpdater<IdIndexer> updater_a(lock, entry, kernel_->ids_index);
+ ScopedIndexUpdater<ParentIdAndHandleIndexer> updater_b(lock, entry,
+ kernel_->parent_id_child_index);
entry->put(ID, new_id);
}
return true;
@@ -1234,6 +1203,10 @@ Directory* Entry::dir() const {
return basetrans_->directory();
}
+Id Entry::ComputePrevIdFromServerPosition(const Id& parent_id) const {
+ return dir()->ComputePrevIdFromServerPosition(kernel_, parent_id);
+}
+
const string& Entry::Get(StringField field) const {
DCHECK(kernel_);
return kernel_->ref(field);
@@ -1372,13 +1345,21 @@ bool MutableEntry::PutIsDel(bool is_del) {
if (!is_del)
PutPredecessor(Id()); // Restores position to the 0th index.
+
return true;
}
bool MutableEntry::Put(Int64Field field, const int64& value) {
DCHECK(kernel_);
if (kernel_->ref(field) != value) {
- kernel_->put(field, value);
+ ScopedKernelLock lock(dir());
+ if (SERVER_POSITION_IN_PARENT == field) {
+ ScopedIndexUpdater<ParentIdAndHandleIndexer> updater(lock, kernel_,
+ dir()->kernel_->parent_id_child_index);
+ kernel_->put(field, value);
+ } else {
+ kernel_->put(field, value);
+ }
kernel_->mark_dirty(dir()->kernel_->dirty_metahandles);
}
return true;
@@ -1606,34 +1587,109 @@ Id Directory::NextId() {
return Id::CreateFromClientString(base::Int64ToString(result));
}
-Id Directory::GetChildWithNullIdField(IdField field,
- BaseTransaction* trans,
- const Id& parent_id) {
- // This query is O(number of children), which should be acceptable
- // when this method is used as the first step in enumerating the children of
- // a node. But careless use otherwise could potentially result in
- // O((number of children)^2) performance.
- ChildHandles child_handles;
- GetChildHandles(trans, parent_id, &child_handles);
- ChildHandles::const_iterator it;
- for (it = child_handles.begin(); it != child_handles.end(); ++it) {
- Entry e(trans, GET_BY_HANDLE, *it);
- CHECK(e.good());
- if (e.Get(field).IsRoot())
- return e.Get(ID);
- }
-
- return Id();
-}
-
Id Directory::GetFirstChildId(BaseTransaction* trans,
const Id& parent_id) {
- return GetChildWithNullIdField(PREV_ID, trans, parent_id);
+ ScopedKernelLock lock(this);
+ // We can use the server positional ordering as a hint because it's generally
+ // in sync with the local (linked-list) positional ordering, and we have an
+ // index on it.
+ ParentIdChildIndex::iterator candidate =
+ GetParentChildIndexLowerBound(lock, parent_id);
+ ParentIdChildIndex::iterator end_range =
+ GetParentChildIndexUpperBound(lock, parent_id);
+ for (; candidate != end_range; ++candidate) {
+ EntryKernel* entry = *candidate;
+ // Filter out self-looped items, which are temporarily not in the child
+ // ordering.
+ if (entry->ref(PREV_ID).IsRoot() ||
+ entry->ref(PREV_ID) != entry->ref(NEXT_ID)) {
+ // Walk to the front of the list; the server position ordering
+ // is commonly identical to the linked-list ordering, but pending
+ // unsynced or unapplied items may diverge.
+ while (!entry->ref(PREV_ID).IsRoot()) {
+ entry = GetEntryById(entry->ref(PREV_ID), &lock);
+ }
+ return entry->ref(ID);
+ }
+ }
+ // There were no children in the linked list.
+ return Id();
}
Id Directory::GetLastChildId(BaseTransaction* trans,
const Id& parent_id) {
- return GetChildWithNullIdField(NEXT_ID, trans, parent_id);
+ ScopedKernelLock lock(this);
+ // We can use the server positional ordering as a hint because it's generally
+ // in sync with the local (linked-list) positional ordering, and we have an
+ // index on it.
+ ParentIdChildIndex::iterator begin_range =
+ GetParentChildIndexLowerBound(lock, parent_id);
+ ParentIdChildIndex::iterator candidate =
+ GetParentChildIndexUpperBound(lock, parent_id);
Nicolas Zea 2011/03/02 22:58:02 The first candidate will actually have parent_id+1
ncarter (slow) 2011/03/02 23:58:16 It's subtle, but I think it's okay. The pattern u
+
+ while (begin_range != candidate) {
+ --candidate;
+ EntryKernel* entry = *candidate;
+
+ // Filter out self-looped items, which are temporarily not in the child
+ // ordering.
+ if (entry->ref(NEXT_ID).IsRoot() ||
+ entry->ref(NEXT_ID) != entry->ref(PREV_ID)) {
+ // Walk to the back of the list; the server position ordering
+ // is commonly identical to the linked-list ordering, but pending
+ // unsynced or unapplied items may diverge.
+ while (!entry->ref(NEXT_ID).IsRoot())
+ entry = GetEntryById(entry->ref(NEXT_ID), &lock);
+ return entry->ref(ID);
+ }
+ }
+ // There were no children in the linked list.
+ return Id();
+}
+
+Id Directory::ComputePrevIdFromServerPosition(
+ const EntryKernel* entry,
+ const syncable::Id& parent_id) {
+ ScopedKernelLock lock(this);
+
+ // Find the natural insertion point in the parent_id_child_index, and
+ // work back from there, filtering out ineligible candidates.
+ ParentIdChildIndex::iterator sibling = LocateInParentChildIndex(lock,
+ parent_id, entry->ref(SERVER_POSITION_IN_PARENT), entry->ref(ID));
+ ParentIdChildIndex::iterator first_sibling =
+ GetParentChildIndexLowerBound(lock, parent_id);
+
+ while (sibling != first_sibling) {
Nicolas Zea 2011/03/02 22:58:02 I think this prevents the possibility of having th
ncarter (slow) 2011/03/02 23:58:16 I think it works okay for the same reason as the p
+ --sibling;
+ EntryKernel* candidate = *sibling;
+
+ // The item itself should never be in the range under consideration.
+ DCHECK_NE(candidate->ref(META_HANDLE), entry->ref(META_HANDLE));
+
+ // Ignore unapplied updates -- they might not even be server-siblings.
+ if (candidate->ref(IS_UNAPPLIED_UPDATE))
+ continue;
+
+ // We can't trust the SERVER_ fields of unsynced items, but they are
+ // potentially legitimate local predecessors. In the case where
+ // |update_item| and an unsynced item wind up in the same insertion
+ // position, we need to choose how to order them. The following check puts
+ // the unapplied update first; removing it would put the unsynced item(s)
+ // first.
+ if (candidate->ref(IS_UNSYNCED))
+ continue;
+
+ // Skip over self-looped items, which are not valid predecessors. This
+ // shouldn't happen in practice, but is worth defending against.
+ if (candidate->ref(PREV_ID) == candidate->ref(NEXT_ID) &&
+ !candidate->ref(PREV_ID).IsRoot()) {
+ NOTREACHED();
+ continue;
+ }
+ return candidate->ref(ID);
+ }
+ // This item will be the first in the sibling order.
+ return Id();
}
bool IsLegalNewParent(BaseTransaction* trans, const Id& entry_id,
@@ -1701,4 +1757,35 @@ std::ostream& operator<<(std::ostream& s, const Blob& blob) {
return s << std::dec;
}
+Directory::ParentIdChildIndex::iterator Directory::LocateInParentChildIndex(
+ const ScopedKernelLock& lock,
+ const Id& parent_id,
+ int64 position_in_parent,
+ const Id& item_id_for_tiebreaking) {
+ kernel_->needle.put(PARENT_ID, parent_id);
+ kernel_->needle.put(SERVER_POSITION_IN_PARENT, position_in_parent);
+ kernel_->needle.put(ID, item_id_for_tiebreaking);
+ return kernel_->parent_id_child_index->lower_bound(&kernel_->needle);
+}
+
+Directory::ParentIdChildIndex::iterator
+Directory::GetParentChildIndexLowerBound(const ScopedKernelLock& lock,
+ const Id& parent_id) {
+ // Peg the parent ID, and use the least values for the remaining
+ // index variables.
+ return LocateInParentChildIndex(lock, parent_id,
+ std::numeric_limits<int64>::min(),
+ Id::GetLeastIdForLexicographicComparison());
+}
+
+
+Directory::ParentIdChildIndex::iterator
+Directory::GetParentChildIndexUpperBound(const ScopedKernelLock& lock,
+ const Id& parent_id) {
+ // The upper bound of |parent_id|'s range is the lower
+ // bound of |++parent_id|'s range.
+ return GetParentChildIndexLowerBound(lock,
+ parent_id.GetLexicographicSuccessor());
+}
+
} // namespace syncable
« no previous file with comments | « chrome/browser/sync/syncable/syncable.h ('k') | chrome/browser/sync/syncable/syncable_id.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698