基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化(二)

简介: 基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化

3 Geadatebse API使用最佳实践


本主题讨论如何使用Geodatabase应用程序编程接口(API)中的某些组件,以优化性能、防止数据损坏和避免意外行为。伴随着对每个最佳实践的描述的是显示不正确或正确模式的代码示例。


本主题旨在成为使用Geodatabase API的开发人员的“备忘单”。有许多可以提高性能的最佳实践,也有许多可能损害性能或导致意外结果的常见错误。本主题中的信息是两者的组合,并基于支持事件、论坛帖子和其他第三方代码中的代码示例。


本主题中的代码示例用于说明周围段落中的文本,而不仅仅是提供可复制并粘贴到应用程序中的代码。在某些情况下,代码用于说明要避免的编程模式。在复制和粘贴本主题中的任何代码之前,请从代码的周围段落文本中确保它是要使用的实践示例,而不是要避免的示例。


3.1 对recycling的理解


回收是游标的一个属性,决定如何创建游标中的行。可以启用或禁用回收,并通过API在多个游标实例化方法(包括ITable.Search和ISelectionSet.Search)上显示为布尔参数。如果启用了回收,则无论从游标返回多少行,游标只为一行分配内存。这在内存使用和运行时间方面都提供了性能优势,但对于某些工作流来说有缺点。在任何时候只引用一行的情况下,回收都很有用,例如,绘制几何图形或将当前行的ObjectID显示到控制台窗口。当需要以某种方式比较光标中的多行时,或者当正在编辑行时,请避免循环使用。


Recycling is a property of cursors that determines how rows from the cursor are created. Recycling can be enabled or disabled and is exposed through the API as a Boolean parameter on several cursor instantiation methods, including ITable.Search and ISelectionSet.Search.  If recycling is enabled, a cursor only allocates memory for a single row regardless of how many rows are returned from the cursor. This provides performance benefits in terms of both memory usage and running time, but has drawbacks for certain workflows. Recycling is useful in situations where only a single row is going to be referenced at any time, for example, drawing geometries or displaying the current row's ObjectID to a console window. When multiple rows from a cursor need to be compared in some way, or when rows are being edited, avoid recycling. Consider the following code example that compares the first two geometries from a feature cursor to see if they are equal:


考虑下面的代码示例,该示例比较要素光标的前两个几何图形,以查看它们是否相等:[要避免使用的示例]


public static void RecyclingInappropriateExample(IFeatureClass featureClass, Boolean
    enableRecycling)
{
    using(ComReleaser comReleaser = new ComReleaser())
    {
        // Create a search cursor.
        IFeatureCursor featureCursor = featureClass.Search(null, enableRecycling);
        comReleaser.ManageLifetime(featureCursor);
        // Get the first two geometries and see if they intersect.
        IFeature feature1 = featureCursor.NextFeature();
        IFeature feature2 = featureCursor.NextFeature();
        IRelationalOperator relationalOperator = (IRelationalOperator)feature1.Shape;
        Boolean geometriesEqual = relationalOperator.Equals(feature2.Shape);
        Console.WriteLine("Geometries are equal: {0}", geometriesEqual);
    }
}

如果启用回收,则前面的代码始终返回true,因为 feature1和feature2引用将指向同一对象,第二个NextFeature调用不创建行,它将覆盖现有行的值。调用IRelationalOperator.Equals是将几何体与自身进行比较。出于同样的原因,“两个”特性的ObjectID或属性值之间的任何比较也表示相等。禁用回收是一种更为谨慎的方法,因为不恰当地使用非回收游标不太可能返回与上一个代码示例中的结果相同的意外结果,但它可能会对性能造成很大的影响。


恰当案例


下面的代码示例将打开要素类上的搜索光标,并查找每个要素的面积之和。由于这不会引用任何先前获取的行,因此这是回收游标的理想候选:

public static void RecyclingAppropriateExample(IFeatureClass featureClass, Boolean
    enableRecycling)
{
    using(ComReleaser comReleaser = new ComReleaser())
    {
        // Create a search cursor.
        IFeatureCursor featureCursor = featureClass.Search(null, enableRecycling);
        comReleaser.ManageLifetime(featureCursor);
        // Create a sum of each geometry's area.
        IFeature feature = null;
        double totalShapeArea = 0;
        while ((feature = featureCursor.NextFeature()) != null)
        {
            IArea shapeArea = (IArea)feature.Shape;
            totalShapeArea += shapeArea.Area;
        }
        Console.WriteLine("Total shape area: {0}", totalShapeArea);
    }
}

前面的代码示例在geodatabase文件中的一个要素类上进行了测试,该文件包含大约500000个要素,结果如下(在下面,~表示大约):在启用回收的情况下,流程的工作集增加了约4%,而在禁用回收的情况下则增加了约48%。在禁用回收的情况下,该方法的运行时间大约是原来的2.25倍。其他类似的工作流在不恰当地使用非循环游标时可能会导致更大的差异,例如工作集增加了近250%,执行时间比启用循环时长12倍。【The preceding code example was tested on a feature class in a file geodatabase containing approximately 500,000 features with the following results (in the following, ~ indicates approximately): (1). The process's working set increased by ~4 percent with recycling enabled as opposed to ~48 percent with recycling disabled. (2). With recycling disabled, the method took ~2.25 times as long to run.Other similar workflows can result in an even more dramatic difference when inappropriately using non-recycling cursors, such as a working set increase of nearly 250 percent and an execution time 12 times longer than with recycling enabled】


参数Recycling为True的时候理解为传引用,为False的时候理解为传值。因此在应用的时候应该注意的“传值”和“传址”的差异。

我们知道引用传递(“传址”)效率较高,因此在绘画要素的时候可以采用True参数。但当要进行遍历后将Feature的Geometry加入某个集合或插入到其他FeatureClass的时候,必须使用传值调用,即参数为False,否则我们加入的都是最后一个变量中的Geometry(因为传地址嘛)。


3.2 存储FindField的结果


方法(如IClass.FindField和IFields.FindField)用于根据字段的名称检索字段在数据集或字段集合中的位置。依赖FindField而不是硬编码字段位置是一个好的做法,但是过度使用FindField可能会影响性能。【Methods, such as IClass.FindField and IFields.FindField are used to retrieve the position of a field in a dataset or a fields collection based on its name. Relying on FindField as opposed to hard-coded field positions is a good practice but overusing FindField can hinder performance】


考虑下面的代码示例,其中“NAME”属性是从游标的功能中检索的:【不当使用】


public static void ExcessiveFindFieldCalls(IFeatureClass featureClass)
{
    using(ComReleaser comReleaser = new ComReleaser())
    {
        // Open a cursor on the feature class.
        IFeatureCursor featureCursor = featureClass.Search(null, true);
        comReleaser.ManageLifetime(featureCursor);
        // Display the NAME value from each feature.
        IFeature feature = null;
        while ((feature = featureCursor.NextFeature()) != null)
        {
            Console.WriteLine(feature.get_Value(featureClass.FindField("NAME")));
        }
    }
}

尽管FindField调用不是一个昂贵的操作,但是当涉及大量要素时,成本会增加。更改代码以重用FindField结果通常会将性能提高3-10%(在某些情况下甚至更高),而且不需要太多努力。


下列示例显示了一个较好的示例(检查FindField的值不为-1);如果返回为-1,说明没有找到;如果-1的值被用作value属性的参数(C#中的get_value和set_value方法),则不会返回描述性错误消息,因为value无法知道客户端要访问哪个字段。


The following code example shows an additional good practice (checking that FindField values are not –1). If a field cannot be found, FindField returns –1. If a value of –1 is then used as a parameter for the Value property (the get_Value and set_Value methods in C#), a descriptive error message is not returned, as Value has no way of knowing what field the client intended to access.


正确做法:【先索引再取值】

public static void SingleFindFieldCall(IFeatureClass featureClass)
{
    using(ComReleaser comReleaser = new ComReleaser())
    {
        // Open a cursor on the feature class.
        IFeatureCursor featureCursor = featureClass.Search(null, true);
        comReleaser.ManageLifetime(featureCursor);
        // Display the NAME value from each feature.
        IFeature feature = null;
        int nameIndex = featureClass.FindField("NAME");
        // Make sure the FindField result is valid.
        if (nameIndex ==  - 1)
        {
            throw new ArgumentException("The NAME field could not be found.");
        }
        while ((feature = featureCursor.NextFeature()) != null)
        {
            Console.WriteLine(feature.get_Value(nameIndex));
        }
    }
}

说白了,就我们先得到字段的索引,如果索引为-1,说明字段不存在;如果不为-1,我们再利用索引获取对应字段的值

3.3 DDL命令不能在编辑时使用

数据定义语言(DDL)命令是修改数据库架构的数据库命令。示例包括创建表、向表中添加新字段或删除索引。触发DDL命令的方法(如IFeatureWorkspace.CreateTable或IClass.AddField)不应在编辑会话中调用,因为DDL命令将提交当前打开的任何事务,因此在发生错误时无法回滚任何不需要的编辑。此实践还扩展到geodatabase模式修改,从数据库的角度来看,这不是真正的DDL,例如修改域,因为这些类型的操作显式提交其更改。这方面的一个实际示例是自定义编辑应用程序,该应用程序根据用户的编辑向编码值域添加新值,然后在应用程序尝试提交编辑时意外失败。在这种情况下,方法是维护用户提供的值列表,然后在编辑会话停止后添加这些值。


Data definition language (DDL) commands are database commands that modify the schema of a database. Examples include creating tables, adding a new field to a table, or dropping an index. Methods that trigger DDL commands, such as IFeatureWorkspace.CreateTable or IClass.AddField, should never be called inside an edit session, because DDL commands will commit any transactions that are currently open, making it impossible to rollback any unwanted edits if an error occurs.


This practice also extends to geodatabase schema modification that is not true DDL from a database perspective—such as modifying a domain—because these types of operations explicitly commit their changes. A real-world example of this is a custom editing application that adds new values to a coded value domain based on a user's edits, then fails unexpectedly when the application tries to commit the edits. The approach in cases like these is to maintain a list of values that the user has provided, then add them once the edit session has been stopped.


3.4 Calling Store inside of Store-triggered events


Geodatabase API公开了几个事件,这些事件允许开发人员在对方法调用存储时应用自定义行为,例如IObjectClassEvents.OnCreate和IRelatedObjectClassEvents.RelatedObjectCreated。实现通过这些方法定义自定义行为的类扩展或事件处理程序的开发人员,以及其他类似的开发人员,应确保不会在触发事件的行上再次调用存储,即使自定义行为导致了行的修改。再次调用对象上的存储将从模型中触发事件模型,从而导致意外行为。在某些情况下,这会导致无限递归,导致应用程序挂起,而在其他情况下,错误将随可能难以解释的消息返回。


The Geodatabase API exposes several events that allow developers to apply custom behavior when Store is called on a method, such as IObjectClassEvents.OnCreate and IRelatedObjectClassEvents.RelatedObjectCreated. Developers implementing class extensions or event handlers that define custom behavior through these methods, and others like them, should ensure that Store is not called again on the row that triggered the event, even if the custom behavior caused the row to be modified. Calling Store on the object again triggers the event model from within the model, leading to unexpected behavior. In some cases, this results in infinite recursion causing an application to hang, while in others, errors are returned with messages that might be difficult to interpret.


下面的代码示例显示了一个简单的“时间戳”示例,该示例旨在创建要素时保持当前用户的名称,但会根据数据源产生不同种类的错误:


The following code example shows a simple "timestamp" example that is intended to maintain the current user's name on features being created, but produces different varieties of errors depending on the data source:不要这么干

private static void EventHandlerInitialization(IFeatureClass featureClass)
{
    IObjectClassEvents_Event objectClassEvents = (IObjectClassEvents_Event)
        featureClass;
    objectClassEvents.OnCreate += new IObjectClassEvents_OnCreateEventHandler
        (OnCreateHandler);
}
private static void OnCreateHandler(IObject obj)
{
    obj.set_Value(NAME_INDEX, Environment.UserName);
    obj.Store(); // Do not do this!
}

3.5 获取要素

IFeatureClass接口公开了两个类似的方法GetFeature和GetFeatures,用于按对象ID检索功能。前者检索单个特征并获取一个整数参数,而后者创建一个游标,该游标返回整数数组参数中指定的要素(它还具有一个参数,该参数指定游标是否将被循环使用)。出于性能目的,每当使用已知ObjectID检索多个功能时,请始终使用GetFeatures方法。


The IFeatureClass interface exposes two similar methods—GetFeature and GetFeatures—for retrieving features by their ObjectIDs. The former retrieves a single feature and takes an integer parameter, while the latter creates a cursor that returns the features specified in an integer array parameter (it also has a parameter that specifies whether the cursor will be recycling).For performance purposes, anytime more than one feature is being retrieved using a known ObjectID, always use the GetFeatures method. Compare the following two code examples:IFeatureClass.GetFeatures uses a conformant array parameter that makes it unsafe for use in .NET; IGeoDatabaseBridge.GetFeatures provides the same functionality in an interop-safe manner.


比较以下两个代码示例:IFeatureClass.GetFeatures使用一致数组参数,这使得在.NET中使用该参数不安全;IGeoDatabaseBridge.GetFeatures以互操作安全的方式提供相同的功能。

private static void GetFeatureExample(IFeatureClass featureClass, int[] oidList)
{
    int nameFieldIndex = featureClass.FindField("NAME");
    foreach (int oid in oidList)
    {
        IFeature feature = featureClass.GetFeature(oid);
        Console.WriteLine("NAME: {0}", feature.get_Value(nameFieldIndex));
    }
}
private static void GetFeaturesExample(IFeatureClass featureClass, int[] oidList)
{
    int nameFieldIndex = featureClass.FindField("NAME");
    using(ComReleaser comReleaser = new ComReleaser())
    {
        IGeoDatabaseBridge geodatabaseBridge = new GeoDatabaseHelperClass();
        IFeatureCursor featureCursor = geodatabaseBridge.GetFeatures(featureClass,
            ref oidList, true);
        comReleaser.ManageLifetime(featureCursor);
        IFeature feature = null;
        while ((feature = featureCursor.NextFeature()) != null)
        {
            Console.WriteLine("NAME: {0}", feature.get_Value(nameFieldIndex));
        }
    }
}

前面的代码示例在请求单个要素时,具有相同的性能级别,但是GetFeatures示例仅在两个要素(尤其是在远程数据库中)上优于GetFeature示例,并且随着请求更多要素,这两个函数之间的差异也会增大。对于100个特性,GetFeature示例通常需要10-12次运行,而对于1000个特性,它通常需要20次运行。


The preceding code examples have the same level of performance if a single feature is being requested, but the GetFeatures example outperforms the GetFeature example on as few as two features (especially with remote databases), and the difference between the two grows as more features are requested. With 100 features, the GetFeature example typically requires as much as 10–12 times to run, while with 1,000 features, it often takes up to 20 times as long.


相关文章
|
5月前
|
Oracle 关系型数据库 Linux
解决在linux服务器上部署定时自动查找cpu,内存,磁盘使用量,并将查询结果写入数据库的脚本,只能手动运行实现插库操作
问题描述:将脚本名命名为mortior.sh(以下简称mo),手动执行脚本后查询数据库,表中有相应的信息,放入自动执行队列中,脚本被执行,但是查询数据库,并没有新增数据。
44 0
|
3月前
|
存储 缓存 Java
Java性能优化: 如何减少Java程序的内存占用?
Java性能优化: 如何减少Java程序的内存占用?
245 2
|
1月前
|
存储 缓存 监控
Linux 系统 内存通用指标以及查询方式
Linux 系统 内存通用指标以及查询方式
18 0
|
1月前
|
监控 Java Android开发
构建高效Android应用:从内存管理到性能优化
【2月更文挑战第30天】 在移动开发领域,打造一个流畅且响应迅速的Android应用是每个开发者追求的目标。本文将深入探讨如何通过有效的内存管理和细致的性能调优来提升应用效率。我们将从分析内存泄露的根本原因出发,讨论垃圾回收机制,并探索多种内存优化策略。接着,文中将介绍多线程编程的最佳实践和UI渲染的关键技巧。最后,我们将通过一系列实用的性能测试工具和方法,帮助开发者监控、定位并解决性能瓶颈。这些技术的综合运用,将指导读者构建出更快速、更稳定、用户体验更佳的Android应用。
|
3月前
|
SQL 算法 关系型数据库
大查询会不会把内存打爆
大查询会不会把内存打爆
|
3月前
|
SQL 开发框架 .NET
C#进阶-LINQ表达式之GroupBy分组查询
本篇文章我们将演示LINQ扩展包基础语法里的GroupBy分组查询,并实现投影等实际操作中常用的类型转换手法。目前LINQ支持两种语法,我会在每个案例前先用大家熟知的SQL语句表达,再在后面用C#的两种LINQ语法分别实现。LINQ语法第一次接触难免感到陌生,最好的学习方式就是在项目中多去使用,相信会有很多感悟。
64 0
|
4月前
|
运维 Linux
Linux 查询 OS、CPU、内存、硬盘信息
Linux 查询 OS、CPU、内存、硬盘信息
91 0
|
4月前
|
C# 索引
C# | 【完全开源】手机号码归属地查询,一秒内百万次查询
这个开源项目是一个.NET库,可以通过手机号码获取号码归属地信息,包括运营商、国家、省份、城市、邮政编码、区号等信息。 该库加载了一个包含46万条数据的“中国手机号归属地信息”数据集,并实现了高速查询。在我的7年老笔记本上执行一百万次查询耗时不足一秒。
144 0
|
5月前
|
存储 缓存 NoSQL
Linux内存性能优化总结,让你的系统更加高效!(下)
Linux内存性能优化总结,让你的系统更加高效!
|
5月前
|
存储 缓存 Linux
Linux内存性能优化总结,让你的系统更加高效!(上)
Linux内存性能优化总结,让你的系统更加高效!