From a04fcce54899337602c2ea2347a8cac8c9acde1c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Sat, 17 Oct 2020 17:31:19 +0200 Subject: [PATCH] Add QgsFeature::approximateMemoryUsage() --- python/core/auto_generated/qgsfeature.sip.in | 11 ++++ src/core/qgsfeature.cpp | 54 ++++++++++++++++++++ src/core/qgsfeature.h | 11 ++++ tests/src/core/testqgsfeature.cpp | 5 ++ 4 files changed, 81 insertions(+) diff --git a/python/core/auto_generated/qgsfeature.sip.in b/python/core/auto_generated/qgsfeature.sip.in index 32f0b6b9f6e..2d114f5f387 100644 --- a/python/core/auto_generated/qgsfeature.sip.in +++ b/python/core/auto_generated/qgsfeature.sip.in @@ -547,6 +547,17 @@ before this method can be used. :return: -1 if field does not exist or field map is not associated. .. seealso:: :py:func:`setFields` +%End + + int approximateMemoryUsage() const; +%Docstring +Returns the approximate RAM usage of the feature, in bytes. + +This method takes into account the size of variable elements (strings, +geometry, ...), but the value returned should be considered as a lower +bound estimation. + +.. versionadded:: 3.16 %End operator QVariant() const; diff --git a/src/core/qgsfeature.cpp b/src/core/qgsfeature.cpp index b7186bf609c..de8cc0fb6b4 100644 --- a/src/core/qgsfeature.cpp +++ b/src/core/qgsfeature.cpp @@ -18,6 +18,8 @@ email : sherman at mrcc.com #include "qgsfields.h" #include "qgsgeometry.h" #include "qgsrectangle.h" +#include "qgsfield_p.h" // for approximateMemoryUsage() +#include "qgsfields_p.h" // for approximateMemoryUsage() #include "qgsmessagelog.h" @@ -279,6 +281,58 @@ int QgsFeature::fieldNameIndex( const QString &fieldName ) const return d->fields.lookupField( fieldName ); } +static size_t qgsQStringApproximateMemoryUsage( const QString &str ) +{ + return sizeof( QString ) + str.size() * sizeof( QChar ); +} + +static size_t qgsQVariantApproximateMemoryUsage( const QVariant &v ) +{ + // A QVariant has a private structure that is a union of things whose larger + // size if a long long, and a int + size_t s = sizeof( QVariant ) + sizeof( long long ) + sizeof( int ); + if ( v.type() == QVariant::String ) + { + s += qgsQStringApproximateMemoryUsage( v.toString() ); + } + else if ( v.type() == QVariant::StringList ) + { + for ( const QString &str : v.toStringList() ) + s += qgsQStringApproximateMemoryUsage( str ); + } + else if ( v.type() == QVariant::List ) + { + for ( const QVariant &subV : v.toList() ) + s += qgsQVariantApproximateMemoryUsage( subV ); + } + return s; +} + +int QgsFeature::approximateMemoryUsage() const +{ + size_t s = sizeof( *this ) + sizeof( *d ); + + // Attributes + for ( const QVariant &attr : qgis::as_const( d->attributes ) ) + { + s += qgsQVariantApproximateMemoryUsage( attr ); + } + + // Geometry + s += sizeof( QAtomicInt ) + sizeof( void * ); // ~ sizeof(QgsGeometryPrivate) + // For simplicity we consider that the RAM usage is the one of the WKB + // representation + s += d->geometry.wkbSize(); + + // Fields + s += sizeof( QgsFieldsPrivate ); + // TODO potentially: take into account the length of the name, comment, default value, etc... + s += d->fields.size() * ( sizeof( QgsField ) + sizeof( QgsFieldPrivate ) ); + + return static_cast( s ); +} + + /*************************************************************************** * This class is considered CRITICAL and any change MUST be accompanied with * full unit tests in testqgsfeature.cpp. diff --git a/src/core/qgsfeature.h b/src/core/qgsfeature.h index 399e56277eb..b442423054f 100644 --- a/src/core/qgsfeature.h +++ b/src/core/qgsfeature.h @@ -536,6 +536,17 @@ class CORE_EXPORT QgsFeature */ int fieldNameIndex( const QString &fieldName ) const; + /** + * Returns the approximate RAM usage of the feature, in bytes. + * + * This method takes into account the size of variable elements (strings, + * geometry, ...), but the value returned should be considered as a lower + * bound estimation. + * + * \since QGIS 3.16 + */ + int approximateMemoryUsage() const; + //! Allows direct construction of QVariants from features. operator QVariant() const { diff --git a/tests/src/core/testqgsfeature.cpp b/tests/src/core/testqgsfeature.cpp index 035432a65dd..961fa26c3ad 100644 --- a/tests/src/core/testqgsfeature.cpp +++ b/tests/src/core/testqgsfeature.cpp @@ -124,6 +124,7 @@ void TestQgsFeature::attributesTest() void TestQgsFeature::constructorTest() { QgsFeature f; + QVERIFY( f.approximateMemoryUsage() > 0 ); QVERIFY( FID_IS_NULL( f.id() ) ); QgsFeature f2 { QgsFields() }; QVERIFY( FID_IS_NULL( f2.id() ) ); @@ -222,6 +223,7 @@ void TestQgsFeature::attributes() feature.setAttributes( mAttrs ); QCOMPARE( feature.attributes(), mAttrs ); QCOMPARE( feature.attributes(), mAttrs ); + QVERIFY( feature.approximateMemoryUsage() > QgsFeature().approximateMemoryUsage() ); //test implicit sharing detachment QgsFeature copy( feature ); @@ -286,6 +288,7 @@ void TestQgsFeature::geometry() //test no double delete of geometry when setting: feature.setGeometry( QgsGeometry( mGeometry2 ) ); QVERIFY( feature.hasGeometry() ); + QVERIFY( feature.approximateMemoryUsage() > QgsFeature().approximateMemoryUsage() ); feature.setGeometry( QgsGeometry( mGeometry ) ); QCOMPARE( feature.geometry().asWkb(), mGeometry.asWkb() ); @@ -355,6 +358,8 @@ void TestQgsFeature::fields() QVERIFY( original.fields().isEmpty() ); original.setFields( mFields ); QCOMPARE( original.fields(), mFields ); + QVERIFY( original.approximateMemoryUsage() > QgsFeature().approximateMemoryUsage() ); + QgsFeature copy( original ); QCOMPARE( copy.fields(), original.fields() );