Выполнение расчетов по свойствам в общем и быстром - PullRequest
0 голосов
/ 18 октября 2019

Цель состоит в том, чтобы создать метод, который в общем случае выполняет вычисления для свойства списка объектов быстрым способом. Ниже приведен весь тестовый код:

using System;
using System.Collections.Generic;

namespace TestApp
{
        public class Minute
        {
            public DateTime DateTimeUtc { get; set; }
            public float Source { get; set; }
            public float Mult2 { get; set; }
            public float Mult3 { get; set; }
            public float Mult4 { get; set; }
        }

        class Program
        {
            public static List<Minute> Minutes = new List<Minute>();

            static void Main(string[] args)
            {

                for (int i = 1; i < 10000000; i++)
                {
                    Minute newMinute = new Minute();
                    newMinute.Source = i;
                    Minutes.Add(newMinute);
                }

                GenerateMult2(Minutes, 2); // 160 ms
                GenerateMult2Generic(Minutes, typeof(Minute), nameof(Minute.Source), nameof(Minute.Mult2),2); // 4300 ms
            }

            public static void GenerateMult2(List<Minute> Minutes, int multiplier)
            {
                for (int i = 0; i < Minutes.Count; i++)
                {
                    // Simplified calculation, there will eventually be a lot more code that goes here!
                    Minutes[i].Mult2 = Minutes[i].Source * multiplier;
                }
            }

            public static void GenerateMult2Generic<T>(List<T> SourceList, Type ContainerType, string propNameSource, string propNameMult, int multiplier)
            {
                var propertyInfoSource = ContainerType.GetProperty(propNameSource);
                var propertyInfoMult = ContainerType.GetProperty(propNameMult);

                foreach (T item in SourceList)
                {
                    float sourceValue = (float)propertyInfoSource.GetValue(item);
                    propertyInfoMult.SetValue(item, sourceValue * multiplier);
                }
            }
        }
    }

В этом тестовом приложении есть метод с именем GenerateMult2 , цель которого - произвести некоторые вычисления для одного из свойств в списке минут. объекты. Этот метод работает отлично и быстро. Проблема в том, что метод слишком специфичен. Если бы я хотел сделать те же вычисления для свойств Mult3 и Mult4, мне нужно было бы создать отдельный метод для каждого из этих свойств, что является слишком большим количеством дублированного кода. Я хочу сделать этот метод более общим, то есть, я хочу, чтобы этот метод также принимал списки других типов, например, список объектов Day или Second. Кроме того, я хочу сообщить методу, на каком свойстве выполнять вычисления.

Итак, я попытался создать универсальный метод с именем GenerateMult2Generic . Этот метод выполняет те же вычисления, что и метод GenerateMult2, и является многоцелевым, что я и хочу. Огромным недостатком является то, что он слишком медленный из-за отражений.

Как можно сделать метод GenerateMult2 в общем виде, но с потерей производительности не более 5%?

Обновление с решением

Изучив ответы здесь, лучшим является тот, который дал Эд Планкетт, но каким-то образом был удален. Поэтому я публикую исходный код, дополненный идеями из этого ответа:

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestApp
{
    public class Minute : BaseTime
    {
        public float MovingAverageFast { get; set; }
        public float MovingAverageSlow { get; set; }
        public float RsiFast { get; set; }
        public float RsiSlow { get; set; }
    }

    public class Day : BaseTime
    {
        public float MovingAverageFast { get; set; }
        public float MovingAverageSlow { get; set; }
        public float RsiFast { get; set; }
        public float RsiSlow { get; set; }
    }

    public class BaseTime
    {
        public DateTime DateTimeUtc { get; set; }
        public float Source { get; set; }
    }

    class Program
    {
        public static List<Minute> Minutes = new List<Minute>();
        public static List<Day> Days = new List<Day>();

        static void Main(string[] args)
        {
            Minutes = Enumerable.Range(1, 10000000).Select(n => new Minute { Source = n }).ToList();
            Days = Enumerable.Range(1, 10000000).Select(n => new Day { Source = n }).ToList();

            // Generating data for Minutes
            GenerateMovingAverage(Minutes, 100, (m, value) => ((Minute)m).MovingAverageFast = value);
            GenerateMovingAverage(Minutes, 500, (m, value) => ((Minute)m).MovingAverageSlow = value);
            GenerateRsi(Minutes, 60, (m, value) => ((Minute)m).RsiFast = value);
            GenerateRsi(Minutes, 250, (m, value) => ((Minute)m).RsiSlow = value);

            // Generating data for Days
            GenerateMovingAverage(Days, 8, (d, value) => ((Day)d).MovingAverageFast = value);
            GenerateMovingAverage(Days, 45, (d, value) => ((Day)d).MovingAverageSlow = value);
            GenerateRsi(Days, 5, (d, value) => ((Day)d).RsiFast = value);
            GenerateRsi(Days, 21, (d, value) => ((Day)d).RsiSlow = value);
        }

        public static void GenerateMovingAverage(IEnumerable<BaseTime> BaseTimeObjects, int Period, Action<BaseTime, float> setter)
        {
            foreach (var BaseTimeObject in BaseTimeObjects)
            {
                float newValue;
                newValue = BaseTimeObject.Source * Period; // pseudo calculation for generating moving average
                setter(BaseTimeObject, newValue);
            }
        }

        public static void GenerateRsi(IEnumerable<BaseTime> BaseTimeObjects, int Period, Action<BaseTime, float> setter)
        {
            foreach (var BaseTimeObject in BaseTimeObjects)
            {
                float newValue;
                newValue = BaseTimeObject.Source / Period; // pseudo calculation for generating rsi
                setter(BaseTimeObject, newValue);
            }
        }
    }
}

Ключевой идеей здесь является установка свойства с помощью действия в вызывающей стороне. Благодаря этому решению методы расчета повторно используются для любого объекта и любого свойства с хорошей производительностью.

Ответы [ 2 ]

0 голосов
/ 19 октября 2019

В дополнение к тому, что написал @ iSR5, вы можете рассмотреть возможность использования фабричного шаблона проектирования , создающего классы, которые выполняют фактические вычисления. Это было бы хорошо, если вы не знаете, что вам действительно нужно делать до времени выполнения.

public interface IMultiValueGenerator
{
  void GenerateValue(ITimeMulti multi, int multiplier);
}

public class Multi2Generator : IMultiValueGenerator
{
  public void GenerateValue(ITimeMulti multi, int multiplier)
  {
    multi.Mult2 = multi.Source * multiplier;
  }
}

public static class MultiGeneratorFactory
{
   public static IMultiValueGenerator GetGenerator(...)
   {
      if (condition)
        return new Multi2Generator();
      // etc
   }
}
0 голосов
/ 18 октября 2019

Не уверен, что у меня есть полная картина, но, насколько я понимаю, вам понадобится интерфейс с базовым классом. Интерфейс - это тот, который вы будете использовать для определения объекта, в то время как базовый класс является контейнером для всех общих операций, которые могут быть наследованы дочерними классами. Затем вы можете создать дочерний класс (столько, сколько хотите) и наследовать базовый класс. Дочерний класс будет иметь необходимые свойства, методы и логику, если это необходимо.

Хватит говорить, давайте рассмотрим это в коде:

interface ITimeMulti
{
    DateTime DateTimeUtc { get; set; }

    float Source { get; set; }

    // will be used for number of available properties.
    int MultCount { get; } 

    // the main method for generating the multipliers. 
    void Generate(int multNumber, int multiplier); 
}

Просто? Давайте теперь создадим базовый класс:

public class TimeMulti : ITimeMulti
{
    public DateTime DateTimeUtc { get; set; }

    public float Source { get; set; }

    // Using Dictionary will be much faster than Reflection 
    protected static Dictionary<string, float> Multipliers { get; set; }

    // Number of Properties (the set should be within the derived classes)
    public int MultCount { get; protected set; }


    // This is a restriction to create this instance from the derived classes only 
    private TimeMulti() { } 

    // for derived classes 
    protected TimeMulti(int multCount)
    {
        // Should be in this constructor only
        Initiate(multCount);
    }

    // This is the main method to generate the multiplication part. 
    public void Generate(int multNumber, int multiplier)
    {
        if (multNumber == 0)
        {
            Multipliers["Mult"] = Source * multiplier;
        }
        else if (Multipliers.ContainsKey("Mult" + multNumber))
        {
            // store the value in the dictionary (this is for reference)
            Multipliers["Mult" + multNumber] = SetMult(multNumber, Source * multiplier);
        }
        else
        {
            throw new NullReferenceException();
        }

    }

    // On new instance, this will fired, which will setup the dictionary
    protected void Initiate(int numberOfMultipliers)
    {
        // Ensure you have an active instance of the dictionary
        if (Multipliers == null)
            Multipliers = new Dictionary<string, float>();

        // Ensurance 
        if(numberOfMultipliers > 0)
        {
            MultCount = numberOfMultipliers;

            for (int x = 1; x <= numberOfMultipliers; x++)
                if (!Multipliers.ContainsKey("Mult" + x))
                    Multipliers.Add("Mult" + x, 0);
        }
        else
        {
            throw new ArgumentOutOfRangeException();
        }

    }

    // this is where we will replace Reflection, here is just returning the multValue
    // we will override it on the derived classes 
    protected virtual float SetMult(int MultNumber, float multValue) => multValue;

}

Теперь, производный класс

public class Minute : TimeMulti
{
    public float Mult1 { get; set; }
    public float Mult2 { get; set; }
    public float Mult3 { get; set; }
    public float Mult4 { get; set; }

    // MultCount = 4
    public Minute(): base(4) { }

    // This method will set the value of the property using switch statment, with this, you will avoid Reflection. 
    protected override float SetMult(int multNumber, float multValue)
    {
        switch (multNumber)
        {
            case 1:
                Mult1 = multValue;
                break;
            case 2:
                Mult2 = multValue;
                break;
            case 3:
                Mult3 = multValue;
                break;
            case 4:
                Mult4 = multValue;
                break;
        }

        return multValue;
    }   

}

Теперь вы можете сделать это:

   class Program
{
    // Create List with type of the ITimeMulti interface
    public static List<ITimeMulti> Minutes = new List<ITimeMulti>();

    static void Main(string[] args)
    {
        // Generate a sample
        for (int i = 1; i < 10000000; i++)
            Minutes.Add(new Minute() { Source = i});

        // Calculate 
        GenerateMultipliers(Minutes, 1, 2); 

    }

    public static void GenerateMultipliers(List<ITimeMulti> source, int multNumber, int multiplier)
    {
        for (int i = 0; i < source.Count; i++)
        {
            source[i].Generate(multNumber, multiplier);
        }

    }

}

Если вы хотитесоздайте новый производный класс:

   public class Day : TimeMulti
    {
        // Properties 
        public float Mult1 { get; set; }

        // Constructor 
        public Day(): base(1) { }

        // This method to map the values to the properties 
        protected override float SetMult(int multNumber, float multValue)
        {
            switch (multNumber)
            {
                case 1:
                    Mult1 = multValue;
                    break;
            }

            return multValue;
        }   

    }

Это всего лишь пример, чтобы дать вам новые идеи, вы можете творить свою магию. Я бы не пошел с Mult1 ... и т. Д. Я бы пошел с уникальными и описательными именами.

Обновлено: Вы можете улучшить производительность вашего обновленного кода, собрав все общие свойства в базе и используя virtual и override, если вы хотите что-то переопределитьвозможность в детском классе. Или используйте interface и struct вместо классов. Кроме того, вместо использования IEnumerable используйте Array, это также улучшит вашу производительность.

public class BaseTime
{
    // shared proprties 
    public DateTime DateTimeUtc { get; set; }
    public float Source { get; set; }
    public float MovingAverageFast { get; set; }
    public float MovingAverageSlow { get; set; }
    public float RsiFast { get; set; }
    public float RsiSlow { get; set; }

}

public class Minute : BaseTime
{
    // add your custom code for Minute
    // No need for recreating them, since it's already inherited from the base 
}

public class Day : BaseTime
{
    // add your custom code for Day
    // No need for recreating them, since it's already inherited from the base 
}

class Program
{
    public static BaseTime[] Minutes;

    public static BaseTime[] Days;

    static void Main(string[] args)
    {
        Minutes = Enumerable.Range(1, 10000000).Select(n => (BaseTime) new Minute { Source = n }).ToArray();
        Days = Enumerable.Range(1, 10000000).Select(n => (BaseTime) new Day { Source = n }).ToArray();

        // Generating data for Minutes
        GenerateMovingAverage(Minutes, 100, (m, value) => m.MovingAverageFast = value);
        GenerateRsi(Minutes, 60, (m, value)  => m.RsiFast = value);
        GenerateRsi(Minutes, 250, (m, value) => m.RsiSlow = value);

        // Generating data for Days
        GenerateMovingAverage(Days, 8, (d, value)  => d.MovingAverageFast = value);
        GenerateMovingAverage(Days, 45, (d, value) => d.MovingAverageSlow = value);
        GenerateRsi(Days, 5, (d, value)  => d.RsiFast = value);
        GenerateRsi(Days, 21, (d, value) => d.RsiSlow = value);         
    }

    public static void GenerateMovingAverage(BaseTime[] BaseTimeObjects, int Period, Action<BaseTime, float> setter) 
    {
        foreach (var BaseTimeObject in BaseTimeObjects)
        {
            setter(BaseTimeObject, BaseTimeObject.Source * Period);
        }
    }

    public static void GenerateRsi(BaseTime[] BaseTimeObjects, int Period, Action<BaseTime, float> setter)
    {
        foreach (var BaseTimeObject in BaseTimeObjects)
        {
            setter(BaseTimeObject, BaseTimeObject.Source / Period);
        }
    }

}
...