Кеширование длительных вычислений с PostSharp
Интересный пример нашел в поставке PostSharp.
Суть в чем. Они предлагают как одно из использований использовать PostSharp и АОП для кеширования долгоиграющих или сложных операций. Честно говоря я для себя придумал много use case АОП, но к такому еще не пришел.
Итак, пусть есть некий метод GetDifficultResult, и пусть вычисления в нем занимают, скажем, больше 1с, что для нас долго. Подход заключается в том, чтобы закешировать разные возвращаемые значения этой функцией в зависимости от входные параметров. Дело в том, что, если выполнение вычислений в методе не зависит от времени, а только от входных параметров, которые выбираются из некоего конечного набора, то мы можем закешировать связку входные “параметры – результат функции” и при этом нету никакой необходимости вызывать эту функцию снова.
Итак, пример (пример как я говорил идет в поставке с посташарпом, но такой подход можно применять и без него):
internal class Program { public static void Main( string[] args ) { Console.WriteLine( "1 ->" + GetDifficultResult( 1 ) ); Console.WriteLine( "2 ->" + GetDifficultResult( 2 ) ); Console.WriteLine( "1 ->" + GetDifficultResult( 1 ) ); Console.WriteLine( "2 ->" + GetDifficultResult( 2 ) ); } [Cache] private static int GetDifficultResult( int arg ) { // If the following text is printed, the method was not cached. Console.WriteLine( "Some difficult work!" ); Thread.Sleep( 1000 ); return arg; } }
Итак есть сложная долговычисляемая функция GetDifficultResult, которая на вход принимает целое число и результат которой зависит только от входных параметров. Нам необходимо ускорить работу программы. Как это сделать? Если входной параметр выбирается из некоторого конечного набора или предположим в нашем случае статистически проверено, что чаще всего передаются входные параметры 1 и 2, то результат функции мы можем закешировать.
Суть кеширования заключается в том, чтобы перед вызовом функции проверить, если в кеше имеется посчитанное значение для такого параметра и если его нет, то вызвать функцию и посчитать, а по завершению подсчетов, внести входной параметр и результат в словарь. Если же для такого(таких) входных параметров в словаре окажется посчитанное значение, то функция не вызывается, а мы сразу возвращаем результат из словаря.
Это можно сделать и без применение АОП и без PostSharp. Но, например, если такие вещи нужно проделывать в нескольких местах или классах, то почему бы не использовать АОП – ведь это сквозной функционал (cross-cutting concern). Тогда при использовании АОП подхода такие методы достаточно пометить атрибутом.
Итак, реализация с АОП и PostSharp. Реализуем свой атрибут, унаследованный от OnMethodBoundaryAspect. OnMethodBoundaryAspect – это абстрактный класс реализованный как атрибут. Чтобы вызывать наш функционал кеширования до вызова метода и после его завершения достаточно переопределить методы OnEntry и OnSuccess, но для того, чтобы проверить был ли правильно использован наш атрибут, переопределим еще два метода CompileTimeInitialize и CompileTimeValidate – которые на этапе компиляции будут проверять и выдавать ошибку компиляции, если атрибут использован на конструкторах, методах, что не возвращают значения, или с out параметрами. Ведь, например, не зачем маркировать void-методы, если они не возвращают значение.
Итак, реализация:
[Serializable] public sealed class CacheAttribute : OnMethodBoundaryAspect { // Some formatting strings to compose the cache key. private MethodFormatStrings formatStrings; // A dictionary that serves as a trivial cache implementation. private static readonly Dictionary<string, object> cache = new Dictionary<string, object>(); // Validate the attribute usage. public override bool CompileTimeValidate( MethodBase method ) { // Don't apply to constructors. if ( method is ConstructorInfo ) { Message.Write( SeverityType.Error, "CX0001", "Cannot cache constructors." ); return false; } MethodInfo methodInfo = (MethodInfo) method; // Don't apply to void methods. if ( methodInfo.ReturnType.Name == "Void" ) { Message.Write( SeverityType.Error, "CX0002", "Cannot cache void methods." ); return false; } // Does not support out parameters. ParameterInfo[] parameters = method.GetParameters(); for ( int i = 0; i < parameters.Length; i++ ) { if ( parameters[i].IsOut ) { Message.Write( SeverityType.Error, "CX0003", "Cannot cache methods with return values." ); return false; } } return true; } // At compile time, initialize the format string that will be // used to create the cache keys. public override void CompileTimeInitialize( MethodBase method, AspectInfo aspectInfo ) { this.formatStrings = Formatter.GetMethodFormatStrings( method ); } // Executed at runtime, before the method. public override void OnEntry( MethodExecutionArgs eventArgs ) { // Compose the cache key. string key = this.formatStrings.Format( eventArgs.Instance, eventArgs.Method, eventArgs.Arguments.ToArray() ); // Test whether the cache contains the current method call. lock ( cache ) { object value; if ( !cache.TryGetValue( key, out value ) ) { // If not, we will continue the execution as normally. // We store the key in a state variable to have it in the OnExit method. eventArgs.MethodExecutionTag = key; } else { // If it is in cache, we set the cached value as the return value // and we force the method to return immediately. eventArgs.ReturnValue = value; eventArgs.FlowBehavior = FlowBehavior.Return; } } } // Executed at runtime, after the method. public override void OnSuccess( MethodExecutionArgs eventArgs ) { // Retrieve the key that has been computed in OnEntry. string key = (string) eventArgs.MethodExecutionTag; // Put the return value in the cache. lock (cache) { cache[key] = eventArgs.ReturnValue; } } }
В коде используется еще класс Formatter код которого я не привожу, так как смысл его просто форматировать значения.
Этот пример можно загрузить вместе с PostSharp, который бесплатен для community использования. Загрузить это все можно тут.
Несомненно, use case кеширования долгоиграющих вычислений с помощью средств АОП весьма интересен и я возьму себе его не заметку.
http://regfordev.blogspot.com/2011/02/postsharp.html