私はAutoMapperの大ファンです。現在、wcfサービスモデルからビジネスモデルなど、さまざまなドメイン間でエンティティをマッピングするために多くのプロジェクトで使用しています。
サンプルWebサイトで(VS Profilerを使用して)いくつかの負荷テストを行った後、AutoMapperが高いCPU消費の原因であることがわかりました。
私はこの振る舞いのためにいくつかのユニットを実行しました:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace AutoMapper.Tests
{
[TestClass]
public class UnitTest
{
public class ClassSource
{
public string PropertyA { get; set; }
public int PropertyB { get; set; }
public NestedClassSource PropertyC { get; set; }
}
public class NestedClassSource
{
public string PropertyD { get; set; }
public DateTime PropertyE { get; set; }
public List<int> PropertyF { get; set; }
}
public class ClassDestination
{
public string PropertyA { get; set; }
public int PropertyB { get; set; }
public NestedClassDestination PropertyC { get; set; }
}
public class NestedClassDestination
{
public string PropertyD { get; set; }
public DateTime PropertyE { get; set; }
public List<int> PropertyF { get; set; }
}
[TestMethod]
public void MappingPerfTests()
{
Mapper.Initialize(a =>
{
a.CreateMap<ClassSource, ClassDestination>();
a.CreateMap<NestedClassSource, NestedClassDestination>();
});
Mapper.AssertConfigurationIsValid();
IList<ClassSource> items = GenerateRandomSources(nbItems: 500);
//automapper
MicroBench(() =>
{
var res = Mapper.Map<IList<ClassSource>, IList<ClassDestination>>(items);
}, nbIterations: 10);
// will take nearly 30 ms per test
// total : 300 ms
//manual mapper
MicroBench(() =>
{
var res = new List<ClassDestination>(items.Count);
foreach (var source in items)
{
res.Add(new ClassDestination()
{
PropertyA = source.PropertyA,
PropertyB = source.PropertyB,
PropertyC = new NestedClassDestination()
{
PropertyD = source.PropertyC.PropertyD,
PropertyE = source.PropertyC.PropertyE,
PropertyF = new List<int>(source.PropertyC.PropertyF)
}
});
}
}, nbIterations: 10);
// will take nearly 0 ms per test
// total : 1 ms
}
private IList<ClassSource> GenerateRandomSources(int nbItems = 1000)
{
IList<ClassSource> res = new List<ClassSource>(100);
foreach (var i in Enumerable.Range(1, nbItems))
{
ClassSource item = new ClassSource()
{
PropertyA = "PropertyA",
PropertyB = i,
PropertyC = new NestedClassSource() { PropertyD = "PropertyD", PropertyE = DateTime.Now, PropertyF = Enumerable.Range(1, 10).ToList() }
};
res.Add(item);
}
return res;
}
private void MicroBench(Action action, int nbIterations = 1000)
{
long totalElapsed = 0L;
foreach (var i in Enumerable.Range(1, nbIterations))
{
Stopwatch watcher = Stopwatch.StartNew();
action();
watcher.Stop();
Console.WriteLine("test : {0} ms", watcher.ElapsedMilliseconds);
totalElapsed += watcher.ElapsedMilliseconds;
}
Console.WriteLine("total : {0} ms", totalElapsed);
Console.WriteLine("avg : {0} ms", totalElapsed / nbIterations);
}
}
}
最後に、AutoMapperは非常に遅いように見えます。テストごとに平均30ミリ秒ですが、手動マッピングには1ミリ秒未満かかります。私は500個の「単純な」オブジェクトを「のみ」マッピングしています!MVC ViewModelの場合、これはめったに発生しませんが、他の人が頻繁にマッピングする可能性があります。
30ミリ秒は速いように見えますが、本当の問題は、この30ミリ秒(無料)がWebサーバーのCPU時間であるということです。サーバーはどのように重い負荷(100人の同時ユーザー)を処理しますか?実際にはよくありませんが、それが負荷テストが警告をスローする理由です。
Mapper.CreateMapExpressionを使用してlinq式を生成する方法を見つけましたが、残念ながら、式にはネストされた型またはオプションが含まれていません。
では、AutoMapperのパフォーマンスを向上させる方法はありますか?ベストプラクティスはありますか?
ありがとう、