Friday, September 28, 2018

FSharp Enum ToString vs. C# Enum Dictionary Lookup for Localization


I was reading Tao Liu's post regarding enums with space for localization to save time and I just had to run a test if this really was faster then defining a lookup table?


On my computer
--------------------------------------
... FsharpEnumToString vs. CSharpEnumDictionaryLookup
FSharpToString  Iterations: 10000000;  Average: 0,287 ticks; Total: 14374450,000 ticks;
CSharpLookup    Iterations: 10000000;  Average: 0,134 ticks; Total: 6688315,000 ticks;

So, a simple dictionary lookup is still faster then ToString of a F# enum.
So I'll guess my team will keep the localization as data files / database table that is loaded at runtime into a variable. This way we can outsource the translations to other teams and specialists while keeping the code clean. A misspelled enum member by a tired developer does not result in a hotfix of an application with new builds etc, just a data file update in runtime.


The code used to perform this test:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using fsharp.techdump;

namespace techdump.console.Tests
{
    public class FsharpEnumToStringVsLookup
    {
        private Dictionary<CSharpEnum, string> _lookup = new Dictionary<CSharpEnum, string>
        {
            { CSharpEnum.Registration, "Registration" },
            { CSharpEnum.UnderReview, "Under Review" },
            { CSharpEnum.Approval, "Approval" },
            { CSharpEnum.Release, "Release" },
            { CSharpEnum.PostRelase, "Post Release" }
        };
        private enum CSharpEnum
        {
            Registration = 0,
            UnderReview = 1,
            Approval = 2,
            Release = 3,
            PostRelase = 4,
        }
        public void Execute()
        {
            Log("--------------------------------------");
            Log("... FsharpEnumToString vs. CSharpEnumDictionaryLookup");
            ExecuteTest(10_000_000);
        }
        
        
        private void ExecuteTest(int iterations)
        {
            string s = string.Empty;
            try
            {
                var index = new List<int> { 0, 1, 2, 3, 4 };
                var fsharpToStringTimings = new RunningAverage();
                var csharpLookupTimings = new RunningAverage();
                for (int i = 0; i < iterations; i++)
                {
                    for (int dataIndex = 0; dataIndex < index.Count; dataIndex++)
                    {
                        var item = index[dataIndex];
                        var fsharpEnumMember = (Techdump.FsharpEnum)item;
                        fsharpToStringTimings.Add(TimeAction(() =>
                        {
                            s = item.ToString();
                        }));
                        if (!string.IsNullOrEmpty(s))
                            s = string.Empty;
                        var csharpEnumMember = (CSharpEnum)item;
                        csharpLookupTimings.Add(TimeAction(() =>
                        {
                            s = _lookup[csharpEnumMember];
                        }));
                        if (!string.IsNullOrEmpty(s))
                            s = string.Empty;
                    }
                }
                Log($"FSharpToString\tIterations:\t{iterations}\tAverage:\t{fsharpToStringTimings.Average:0.000}\tticks\tTotal:\t{fsharpToStringTimings.Total:0.000}\tticks");
                Log($"CSharpLookup\tIterations:\t{iterations}\tAverage:\t{csharpLookupTimings.Average:0.000}\tticks\tTotal:\t{csharpLookupTimings.Total:0.000}\tticks");
            }
            catch (Exception ex)
            {
                Log($"Fail\tDataCount\t2\tIterations:\t{iterations}\tFailed\t{ex.Message}");
            }
        }


        private float TimeAction(Action action)
        {
            var sw = Stopwatch.StartNew();
            action();
            sw.Stop();
            return sw.ElapsedTicks;
        }

        private void Log(string s)
        {
            Console.WriteLine(s);
            File.AppendAllText(@"c:\temp\enumToStringTest.txt", $"{s}{Environment.NewLine}");
        }
        
    }
    
}

FSharp Enum defined as follows:
namespace fsharp.techdump

module Techdump =
    type public FsharpEnum =
        Registration = 0
        | ``Under Review``=1
        | Approval = 2
        | Release = 3
        | ``Post Release`` = 4


And the RunningAverage class from a previous post.


No comments:

Post a Comment