zoukankan      html  css  js  c++  java
  • Apworks框架中各种仓储实现的性能基准测试与结果对比

    周末抽空简单地对Apworks框架所支持的三种仓储实现:Entity Framework、NHibernate以及MongoDB进行了性能基准测试,并对其结果进行对比。从对比的结果来看,MongoDB仓储的性能要远胜于其它两者。基准测试采用的是我在两年前开发的一个基于Visual Studio Unit Test Framework的基准测试程序(名为:Visual Benchmark),该程序可以设定基准测试引擎,并对测试结果进行图形化呈现。在本文末尾会简要地对此软件进行介绍,有兴趣的朋友不妨了解一下。

    单体测试的设计

    由于Visual Benchmark是基于Visual Studio Unit Test Framework的单体测试用例的,因此,我们只需要在Visual Studio中开发有待测试的单体测试程序即可。在开发单体测试程序之前,首先让我们了解一下Visual Benchmark所支持的“迭代基准测试”模式。所谓“迭代基准测试”,意思就是Visual Benchmark会循环地调用单体测试方法并在每次调用结束时,统计当前“代”中所消耗的CPU周期或者时间;当Visual Benchmark完成整个测试之后,会将各代的统计结果组织并显示出来。在这个过程中,单体测试方法可以通过Visual Benchmark所提供的基准测试参数来获得当前所处的“代”数(也就是循环因子的值),然后就可以基于这个“代”数对测试数据进行模拟,以反映出随着测试数据的增长,单体测试方法的执行效率。

    举例来说,在测试三种不同的仓储对于聚合的保存执行效率时,我首先在Visual Studio中新建了一个Unit Test类,并在类中定义了两个成员变量:

    [TestClass]
    public class InsertAggregateRootsTest
    {
        private int thisIteration;
        private IEnumerable<SalesOrder> mockSalesOrders;
        // 其它部分暂时省略
    }

    第一个成员变量thisIteration用来保存从Visual Benchmark传入的“代”数;而第二个成员变量mockSalesOrders则是保存了一组即将通过仓储插入的聚合模拟数据。

    接下来,我在这个测试类中加入了Test Initialize的方法,以便在每次测试方法被调用前,执行一些数据初始化的操作。在这个方法中,会对以上两个成员变量初始化,同时清空后台数据库,为执行测试做准备。

    [TestInitialize()]
    public void MyTestInitialize()
    {
        thisIteration = (int)BenchmarkRuntimeArgs.Instance.ThisIteration;
        mockSalesOrders = Helper.MockSalesOrders(thisIteration);
        Helper.ClearSQLServerTables();
        Helper.ClearMongoDB();
    }

    在上面的方法中,首先使用BenchmarkRuntimeArgs.Instance.ThisIteration对thisIteration进行初始化,以便获得当前测试的“代”。BenchmarkRuntimeArgs是一个跨应用程序域的单件(Singleton),在每次执行Benchmark之前都会被初始化。然后根据获得的“代”数,创建聚合模拟数据。此处mockSalesOrders中所包含的数据量会随着“代”数的增长而增加,以反映随着数据量的增长,被测函数的性能趋势。最后,使用Helper类清空后台数据库。

    由于仓储的实现是基于不同的应用框架,因此,在所有测试启动前,需要对这些框架进行初始化。有写过单体测试的朋友都知道,这部分逻辑应该写在Class Initialize的方法中:

    [ClassInitialize()]
    public static void MyClassInitialize(TestContext testContext)
    {
        Database.SetInitializer<EntityFrameworkDbContext>(new 
            DropCreateDatabaseIfModelChanges<EntityFrameworkDbContext>());
        MongoDBRepositoryContext.RegisterConventions();
        if (!BsonClassMap.IsClassMapRegistered(typeof(SalesLine)))
        {
            BsonClassMap.RegisterClassMap<SalesLine>(p =>
            {
                p.AutoMap();
                p.UnmapProperty<SalesOrder>(q => q.SalesOrder);
            });
        }
    }
    

    在这里并没有使用代码的方式对NHibernate框架进行初始化,因为NHibernate的初始化过程是由Apworks中的NHibernateApplicationConfiguration类型完成的,这个类型依赖应用程序的app/web.config文件。所以我们需要在单体测试项目中添加app.config以及相关的配置节点。篇幅原因,这里就不贴app.config的代码了,文章末尾我会给出源代码。Visual Benchmark支持在“客户应用程序域”(Client AppDomain)中装载app/web.config文件。

    现在,可以开始写测试方法了,以下是基于三种不同仓储实现的测试方法。从各方法中我们可以看到,除了所创建的IRepositoryContext、IRepository的具体实现不同之外,其它的操作逻辑完全相同:即通过仓储对聚合进行保存:

    [TestMethod]
    public void EntityFramework()
    {
        using (IRepositoryContext context = new EntityFrameworkRepositoryContext(new EntityFrameworkDbContext()))
        {
            IRepository<SalesOrder> salesOrderRepository = new EntityFrameworkRepository<SalesOrder>(context);
            foreach (var salesOrder in mockSalesOrders)
                salesOrderRepository.Add(salesOrder);
            context.Commit();
        }
    }
    
    [TestMethod]
    public void NHibernate()
    {
        using (IRepositoryContext context = new NHibernateContext(new NHibernateApplicationConfiguration()))
        {
            IRepository<SalesOrder> salesOrderRepository = new NHibernateRepository<SalesOrder>(context);
            foreach (var salesOrder in mockSalesOrders)
                salesOrderRepository.Add(salesOrder);
            context.Commit();
        }
    }
    
    [TestMethod]
    public void MongoDB()
    {
        using (IRepositoryContext context = new MongoDBRepositoryContext(new MongoDBRepositoryContextSettings()))
        {
            IRepository<SalesOrder> salesOrderRepository = new MongoDBRepository<SalesOrder>(context);
            foreach (var salesOrder in mockSalesOrders)
                salesOrderRepository.Add(salesOrder);
            context.Commit();
        }
    }

    执行测试

    首先,我们在Visual Studio中测试这三个方法,以确保每个方法都能够正确完成。在启动测试之前,先回到上面的MyTestInitialize方法,将thisIteration设置为一个固定的整数值,比如20,以便测试能够正常启动。在完成三个方法的测试之后,我们可以通过Test Results窗口看到测试结果。

    image

    打开Visual Benchmark,新建一个Session,在“打开”对话框中,选择已经编译好的DLL文件,此时Visual Benchmark会将其中包含的所有的测试类和测试方法加载到左边的树形结构中。在树形结构中,选中需要测试的方法,然后单击“开始”按钮,Visual Benchmark便会针对所选的测试方法进行基准测试。最后,会根据不同的测试引擎的设计,将结果显示出来。

    image

    测试结果

    Visual Benchmark能够根据设置,采用一些减噪手段以尽量保证测试结果的真实性。通过所测结果不难看出,在我所测试的三个场景中,基于MongoDB实现的仓储,性能要优于其它两者。而NHibernate仓储又要好于Entity Framework仓储。

    测试环境

    以下是执行测试的环境配置:

    • CPU:Intel Core i5-540M Cores: 2 Logical: 4
    • Chipset:Intel QM57 (IbexPeak-M DO)
    • Memory:Hynix 666.7MHz (PC3-10600) 2048MB x1, Kingston 666.7MHz (PC3-10600) 4096MB x1. Totally 6144MB
    • OS:Microsoft Windows 7 Enterprise (x64) Build 7601

    场景一:聚合保存

    Insert

    注:上图中X轴表示的是“代”数,亦即模拟的聚合数量;Y轴表示执行时间(毫秒数)。下同。

    场景二:聚合查询

    Retrieve

    注:在此场景中,EntityFramework支线所表示的是使用Eager Loading将SalesOrder及其下所有Sales Lines实体读出所开销的时间;而EntityFramework_NoEagerLoad支线所表示的是仅读出SalesOrder(不包括其下所有Sales Lines)所开销的时间。

    场景三:查询所有并删除

    FindAndDelete

    关于Visual Benchmark

    Visual Benchmark是我在2010年开发的一款基于Visual Studio单体测试框架的性能基准测试程序,从整体上看,Visual Benchmark具有如下架构设计:

    image

    首先,Visual Benchmark和被测试的程序集都是基于Microsoft .NET Framework的,在Visual Benchmark中,基准测试的执行是以Session为单位的。Engine Management System为Visual Benchmark提供了安全的、可扩展的基准测试引擎管理系统,因此,通过这套管理系统,用户可以选用各种不同的引擎进行测试,开发人员也可以根据自己的实际需求对引擎进行二次开发与定制,并应用到Visual Benchmark系统中。

    其次,当Session被打开时,它会通过Remote Proxy将被测试的程序集装载到客户应用程序域(Client AppDomain)中。这样做的理由是:1、能够在完成测试后,以AppDomain.Unload的方式卸载被测试程序集;2、能够在装载程序集时,同时将app/web.config和resource都装载到Client AppDomain中,以此模拟真实的执行环境。

    功能技术特点

    Visual Benchmark具有如下功能技术特点:

    1. 可定制的基准测量标尺:开发人员可以自己开发基准测试的测量标尺。目前仅支持两种:StopwatchTickRuler和StopwatchMillisecondsRuler。上文的测试采用的是StopwatchMillisecondsRuler
    2. 可定制的测试引擎:开发人员可以根据需求定制开发测试引擎。框架提供了完整的引擎定制功能,这包括:引擎的元数据(例如名称、作者、描述等)、版本、配置界面、结果显示界面以及HTML文档。目前支持Iterated Throughput、Simple、Simple Iteration以及Throughput四种引擎。上文的测试采用了Simple Iteration引擎
    3. 减噪选项:使用减噪选项以获得更真实的测试数据。Visual Benchmark提供两个减噪选项:在每次执行测试之前强制垃圾回收、丢弃第一次的测试结果。测试引擎也会根据情况提供获取平均执行时间的选项
    4. 在客户应用程序域(Client AppDomain)中执行基准测试:能够对单体测试环境进行模拟,被测方法能够正常地访问配置文件和资源文件
    5. 跨AppDomain的单件(Singleton)实现:能够方便地在单体测试方法中读取Visual Benchmark的相关参数信息
    6. 多线程执行:用户可以随时停止Benchmark的执行

    界面截图

    基于两种不同引擎的执行结果显示

    image

    测试引擎的配置界面与文档界面

    image

    Session信息与客户应用程序域(Client AppDomain)信息

    image

    总结

    本文对Apworks框架中所支持的三种仓储实现进行了性能上的基准测试,并得出了测试结果。在最开始的时候,我是打算结合Visual Studio的测试框架来完成这些工作的,但后来发现Visual Studio的测试框架所提供的功能并不能达到我的需求,之前也采用了Visual Studio的Load Test来做压力测试,但是效果并不算太理想。在下才疏学浅,并没有弄通Visual Studio提供的强大测试功能,所以也只能借用我之前写的Visual Benchmark程序了。如果有读者朋友知道如何在Visual Studio中完成类似的测试工作,还烦请告知在下,我会虚心向您学习。

    下一步,我将对Apworks框架的线程安全性做一些评估,等到有了满意的结果,我也会将相关经验分享出来。

    相关下载

    【单击此处】下载本文的单体测试解决方案及源代码

    【单击此处】下载Visual Benchmark的绿色免安装程序

    【单击此处】下载Visual Benchmark的源程序代码(请使用Visual Studio 2010打开,没有使用任何第三方控件,所以无需安装其它组件)

  • 相关阅读:
    RabbitMQ入门-消息订阅模式
    RabbitMQ入门-消息派发那些事儿
    RabbitMQ入门-高效的Work模式
    RabbitMQ入门-从HelloWorld开始
    RabbitMQ入门-初识RabbitMQ
    CMake INSTALL 命令设置exe dll lib的安装位置
    VS调试DLL代码使用”附加到进程“
    模型自身面片重合引起的闪烁破损解决方法
    地球表面使用世界坐标系绘制物体闪烁破损处理方法
    3dmax osg格式导出插件 osgExp OpenSceneGraph Max Exporter
  • 原文地址:https://www.cnblogs.com/daxnet/p/2616197.html
Copyright © 2011-2022 走看看