简体   繁体   中英

Eliminating visitor pattern in C#

I have code that looks like this:

var visitor = new ImplementsVisitor();
for(int i = 0; i < array.Length; ++i)
    arrray[i].Accept(visitor);

Each element in the array implements IItem interface, which has an Accept(IVisitor) method. Nothing but the standard visitor pattern.

While measuring performance, I came to the conclusion that the call by interface is too slow, and in this code is performance is critical. From your experience, what would be the best option of eliminating any virtual or interface calls? An if statement that checks for the concrete type? An enum on each element with a switch/case (in this case, the code's structure is such that no cast will be required)? Something else?

PS I cannot sort the items in the array. The order is important. Thus, I cannot sort them by concrete type to help branch prediction.

I created the following program. On my laptop the loop runs a million times in 8ms (that's a Release build, Debug is 11ms or so). That is approximately 0.000008ms to do the virtual dispatch and increment an int. Exactly how fast do you need it to be? I'd suspect that something has gone wrong with either your performance test or mine. If mine I'd be interested to hear suggestions for improvement.

Generally if performance at this level isn't good enough then using C# is probably a problem in itself. Its garbage collector has a habit of freezing threads in the middle of loops for example. If 0.000008ms on a loop iteration really is an issue, I'd suspect Assembly language or C would be a better choice.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            const int count = 1000000;

            IList<IItem> items = new List<IItem>(count);
            for (int i = 0; i < count; i++)
            {
                var rnd = new Random();
                if (rnd.NextDouble() > 0.5)
                {
                    items.Add(new ClassA());
                }
                else
                {
                    items.Add(new ClassB());
                }
            }

            var visitor = new MyVisitor();

            Stopwatch s = Stopwatch.StartNew();
            for (int i = 0; i < items.Count; i++)
            {
                items[i].Accept(visitor);
            }
            s.Stop();

            Console.WriteLine("ExecTime = {0}, Per Cycle = {1}", s.ElapsedMilliseconds, (double)s.ElapsedMilliseconds / count);
            visitor.Output();
        }

        interface IVisitor
        {
            void Process(ClassA item);
            void Process(ClassB item);
        }

        interface IItem
        {
            void Accept(IVisitor visitor);
        }

        abstract class BaseVisitor : IVisitor
        {
            public virtual void Process(ClassA item)
            {

            }

            public virtual void Process(ClassB item)
            {

            }
        }

        class ClassA : IItem
        {
            public void Accept(IVisitor visitor)
            {
                visitor.Process(this);
            }
        }

        class ClassB : IItem
        {
            public void Accept(IVisitor visitor)
            {
                visitor.Process(this);
            }
        }

        class MyVisitor : BaseVisitor
        {
            int a = 0;
            int b = 0;

            public override void Process(ClassA item)
            {
                a++;
            }

            public override void Process(ClassB item)
            {
                b++;
            }

            public void Output()
            {
                Console.WriteLine("a = {0}, b = {1}", a, b);
            }
        }
    }
}

You don't have one virtual call here, you have two, but you only need one. First your array presumably has a virtual call through IItem - but if these are all the same type, and you know the type (and it is sealed) a virtual call is unnecessary.

Then within the visited object, you need to do whatever operation the visitor wants to do. This will probably also involve a virtual call.

You might do better with a typed IVisitor:

 interface IItem<TVisitor> : IItem 
     where TVisitor : IVisitor
 {
     void Accept(TVisitor visitor);
 }

 // Then
 SpecialVisitor visitor = ImplementsSpecialVisitor();
 foreach(var item in arrayOfSpecialItems){
     item.Accept<SpecialVisitor>(visitor);
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM