Как получить это очень быстро?

У меня есть структура, которая позволяет пользователям выполнять запросы к определенному источнику данных (внутриигровая база данных Football Manager 2010, для тех из вас, кто заинтересован).

В этой структуре у меня есть два разных режима, в которых может работать моя структура: режим реального времени и режим кэширования. Я хочу, чтобы пользователи, использующие этот фреймворк, могли переключаться, просто вызывая другой конструктор (например, new Context(Mode.Cached)). Это должен быть единственный переключатель, который должен сделать пользователь, чтобы он мог по-прежнему иметь все те же вызовы Linq, но просто использовать режим кэширования, когда его приложение лучше подходит. Прозрачный.

Я решил, что использование PostSharp должно быть моим лучшим выбором, потому что:

  • Создайте аспект для каждого свойства (которое уже было оформлено атрибутом)
  • В этом аспекте проверьте, находимся ли мы в режиме Cached или Realtime
  • Вернуть значение либо из памяти, либо из кеша

Что ж, это работает. НО! Скорость недостаточно хороша. При выполнении следующих действий на 90 000 объектов:

foreach (Player p in fm.Players)
{
    int ca = (short)ProcessManager.ReadFromBuffer(p.OriginalBytes, PlayerOffsets.Ca, typeof(Int16));
}

Это занимает всего 63 мс. (ReadFromBuffer — это высокооптимизированная функция, которая принимает byte[], int, Type и возвращает object), 63 мс — это очень разумно, учитывая большое количество объектов.

Но! В PostSharp я реализовал то же самое, используя это:

    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
        if (eventArgs.Method.Name.StartsWith("~get_"))
        {
            if (Global.DatabaseMode == DatabaseModeEnum.Cached)
            {
                byte[] buffer = ((BaseObject)eventArgs.Instance).OriginalBytes;

                eventArgs.ReturnValue =
                        ProcessManager.ReadFromBuffer(buffer, this.Offset, eventArgs.Method.ReturnType);
            }

Теперь я называю это с помощью

foreach (Player p in fm.Players)
{
    int ca = p.CA;
}

И это занимает 782 мс, то есть более чем в 10 раз больше!

Я создал аспект как:

[Serializable]
[MulticastAttributeUsage(MulticastTargets.Method, PersistMetaData = true)]
internal class FMEntityAttribute : OnMethodInvocationAspect
{
    public FMEntityAttribute(int offset, int additionalStringOffset)
    {
        this.Offset = offset;
        this.AdditionalStringOffset = additionalStringOffset;
    }
    //blah blah AOP code
}

И собственность оформлена как

    [FMEntityAttribute(PlayerOffsets.Ca)]
    public Int16 CA { get; set; }

Как я могу заставить это работать хорошо?!


person Jan Jongboom    schedule 17.11.2009    source источник
comment
Что, согласно вашему профилированию, было дорогой точкой доступа?   -  person Eric Lippert    schedule 18.11.2009
comment
То, что больше всего времени было потрачено на получение моего свойства, не очень полезная информация.   -  person Jan Jongboom    schedule 18.11.2009


Ответы (4)


Вместо того, чтобы создавать свой контекст с помощью new Context(Mode.Cached)), используйте фабричный метод, который создает контекст. Затем реализуйте два ваших поведения в двух разных классах, которые разделяют все, что им нужно от абстрактного супертипа. Используйте аспекты и рефлексию для решения проблем, которые не имеют простого прямого решения.


заменять

[FMEntityAttribute(PlayerOffsets.Ca)] public Int16 CA { get; }

с участием

public Int16 CA { get { return PlayerAttrs.Ca.Get(this); } }

где PlayerAttrs имеет оператор Int16 для преобразования себя в Int16 по запросу, имеет требуемое смещение и выполняет соответствующий кэшированный/некэшированный поиск.

person Pete Kirkham    schedule 17.11.2009
comment
Да, я думал об этом, но на данный момент существует около 400 свойств, разделенных на 10 классов. Каждое свойство ведет себя одинаково: вызовите ProcessManager.ReadFromBuffer с byte[] объекта, смещением в его атрибуте и его типом. Для другого режима что-то подобное. Мне кажется, что кодовое плетение должно быть идеальным для этого, иначе мне пришлось бы копировать и вставлять один и тот же код 400 раз. - person Jan Jongboom; 18.11.2009
comment
В порядке. Предоставленный вами пример кода кажется действительно хорошей идеей. Будет реализовывать это. - person Jan Jongboom; 18.11.2009

Вы можете получить гораздо лучшие результаты, используя LocationInterceptionAspect PostSharp 2.0.

Но тогда вам следует избегать использования eventArgs.Method.ReturnType во время выполнения; скорее получите значение в методе RuntimeInitialize и сохраните его в поле. Таким образом, System.Reflection не используется во время выполнения.

person Gael Fraiteur    schedule 18.11.2009
comment
Ах, я думал, что eventArgs.Method.ReturnType — это просто поле, созданное во время компиляции и не используемое для отражения!? - person Jan Jongboom; 18.11.2009

  1. Используйте метод CompileTimeValidate, чтобы проверить, является ли это свойством или нет.
person Community    schedule 17.11.2009
comment
Ну, это всегда свойство, так как я применяю атрибут только к свойствам. Проверка для ~get существует, потому что у меня также есть разные поведения сеттера для двух режимов, как я их разделил? - person Jan Jongboom; 18.11.2009
comment
Тем не менее, вы можете переместить оператор if и сравнить строку с CompileTimeValidate и сохранить результат в частном логическом свойстве аспекта. - person ; 18.11.2009
comment
Круто, урезал на 70% с помощью этого - person Jan Jongboom; 18.11.2009

Отражение может быть дорогим. Одна вещь, которую вы можете попробовать, — это скомпилировать оболочку для этого класса во время выполнения и сохранить себе попадание для каждого вызова, которое у вас есть в настоящее время.

person Chris Pitman    schedule 18.11.2009