Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any way to improve Automapper performance?

I am a big fan of AutoMapper. I am now using it in many projects for mapping entities between different domains like from wcf service model to business model.

After some load tests (with VS Profiler) in a sample website, I found that AutoMapper is responsible for high CPU consumption.

I have done some unit for this behavior :

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);

        }
    }
}

At the end, AutoMapper seems quite slow : average 30 ms per test whereas manual mapping takes less than one ms. I am "only" mapping 500 "simple" objects ! Maybe for a MVC ViewModel, this will rarely occurs, but others mapping it will can be frequent.

30 ms seems to be fast, but the real trouble is that this 30 ms (for nothing) is CPU time on the web server How does the server will handle heavy load (100 concurrent users) ? Not well in fact, that's why our load test throws a warning.

I founded a way to generate linq expression with Mapper.CreateMapExpression, but unfortunately the expression does not contains nested types or options.

So, is there any way to improve AutoMapper performance? Are there best practices?

Thanks,

like image 583
Cybermaxs Avatar asked Dec 16 '22 19:12

Cybermaxs


1 Answers

Look into this article to get some insight about AutoMapper, its alternatives as well as performance metrics. I think it will give you more holistic view and you won't be concerned about performance as much.

Which is faster: AutoMapper, Valuinjector, or manual mapping? To what degree is each one faster?

My opinion will be similar - there's no right answer to your question (you didn't really ask a question in your posting:) ). You balance between CPU time vs. human time (to maintain), and you cannot compare the two. But this IMHO should be the decisive factor, but its obviously up to you what approach to take.

Edit

Try looking here to see if you find some clues relevant to your situation: Need to speed up automapper...It takes 32 seconds to do 113 objects

Also another link (although from 2009), but maybe relevant still ... Analyzing AutoMapper performance

Thanks, hope this helps.

like image 102
Display Name Avatar answered Dec 18 '22 12:12

Display Name