Skip to content
Permalink
Browse files
Fix massive performance regression in attribute table
Follow up 56f7812

This commit fixed the ordering of features coming from the
vector layer cache for the attribute table, but came with a massive
speed impact due to the repeated calls QList::contains for
every feature fetched. For any moderately sized table or above
these calls stacked up into multiple minute delays in opening
the table.

Avoid this by tracking the added feature ids in a separate
unordered set, so that we don't need to check through the
ordered list for existing features at all.

Eg a 500k feature gpkg was taking 10 minutes to open the table.
With this optimization that's back down to 20 seconds.
  • Loading branch information
nyalldawson committed Jun 8, 2021
1 parent 1313786 commit 5fdb88bae3e40316a489d71594f17548f7a55f00
@@ -11,7 +11,6 @@




class QgsVectorLayerCache : QObject
{
%Docstring(signature="appended")
@@ -53,7 +53,7 @@ QgsCachedFeatureIterator::QgsCachedFeatureIterator( QgsVectorLayerCache *vlCache
break;

default:
mFeatureIds = mVectorLayerCache->mCacheOrderedKeys;
mFeatureIds = QList( mVectorLayerCache->mCacheOrderedKeys.begin(), mVectorLayerCache->mCacheOrderedKeys.end() );
break;
}

@@ -177,7 +177,15 @@ bool QgsVectorLayerCache::removeCachedFeature( QgsFeatureId fid )
{
bool removed = mCache.remove( fid );
if ( removed )
mCacheOrderedKeys.removeOne( fid );
{
if ( auto unorderedIt = std::find( mCacheUnorderedKeys.begin(), mCacheUnorderedKeys.end(), fid ); unorderedIt != mCacheUnorderedKeys.end() )
{
mCacheUnorderedKeys.erase( unorderedIt );

if ( auto orderedIt = std::find( mCacheOrderedKeys.begin(), mCacheOrderedKeys.end(), fid ); orderedIt != mCacheOrderedKeys.end() )
mCacheOrderedKeys.erase( orderedIt );
}
}
return removed;
}

@@ -270,7 +278,13 @@ void QgsVectorLayerCache::onJoinAttributeValueChanged( QgsFeatureId fid, int fie
void QgsVectorLayerCache::featureDeleted( QgsFeatureId fid )
{
mCache.remove( fid );
mCacheOrderedKeys.removeOne( fid );

if ( auto it = mCacheUnorderedKeys.find( fid ); it != mCacheUnorderedKeys.end() )
{
mCacheUnorderedKeys.erase( it );
if ( auto orderedIt = std::find( mCacheOrderedKeys.begin(), mCacheOrderedKeys.end(), fid ); orderedIt != mCacheOrderedKeys.end() )
mCacheOrderedKeys.erase( orderedIt );
}
}

void QgsVectorLayerCache::onFeatureAdded( QgsFeatureId fid )
@@ -330,6 +344,7 @@ void QgsVectorLayerCache::invalidate()
{
mCache.clear();
mCacheOrderedKeys.clear();
mCacheUnorderedKeys.clear();
mFullCache = false;
emit invalidated();
}
@@ -24,7 +24,8 @@
#include "qgsfield.h"
#include "qgsfeaturerequest.h"
#include "qgsfeatureiterator.h"

#include <unordered_set>
#include <deque>
#include <QCache>

class QgsVectorLayer;
@@ -393,13 +394,21 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
{
QgsCachedFeature *cachedFeature = new QgsCachedFeature( feat, this );
mCache.insert( feat.id(), cachedFeature );
if ( !mCacheOrderedKeys.contains( feat.id() ) )
mCacheOrderedKeys << feat.id();
if ( mCacheUnorderedKeys.find( feat.id() ) == mCacheUnorderedKeys.end() )
{
mCacheUnorderedKeys.insert( feat.id() );
mCacheOrderedKeys.emplace_back( feat.id() );
}
}

QgsVectorLayer *mLayer = nullptr;
QCache< QgsFeatureId, QgsCachedFeature > mCache;
QList< QgsFeatureId > mCacheOrderedKeys;

// we need two containers here. One is used for efficient tracking of the IDs which have been added to the cache, the other
// is used to store the order of the incoming feature ids, so that we can correctly iterate through features in the original order.
// the ordered list alone is far too slow to handle this -- searching for existing items in a list is magnitudes slower than the unordered_set
std::unordered_set< QgsFeatureId > mCacheUnorderedKeys;
std::deque< QgsFeatureId > mCacheOrderedKeys;

bool mCacheGeometry = true;
bool mFullCache = false;

0 comments on commit 5fdb88b

Please sign in to comment.