4

All, a while back (when I was rushed off my feet) I asked the following question Performace Overheads when Using Resource Files (.resx) regarding the performance overhead of using resource strings. I got an up-voted answer and took the answer to be correct. However, before, I was localising message strings which are called in error conditions and not performance critical - now I have been asked to implement localisation to our codes 'power-house' (lots of performance critical code, embedded loops etc.).

Having some time to look into this in more detail and I noticed that calling a resource like

Resources.MessageStrings.SomeResourceName

merely refers the call to the auto-generated code MessageStrings.Designer.cs, which uses

internal static string SomeResourceName {
    get {
        return ResourceManager.GetString("SomeResourceName", resourceCulture);}
}

So digging deeper, I thought I would decompile ResourceManager which is found in

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.5\mscorlib.dll

To see what GetString() was doing [Is it really caching my resource strings?]. Decompiling, I find

[__DynamicallyInvokable]
public virtual string GetString(string name, CultureInfo culture)
{
  if (name == null)
    throw new ArgumentNullException("name");
  if (ResourceManager.s_IsAppXModel && object.ReferenceEquals((object) culture, (object) CultureInfo.CurrentUICulture))
    culture = (CultureInfo) null;
  if (this._bUsingModernResourceManagement)
  {
    if (this._PRIonAppXInitialized)
      return this.GetStringFromPRI(name, culture == null ? (string) null : culture.Name, this._neutralResourcesCulture.Name);
    if (this._PRIExceptionInfo == null || this._PRIExceptionInfo._PackageSimpleName == null || this._PRIExceptionInfo._ResWFile == null)
      throw new MissingManifestResourceException(Environment.GetResourceString("MissingManifestResource_NoPRIresources"));
    throw new MissingManifestResourceException(Environment.GetResourceString("MissingManifestResource_ResWFileNotLoaded", (object) this._PRIExceptionInfo._ResWFile, (object) this._PRIExceptionInfo._PackageSimpleName));
  }
  else
  {
    if (culture == null)
      culture = Thread.CurrentThread.GetCurrentUICultureNoAppX();
    if (FrameworkEventSource.IsInitialized)
      FrameworkEventSource.Log.ResourceManagerLookupStarted(this.BaseNameField, this.MainAssembly, culture.Name);
    ResourceSet resourceSet1 = this.GetFirstResourceSet(culture);
    if (resourceSet1 != null)
    {
      string @string = resourceSet1.GetString(name, this._ignoreCase);
      if (@string != null)
        return @string;
    }
    foreach (CultureInfo culture1 in new ResourceFallbackManager(culture, this._neutralResourcesCulture, true))
    {
      ResourceSet resourceSet2 = this.InternalGetResourceSet(culture1, true, true);
      if (resourceSet2 != null)
      {
        if (resourceSet2 != resourceSet1)
        {
          string @string = resourceSet2.GetString(name, this._ignoreCase);
          if (@string != null)
          {
            if (this._lastUsedResourceCache != null)
            {
              lock (this._lastUsedResourceCache)
              {
                this._lastUsedResourceCache.lastCultureName = culture1.Name;
                this._lastUsedResourceCache.lastResourceSet = resourceSet2;
              }
            }
            return @string;
          }
          else
            resourceSet1 = resourceSet2;
        }
      }
      else
        break;
    }
    if (FrameworkEventSource.IsInitialized)
      FrameworkEventSource.Log.ResourceManagerLookupFailed(this.BaseNameField, this.MainAssembly, culture.Name);
    return (string) null;
  }
}

Nothing in the above code suggests that it is 'caching' my strings (in the typical/truest sense of the word), it seems it is doing a complex look-up of some type. I noticed that the method was using the undocumented __DynamicallyInvokable attribute, and found a breaf discussion of this attribute by Hans (What is the __DynamicallyInvokable attribute for?).

My question is: For performance critical code can I rely on the ResourceManager being fast enough (does it cache my strings?), or do I need to pre-process and cache the resource strings myself?

Thanks for your time.

Community
  • 1
  • 1
MoonKnight
  • 23,214
  • 40
  • 145
  • 277

2 Answers2

7

The ressources are cached. If you follow the call stack through the resource manager it is like this: 1.

[System.Security.SecuritySafeCritical]  // auto-generated 
public virtual String GetString(String name, CultureInfo culture) {
  //...
  String value = rs.GetString(name, _ignoreCase);
  //...
}

2.

public virtual string GetString(string name, bool ignoreCase)
{
  object objectInternal = this.GetObjectInternal(name);
  //...
}

3.

private object GetObjectInternal(string name)
{
  //...
  Hashtable hashtable = this.Table;
  //...
  return hashtable[(object) name];
}

So at this point the values is read from the hashtable.

The hashtable is filled once the ressource file is accessed:

Constructor:

[SecuritySafeCritical]
public ResourceSet(string fileName)
{
  this.Reader = (IResourceReader) new ResourceReader(fileName);
  this.CommonInit();
  this.ReadResources();
}

and the ReadResources:

protected virtual void ReadResources()
{
  IDictionaryEnumerator enumerator = this.Reader.GetEnumerator();
  while (enumerator.MoveNext())
  {
    object obj = enumerator.Value;
    this.Table.Add(enumerator.Key, obj);
  }
}
DerApe
  • 3,097
  • 2
  • 35
  • 55
  • @Killercam glad I could help, was on the wrong track first too =) – DerApe Apr 10 '13 at 14:29
  • 1
    @Killercam That's the hash table I was talking about in [my last comment to my answer here](http://stackoverflow.com/questions/15521204/performace-overheads-when-using-resource-files-resx/15521524#15521524) but derape has posted the actual code, which is very helpful. :) – Matthew Watson Apr 10 '13 at 14:33
  • @Killercam your code does not contain the call. It should be called in the Method which is called in your code at this line: `string @string = resourceSet1.GetString(name, this._ignoreCase);` – DerApe Apr 11 '13 at 06:26
  • Note that this is not required. The code I have above uses the auto-generated code that is in `resource.Designer.cs`. The property I have shown above calls the code you have shown in your answer... – MoonKnight Apr 11 '13 at 08:13
2

The things that are being held in memory are things like the ResourceSets that it's working with, and those seem to directly hold the strings. You don't necessarily see it inside this method because at this point, every call could be coming in for different resource names and different cultures.

And, as always, is this where your profiling has told you that you have a performance issue? It's rarely, if ever, a good idea to try to optimize code by guessing at places that might be inefficient.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • +1 Thanks for your time. I agree about the fact that it is not a good ides to guess where bottle neck are going to be. However, it _is_ good practice to optimise your code where you can in the first place, reducing the number of areas of concern in the first instance... – MoonKnight Apr 10 '13 at 14:25