Memoization and it's use in AX365
Let us see the definition of memoization in wiki.
"Memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again."
Background: In our implementation project we have a dimension helper class which has specific static methods to find the dimension value of a dimension or dimension value description or by dimension attribute recid etc. So in the implementation many customizations or reports or data entity etc makes use of ready made dimension helper class. As a result of which MS detected there was a whooping amount of queries to DIMENSIONATTRIBUTEVALUESETITEM table which resulted to overwhelming of SQL and causing high memory / buffer IO.
So lets see one such method getDimensionDisplayValueByAttributeRecid from the dimension helper class which uses DimensionAttributeValueSetStorage class. We can see the find method in the below screenshot which uses DimensionAttributeValueSetItem and loops records
As per MS using DimensionAttributeValueSetStorage class to fetch dislay values is a heavy weight approach. In order to reduce the amount of db calls for same records rather then going sql we would need to cache the records. Standard have already implemented the memoization pattern in many scenarios. eg: LedgerCache, BudgerCache classes.
So now we would improve our getDimensionDisplayValueByAttributeRecid method using memoization pattern.
Create a enum with an element which defines a scope
Create a class for caching
/// <summary> /// The <c>KrishDimensionCache</c> class caches default dimension information. /// </summary> /// public class KrishDimensionCache { protected void new() { } /// <summary> /// Clears the Krish cache for all Krish cache scopes. /// </summary> public static void clearAllScopes() { DictEnum dictEnum = new DictEnum(enumnum(KrishDimensionCacheScope)); int i; for (i = 0; i <= dictEnum.values(); i++) { KrishDimensionCache::clearScope(dictEnum.index2Value(i)); } } /// <summary> /// Clears the Krish cache for the specified Krish cache scope. /// </summary> /// <param name="_scope"> /// The Krish cache scope for which to clear. /// </param> public static void clearScope(KrishDimensionCacheScope _scope) { SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache(); objectCache.clear(KrishDimensionCache::getCacheScopeStr(_scope)); } /// <summary> /// Gets the Krish cache scope identifier for the specified Krish cache scope. /// </summary> /// <param name="_scope"> /// The Krish cache scope for which to get the identifier. /// </param> /// <returns> /// The identifier of the Krish cache scope. /// </returns> private static str getCacheScopeStr(KrishDimensionCacheScope _scope) { // Int2Str is used instead of Enum2Str to get the int // value like '2' instead of the string value for // the scope to ensure uniqueness. The _scope enum // is implicitly cast to an int by this call. This // avoids calling strfmt() which causes a kernel // callback running under IL and is therefore significantly // slower. return 'KrishDimensionCache_' + int2str(_scope); } /// <summary> /// Gets the Krish cache value for the specified Krish cache scope and value key. /// </summary> /// <param name="_scope"> /// The Krish cache scope for which to obtain the cache value. /// </param> /// <param name="_key"> /// The value key for which to obtain the cache value. /// </param> /// <returns> /// The Krish cache value. /// </returns> public static container getValue(KrishDimensionCacheScope _scope, container _key) { SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache(); return objectCache.find(KrishDimensionCache::getCacheScopeStr(_scope), _key); } /// <summary> /// Inserts the Krish cache value. /// </summary> /// <param name="_scope"> /// The Krish cache scope to insert. /// </param> /// <param name="_key"> /// The value key to insert. /// </param> /// <param name="_value"> /// The value to insert. /// </param> public static void insertValue(KrishDimensionCacheScope _scope, container _key, container _value) { SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache(); objectCache.insert(KrishDimensionCache::getCacheScopeStr(_scope), _key, _value); } /// <summary> /// Indicates whether the specified value key in the Krish cache exists. /// </summary> /// <param name="_scope"> /// The Krish cache scope of the Krish cache to check for existence. /// </param> /// <param name="_key"> /// The value key of the Krish cache to check for existence. /// </param> /// <returns> /// true if the specified value key exists; otherwise, false. /// </returns> public static boolean valueExists(KrishDimensionCacheScope _scope, container _key) { SysGlobalObjectCache objectCache = ClassFactory.globalObjectCache(); return (objectCache.find(KrishDimensionCache::getCacheScopeStr(_scope), _key) != connull()); } }
Now we will use this KrishDimensionCache class in our dimensionHelperclass method getDimensionDisplayValueByAttributeRecid to implement memoization.
In order to use such pattern only thing we need to identify is the key , value combination.
Dynamics 365 F&SCM Integration Developer/Technical Architect
2 年AFAIK this is named idempotence
Technical Architect at Hitachi Solutions | D365FinOps | Azure | LCS | Power platform | SnapLogic | MCP
4 年Interesting ! Will try this out for sure.
Dynamics 365 Finance and Operations
4 年Good one
Dynamics 365 Finance and SCM Technical Consultant
4 年Very useful post. Thanks for sharing.
Consultant Cloud
4 年Erdogan ??