• Avalanche.Core
Search Results for

    Show / Hide Table of Contents
    • Avalanche.Accessor
      • Introduction
      • IAccessor
        • IAccessor
        • IListAccessor
        • IMapAccessor
        • IRecordAccessor
        • IContentAccessor
        • IOneOfAccessor
        • IAnyAccessor
      • .Net
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
      • Protobuf
        • Introduction
      • Articles
        • Dependency Injection
        • AccessorMessages
    • Avalanche.Binding
      • Introduction
    • Avalanche.Core
      • License
    • Avalanche.DataType
      • Introduction
      • DataType
        • IDataType
        • IListType
        • IMapType
        • IRecordType
        • IFieldType
        • IOneOfType
        • IAnyType
        • IStringType
        • IValueType
        • IIntegerType
        • IEnumerationType
        • IRealType
      • .Net
        • Introduction
        • IList<T>
        • IDictionary<K,V>
        • FieldInfo
        • Enum
        • OneOfAttribute
        • StructLayoutAttribute
        • Class
      • Protobuf
        • Introduction
      • Articles
        • DataTypeRequest
        • PrintTree
        • DataTypeMessages
    • Avalanche.Emit
      • Introduction
      • TypeBuilder
      • ConstructorBuilder
      • MethodBuilder
      • PropertyBuilder
      • FieldBuilder
      • Emit
      • Utilities
    • Avalanche.FileSystem
      • Introduction
      • Abstractions
        • IFileSystem
          • IFileSystemBrowse
          • IFileSystemCreateDirectory
          • IFileSystemDelete
          • IFileSystemFileAttribute
          • IFileSystemMount
          • IFileSystemMove
          • IFileSystemObserve
          • IFileSystemOpen
        • IEvent
        • IEntry
        • IOption
        • IToken
      • FileSystem
      • VirtualFileSystem
      • MemoryFileSystem
      • EmbeddedFileSystem
      • HttpFileSystem
      • Decoration
      • IFileProvider
      • Events
      • Utilities
        • Dispose
        • File Scanner
        • Visit Tree
        • File Operation
    • Avalanche.Identity
      • Introduction
      • Identity
      • IdentityParts
      • IdentityInterner
      • IdentityComparer
      • Print Tree
      • IdentityAccessors
        • Introduction
        • TypeName
    • Avalanche.Localization
      • Introduction
      • Localization
      • LocalizationFile
      • LocalizationFiles
      • LocalizationFileSystem
      • LocalizationFileFormat
      • LocalizationLine
      • LocalizationLines
      • TemplateFormat
      • CultureProvider
      • FallbackCultureProvider
      • ResourceManager
      • LocalizationError
      • Microsoft.Extensions
        • Introduction
        • DependencyInjection
        • FileProvider
        • Logging
        • ITextLocalizer
        • IFileLocalizer
        • Localization
      • Asp.Net
        • Introduction
        • Supplying localization
        • Inject to pages
        • Culture Assigned
        • Minimalistic Api
        • Diagnostics
      • Pluralization
        • Introduction
        • Multiple plural parameters
        • Custom PluralRules
        • Invariant Culture
        • Unit Prefix
        • IPluralRule
        • IPluralNumber
        • IPluralRules
        • CLDRs
        • Unicode.CLDR40
        • Unicode.CLDR41
        • Unicode.CLDR42
      • Articles
        • Alphabet localization
        • Benchmarks
        • Caching
        • Class Library
        • Demo
        • Diagnostics
        • Embedded resources
        • Emplacement
        • File localization
        • Text localization
        • Printing templates
    • Avalanche.Message
      • Introduction
      • IMessage
      • IMessageProvider
      • IMessageDescription
      • IMessageDescriptions
      • MessageLevel
      • Message printing
      • Messages and Exceptions
      • Microsoft.Extensions
        • DependencyInjection
      • Articles
        • Aggregate Messages
        • Localization
        • Logging
        • Validation
    • Avalanche.Service
      • Introduction
      • Service
        • Introduction
        • IService
        • IServiceDisposable
        • IServiceDecoration
        • IServiceCast
        • IServiceObservable
        • IServiceContainer
        • Construction
        • Query
        • CancellationToken
        • CachePolicy
        • Scope
      • Handler
        • Introduction
        • IHandler
        • IHandlerCast
        • IHandlerDecoration
        • IHandlerWithOrder
        • CancellationToken
        • Cyclicity
        • Delegates
        • Invokable
        • ExportAttribute
        • OrderAttribute
        • PrintTree
        • Recursion
      • Query
        • Introduction
        • IQuery
        • IQueryCast
        • IQueryDecoration
      • Entry
        • Introduction
        • IEntry
        • IEntryCast
        • IEntryDecoration
        • IEntryObservable
        • IEntryVisitable
        • EntryState
      • Request
        • Introduction
        • IRequest
        • IRequestFor
        • IRequestToBeCached
        • IRequestToBeDisposed
        • RequestAttribute
        • ContextParameterAttribute
        • Print Tree
      • Dependency Injection
        • Introduction
        • Asp.Net
        • ServiceRequest<T>
        • Decorating a service
        • Handler
        • CachePolicy
        • CancellationToken
        • QueryLogger
      • Examples
        • NodeCount
        • Expression
        • Mapper
      • Articles
        • Benchmarks
        • Error Handling
        • ServiceMessages
    • Avalanche.StatusCode
      • Introduction
      • HResult
        • Introduction
        • HResult.Facilities
        • BasicMessages
        • RpcMessages
        • DispatchMessages
        • ItfMessages
        • Win32Messages
        • ClrMessages
      • System
        • Introduction
        • AccessControlMessages
        • AggregateMessages
        • AppDomainMessages
        • ArgumentMessages
        • ArgumentNullMessages
        • ArgumentOutOfRangeMessages
        • ArithmeticMessages
        • ArrayMessages
        • AssemblyMessages
        • BadImageFormatMessages
        • CodeContractMessages
        • CodePageMessages
        • CollectionsMessages
        • CompilerServiceMessages
        • CryptographyMessages
        • CultureMessages
        • DiagnosticsMessages
        • EventSourceMessages
        • ExecutionEngineMessages
        • FormatMessages
        • HostProtectionMessages
        • IOMessages
        • IndexOutOfRangeMessages
        • InteropServiceMessages
        • InvalidCastMessages
        • InvalidOperationMessages
        • IsolatedStorageMessages
        • LazyMessages
        • MarshalerMessages
        • MemoryMessages
        • MiscellaneousMessages
        • NotImplementedMessages
        • NotSupportedMessages
        • ObjectDisposedMessages
        • OperationCanceledMessages
        • OverflowMessages
        • PlatformMessages
        • PolicyMessages
        • PrincipalMessages
        • ProgramMessages
        • ReferenceMessages
        • ReflectionMessages
        • RegionMessages
        • RemotingMessages
        • ResourcesMessages
        • SecurityMessages
        • SerializationMessages
        • StackMessages
        • TaskMessages
        • TextMessages
        • ThreadingMessages
        • TimeZoneMessages
        • TypeMessages
        • XmlMessages
      • HttpStatusCode
      • OpcUaStatusCode
    • Avalanche.Template
      • Introduction
      • TemplateFormats
      • ITemplatePrintable
      • ITemplateFormatPrintable
      • ITemplateText
      • ITemplateBreakdown
      • ITemplateFormat
      • ITemplateFormats
      • Extract Arguments
      • Emplacement
    • Avalanche.Tokenizer
      • Introduction
      • IToken
      • ITokenizer
      • Tokenizers
    • Avalanche.Utilities
      • Introduction
      • Collections
        • Tuples
        • StructList
        • ArrayList
        • BijectionMap
        • LocakableDictionary
        • LockableList
        • MapList
        • Pipe
        • RingQueue
        • EnumerableExtensions
        • TupleUtilities
        • ArrayUtilities
      • Comparers
        • IGraphComparer
        • IGraphComparable
        • AlphaNumericComparer
        • EnumerableComparer
        • EnumerableGraphComparer
        • ReferenceComparer
        • KeyValuePairComparer
        • DefaultComparerProvider
        • RecordComparer
      • Cloners
        • ICloner
        • IGraphCloner
        • IGraphCloneable
        • ListCloner
        • DictionaryCloner
        • FieldCloner
        • PassthroughCloner
        • RecordCloner
        • ClonerProvider
      • Dispose
        • IDisposeAttachable
        • IDisposeBelatable
      • Provider
        • Introduction
        • ProviderBase
        • Delegate
        • Pipe
        • Cache
        • ResultCapture
        • AsReadOnly
        • AsService
        • IProviderEvent
      • Record
        • IRecordDescription
        • IFieldDescription
        • IConstructorDescription
        • IConstructionDescription
        • IParameterDescription
        • IRecordProviders
        • RecordDelegates
          • RecordCreate
          • RecordClone
          • RecordCopy
          • IRecordDelegates
        • FieldDelegates
          • FieldRead
          • FieldWrite
          • RecreateWith
          • IFieldDelegates
      • String
        • IEscaper
        • UnicodeString
      • Miscellaneous
        • IIdGenerator
        • Permutation
        • IReadOnly
        • IUserDataContainer
        • Void
    • Avalanche.Writer
      • Introduction
      • ConstantWriter
      • Context
      • ConvertWriter
      • DefaultConstructor
      • DelegateWriter
      • PassthroughWriter
      • Referer
      • TypeCast
      • Writer
      • WriterPipe
      • WriterMessages

    Benchmarks

    Avalanche.Localization is initialization heavy as resources are located, read and parsed on first use, albeit lazily. After initialization, texts are cached and are accessible with a dictionary lookup. In this article after-initialization performance is studied.

    LocalizedText

    ILocalizedText was ran with 0 and 1 parameters, with and without memory pool, and with and without pluralized parameter.

    Method Mean Error StdDev Gen 0 Allocated
    LocalizedTextZeroParameters 114.0 ns 1.03 ns 0.96 ns - -
    LocalizedTextZeroParametersMemoryPool 262.0 ns 1.52 ns 1.43 ns - -
    LocalizedTextOneParameter 319.5 ns 2.23 ns 2.09 ns 0.0105 88 B
    LocalizedTextOneParameterMemoryPool 458.0 ns 3.21 ns 3.00 ns - -
    LocalizedTextOnePluralizedParameter 278.7 ns 1.91 ns 1.79 ns 0.0076 64 B
    LocalizedTextOnePluralizedParameterMemoryPool 454.8 ns 2.73 ns 2.56 ns 0.0076 64 B

    Benchmark's localization initialization.

    ILocalization localization =
        Localization.CreateDefault()
        .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
        .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
        .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
        .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
        .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
    

    Zero parameter localization is a simple dictionary lookup and is zero heap operation.

    public void LocalizedTextZeroParameters()
    {
        // Localize text from cache
        ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.HelloWorld")];
        // Print without arguments
        string print = text.Print(null, null);
    }
    

    One parameter localization allocates a string for result, and the caller allocates an object[] for input.

    public void LocalizedTextOneParameter()
    {
        // Localize text from cache
        ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Welcome")];
        // Print with argument
        string print = text.Print(null, new object?[] { "User" });
    }
    

    One parameter localization can be processed zero heap, if the caller allocates arguments and result characters from memory pool.

    public void LocalizedTextOneParameterMemoryPool()
    {
        // Localize text from cache
        ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Welcome")];
        // Rent arguments
        object[] arguments = ArrayPool<object>.Shared.Rent(1);
        // Assign argument
        arguments[0] = "User";
        // Estimate length
        int length = text.EstimatePrintLength(arguments);
        // Rent chars
        char[] buffer = ArrayPool<char>.Shared.Rent(length);
        // Get span
        Span<char> bufferSpan = buffer.AsSpan();
        // Print to buffer
        length = text.PrintTo(bufferSpan, arguments);
        // Slice print
        ReadOnlySpan<char> print = bufferSpan[..length];
        // Return rentals
        ArrayPool<object>.Shared.Return(arguments);
        ArrayPool<char>.Shared.Return(buffer);
    }
    

    If a parameter is pluralized, the evaluation is about 3x-4x heavier. Features are extracted from the argument and features are evaluated against pluralization rules.

    public void LocalizedTextOnePluralizedParameter()
    {
        // Localize text from cache
        ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Apples")];
        // Create arguments
        object?[] arguments = new object?[] { "2" };
        // Print with arguments
        string print = text.Print(null, arguments);
    }
    

    Pluralization heap footprint is alleviated slightly (-64 bytes, -11%) if caller uses memory pool.

    public void LocalizedTextOnePluralizedParameterMemoryPool()
    {
        // Localize text from cache
        ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Apples")];
        // Rent arguments
        object[] arguments = ArrayPool<object>.Shared.Rent(1);
        // Assign argument
        arguments[0] = "2";
        // Localize & Pluralize to culture & arguments
        ITemplatePrintable printable = text.Pluralize(arguments);
        // Estimate length
        int length = printable.EstimatePrintLength(arguments);
        // Rent chars
        char[] buffer = ArrayPool<char>.Shared.Rent(length);
        // Get span
        Span<char> bufferSpan = buffer.AsSpan();
        // Print to buffer
        length = printable.PrintTo(bufferSpan, arguments);
        // Slice print
        ReadOnlySpan<char> print = bufferSpan[..length];
        // Return rentals
        ArrayPool<object>.Shared.Return(arguments);
        ArrayPool<char>.Shared.Return(buffer);
    }
    
    LocalizedTextBenchmark.cs
    using System.Buffers;
    using System.Globalization;
    using Avalanche.Localization;
    using Avalanche.Template;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    
    [SimpleJob(RuntimeMoniker.Net70), MemoryDiagnoser]
    public class LocalizedTextBenchmark
    {
        // <init>
        ILocalization localization =
            Localization.CreateDefault()
            .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
            .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
        // </init>
    
        [Benchmark]
        // <LocalizedTextZeroParameters>
        public void LocalizedTextZeroParameters()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.HelloWorld")];
            // Print without arguments
            string print = text.Print(null, null);
        }
        // </LocalizedTextZeroParameters>
    
        [Benchmark]
        // <LocalizedTextZeroParametersMemoryPool>
        public void LocalizedTextZeroParametersMemoryPool()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.HelloWorld")];
            // Assign arguments
            object?[]? arguments = null;
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizedTextZeroParametersMemoryPool>
    
        [Benchmark]
        // <LocalizedTextOneParameter>
        public void LocalizedTextOneParameter()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Welcome")];
            // Print with argument
            string print = text.Print(null, new object?[] { "User" });
        }
        // </LocalizedTextOneParameter>
    
        [Benchmark]
        // <LocalizedTextOneParameterMemoryPool>
        public void LocalizedTextOneParameterMemoryPool()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Welcome")];
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "User";
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizedTextOneParameterMemoryPool>
    
        [Benchmark]
        // <LocalizedTextOnePluralizedParameter>
        public void LocalizedTextOnePluralizedParameter()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Apples")];
            // Create arguments
            object?[] arguments = new object?[] { "2" };
            // Print with arguments
            string print = text.Print(null, arguments);
        }
        // </LocalizedTextOnePluralizedParameter>
    
        [Benchmark]
        // <LocalizedTextOnePluralizedParameterMemoryPool>
        public void LocalizedTextOnePluralizedParameterMemoryPool()
        {
            // Localize text from cache
            ILocalizedText text = localization.LocalizedTextCached[("fi", "Namespace.Apples")];
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "2";
            // Localize & Pluralize to culture & arguments
            ITemplatePrintable printable = text.Pluralize(arguments);
            // Estimate length
            int length = printable.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = printable.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizedTextOnePluralizedParameterMemoryPool>
    }
    

    LocalizableText

    ILocalizableText performance is similiar to the performance of ILocalizedText.

    Method Mean Error StdDev Gen 0 Allocated
    LocalizableTextZeroParameters 220.5 ns 1.62 ns 1.51 ns - -
    LocalizableTextZeroParametersMemoryPool 430.1 ns 4.18 ns 3.91 ns - -
    LocalizableTextOneParameter 436.4 ns 1.12 ns 0.88 ns 0.0105 88 B
    LocalizableTextOneParameterMemoryPool 654.6 ns 8.41 ns 7.86 ns - -
    LocalizableTextOnePluralizedParameter 418.4 ns 3.01 ns 2.82 ns 0.0076 64 B
    LocalizableTextOnePluralizedParameterMemoryPool 666.0 ns 4.14 ns 3.88 ns 0.0076 64 B
    LocalizableTextBenchmark.cs
    using System;
    using System.Buffers;
    using System.Globalization;
    using Avalanche.Localization;
    using Avalanche.Template;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    
    [SimpleJob(RuntimeMoniker.Net70), MemoryDiagnoser]
    public class LocalizableTextBenchmark
    {
        ILocalization localization =
            Localization.CreateDefault()
            .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
            .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
    
        CultureInfo _culture = CultureInfo.GetCultureInfo("fi");
    
        [Benchmark]
        // <LocalizableTextZeroParameters>
        public void LocalizableTextZeroParameters()
        {
            // Localizable text from cache
            ILocalizableText text = localization.LocalizableTextCached["Namespace.HelloWorld"];
            // Print without arguments
            string print = text.Print(_culture, null);
        }
        // </LocalizableTextZeroParameters>
    
        [Benchmark]
        // <LocalizableTextZeroParametersMemoryPool>
        public void LocalizableTextZeroParametersMemoryPool()
        {
            // Localizable text from cache
            ILocalizableText localizable = localization.LocalizableTextCached["Namespace.HelloWorld"];
            // Localize text from cache
            ILocalizedText text = localizable.Localize("fi")!.Value;
            // Assign arguments
            object?[]? arguments = null;
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizableTextZeroParametersMemoryPool>
    
        [Benchmark]
        // <LocalizableTextOneParameter>
        public void LocalizableTextOneParameter()
        {
            // Localizable text from cache
            ILocalizableText text = localization.LocalizableTextCached["Namespace.Welcome"];
            // Print with argument
            string print = text.Print(_culture, new object?[] { "User" });
        }
        // </LocalizableTextOneParameter>
    
        [Benchmark]
        // <LocalizableTextOneParameterMemoryPool>
        public void LocalizableTextOneParameterMemoryPool()
        {
            // Localizable text from cache
            ILocalizableText localizable = localization.LocalizableTextCached["Namespace.Welcome"];
            // Localize text from cache
            ILocalizedText text = localizable.Localize("fi")!.Value;        // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "User";
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizableTextOneParameterMemoryPool>
    
        [Benchmark]
        // <LocalizableTextOnePluralizedParameter>
        public void LocalizableTextOnePluralizedParameter()
        {
            // Localizable text from cache
            ILocalizableText text = localization.LocalizableTextCached["Namespace.Apples"];
            // Create arguments
            object?[] arguments = new object?[] { "2" };
            // Print with arguments
            string print = text.Print(_culture, arguments);
        }
        // </LocalizableTextOnePluralizedParameter>
    
        [Benchmark]
        // <LocalizableTextOnePluralizedParameterMemoryPool>
        public void LocalizableTextOnePluralizedParameterMemoryPool()
        {
            // Localizable text from cache
            ILocalizableText localizable = localization.LocalizableTextCached["Namespace.Apples"];
            // Localize text from cache
            ILocalizedText text = localizable.Localize("fi")!.Value;
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "2";
            // Localize & Pluralize to culture & arguments
            ITemplatePrintable printable = text.Pluralize(arguments);
            // Estimate length
            int length = printable.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = printable.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizableTextOnePluralizedParameterMemoryPool>
    
    }
    

    LocalizingText

    ILocalizingText performance is similiar to the performance of ILocalizedText.

    Method Mean Error StdDev Gen 0 Allocated
    LocalizingTextZeroParameters 247.3 ns 2.85 ns 2.67 ns - -
    LocalizingTextZeroParametersMemoryPool 418.7 ns 3.71 ns 3.47 ns - -
    LocalizingTextOneParameter 461.3 ns 4.01 ns 3.55 ns 0.0105 88 B
    LocalizingTextOneParameterMemoryPool 603.2 ns 8.13 ns 7.60 ns - -
    LocalizingTextOnePluralizedParameter 448.8 ns 5.18 ns 4.85 ns 0.0076 64 B
    LocalizingTextOnePluralizedParameterMemoryPool 650.3 ns 5.41 ns 5.06 ns 0.0076 64 B
    LocalizingTextBenchmark.cs
    using System.Buffers;
    using System.Globalization;
    using Avalanche.Localization;
    using Avalanche.Template;
    using Avalanche.Utilities;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    
    [SimpleJob(RuntimeMoniker.Net70), MemoryDiagnoser]
    public class LocalizingTextBenchmark
    {
        ILocalization localization =
            Localization.CreateDefault()
            .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
            .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
    
        CultureInfo _culture = CultureInfo.GetCultureInfo("fi");
        ICultureProvider cultureProvider = new CultureProvider("fi").SetReadOnly();
    
        [Benchmark]
        // <LocalizingTextZeroParameters>
        public void LocalizingTextZeroParameters()
        {
            // Localizing text from cache
            ILocalizingText text = localization.LocalizingTextCached[(cultureProvider, "Namespace.HelloWorld")];
            // Print without arguments
            string print = text.Print(_culture, null);
        }
        // </LocalizingTextZeroParameters>
    
        [Benchmark]
        // <LocalizingTextZeroParametersMemoryPool>
        public void LocalizingTextZeroParametersMemoryPool()
        {
            // Localizing text from cache
            ILocalizingText localizing = localization.LocalizingTextCached[(cultureProvider, "Namespace.HelloWorld")];
            // Localize text from cache
            ILocalizedText text = localizing.Value!.Value;
            // Assign arguments
            object?[]? arguments = null;
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizingTextZeroParametersMemoryPool>
    
        [Benchmark]
        // <LocalizingTextOneParameter>
        public void LocalizingTextOneParameter()
        {
            // Localizing text from cache
            ILocalizingText text = localization.LocalizingTextCached[(cultureProvider, "Namespace.Welcome")];
            // Print with argument
            string print = text.Print(_culture, new object?[] { "User" });
        }
        // </LocalizingTextOneParameter>
    
        [Benchmark]
        // <LocalizingTextOneParameterMemoryPool>
        public void LocalizingTextOneParameterMemoryPool()
        {
            // Localizing text from cache
            ILocalizingText localizing = localization.LocalizingTextCached[(cultureProvider, "Namespace.Welcome")];
            // Localize text from cache
            ILocalizedText text = localizing.Value!.Value;
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "User";
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizingTextOneParameterMemoryPool>
    
        [Benchmark]
        // <LocalizingTextOnePluralizedParameter>
        public void LocalizingTextOnePluralizedParameter()
        {
            // Localizing text from cache
            ILocalizingText text = localization.LocalizingTextCached[(cultureProvider, "Namespace.Apples")];
            // Create arguments
            object?[] arguments = new object?[] { "2" };
            // Print with arguments
            string print = text.Print(_culture, arguments);
        }
        // </LocalizingTextOnePluralizedParameter>
    
        [Benchmark]
        // <LocalizingTextOnePluralizedParameterMemoryPool>
        public void LocalizingTextOnePluralizedParameterMemoryPool()
        {
            // Localizing text from cache
            ILocalizingText localizing = localization.LocalizingTextCached[(cultureProvider, "Namespace.Apples")];
            // Localize text from cache
            ILocalizedText text = localizing.Value!.Value;
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "2";
            // Localize & Pluralize to culture & arguments
            ITemplatePrintable printable = text.Pluralize(arguments);
            // Estimate length
            int length = printable.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = printable.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </LocalizingTextOnePluralizedParameterMemoryPool>
    }
    

    StringLocalizer

    IStringLocalizer implementation allocates two heap objects, a string for the key, and another object for the LocalizedString result object. It's possible to drop heap allocation to zero if localizer is decorated with a Dictionary lookup.

    Method Mean Error StdDev Gen 0 Allocated
    StringLocalizerZeroParameters 115.1 ns 1.00 ns 0.93 ns 0.0134 112 B
    StringLocalizerOneParameter 363.4 ns 1.69 ns 1.58 ns 0.0229 192 B
    StringLocalizerOnePluralizedParameter 325.4 ns 4.42 ns 4.14 ns 0.0200 168 B
    StringLocalizerBenchmark.cs
    using Avalanche.Localization;
    using Avalanche.Localization.Internal;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    using Microsoft.Extensions.Localization;
    
    [SimpleJob(RuntimeMoniker.Net70), MemoryDiagnoser]
    public class StringLocalizerBenchmark
    {
        ILocalization localization =
            Avalanche.Localization.Localization.CreateDefault()
            .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
            .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
    
        IStringLocalizer localizer;
    
        public StringLocalizerBenchmark()
        {
            localizer = new DiStringLocalizer(localization, "Namespace", "fi");
        }
    
        [Benchmark]
        // <StringLocalizerZeroParameters>
        public void StringLocalizerZeroParameters()
        {
            // Localize text
            LocalizedString text = localizer["HelloWorld"]!;
            // Print
            string print = text.Value;
        }
        // </StringLocalizerZeroParameters>
    
        [Benchmark]
        // <StringLocalizerOneParameter>
        public void StringLocalizerOneParameter()
        {
            // Localize text
            LocalizedString text = localizer["Welcome", "User"]!;
            // Print
            string print = text.Value;
        }
        // </StringLocalizerOneParameter>
    
        [Benchmark]
        // <StringLocalizerOnePluralizedParameter>
        public void StringLocalizerOnePluralizedParameter()
        {
            // Localize text
            LocalizedString text = localizer["Apples", "2"]!;
            // Print
            string print = text.Value;
        }
        // </StringLocalizerOnePluralizedParameter>
    
    }
    

    TextLocalizer

    ITextLocalizer implementation allocates a string for the key. It's possible to drop heap allocation to zero if localizer is decorated with a Dictionary lookup.

    Method Mean Error StdDev Gen 0 Allocated
    TextLocalizerZeroParameters 212.5 ns 1.01 ns 0.79 ns 0.0076 64 B
    TextLocalizerZeroParametersMemoryPool 356.5 ns 3.85 ns 3.41 ns 0.0076 64 B
    TextLocalizerOneParameter 408.3 ns 1.51 ns 1.26 ns 0.0172 144 B
    TextLocalizerOneParameterMemoryPool 539.2 ns 1.66 ns 1.55 ns 0.0067 56 B
    TextLocalizerOnePluralizedParameter 394.3 ns 2.72 ns 2.54 ns 0.0143 120 B
    TextLocalizerOnePluralizedParameterMemoryPool 577.0 ns 6.63 ns 6.20 ns 0.0143 120 B
    TextLocalizerBenchmark.cs
    using System.Buffers;
    using Avalanche.Localization;
    using Avalanche.Template;
    using Avalanche.Utilities;
    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    
    [SimpleJob(RuntimeMoniker.Net70), MemoryDiagnoser]
    public class TextLocalizerBenchmark
    {
        ILocalization localization =
            Localization.CreateDefault()
            .AddLine("fi", "Namespace.HelloWorld", "Brace", "Hei maailma")
            .AddLine("fi", "Namespace.Welcome", "Brace", "Tervetuloa, {User}.")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on ei ole omenoita.", "Unicode.CLDR41", "0:cardinal:zero")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on yksi omena.", "Unicode.CLDR41", "0:cardinal:one")
            .AddLine("fi", "Namespace.Apples", "Brace", "Sinulla on {0} omenaa.", "Unicode.CLDR41", "0:cardinal:other");
    
        ICultureProvider cultureProvider = new CultureProvider("fi").SetReadOnly();
        ITextLocalizer localizer;
    
        public TextLocalizerBenchmark()
        {
            localizer = new TextLocalizer(localization, cultureProvider, "Namespace");
        }
    
        [Benchmark]
        // <TextLocalizerZeroParameters>
        public void TextLocalizerZeroParameters()
        {
            // Localize text
            ILocalizedText text = localizer["HelloWorld"]!;
            // Print without arguments
            string print = text.Print(null, null);
        }
        // </TextLocalizerZeroParameters>
    
        [Benchmark]
        // <TextLocalizerZeroParametersMemoryPool>
        public void TextLocalizerZeroParametersMemoryPool()
        {
            // Localize text
            ILocalizedText text = localizer["HelloWorld"]!;
            // Assign arguments
            object?[]? arguments = null;
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </TextLocalizerZeroParametersMemoryPool>
    
        [Benchmark]
        // <TextLocalizerOneParameter>
        public void TextLocalizerOneParameter()
        {
            // Localize text
            ILocalizedText text = localizer["Welcome"]!;
            // Print with argument
            string print = text.Print(null, new object?[] { "User" });
        }
        // </TextLocalizerOneParameter>
    
        [Benchmark]
        // <TextLocalizerOneParameterMemoryPool>
        public void TextLocalizerOneParameterMemoryPool()
        {
            // Localize text
            ILocalizedText text = localizer["Welcome"]!;
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "User";
            // Estimate length
            int length = text.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = text.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </TextLocalizerOneParameterMemoryPool>
    
        [Benchmark]
        // <TextLocalizerOnePluralizedParameter>
        public void TextLocalizerOnePluralizedParameter()
        {
            // Localize text
            ILocalizedText text = localizer["Apples"]!;
            // Create arguments
            object?[] arguments = new object?[] { "2" };
            // Print with arguments
            string print = text.Print(null, arguments);
        }
        // </TextLocalizerOnePluralizedParameter>
    
        [Benchmark]
        // <TextLocalizerOnePluralizedParameterMemoryPool>
        public void TextLocalizerOnePluralizedParameterMemoryPool()
        {
            // Localize text
            ILocalizedText text = localizer["Apples"]!;
            // Rent arguments
            object[] arguments = ArrayPool<object>.Shared.Rent(1);
            // Assign argument
            arguments[0] = "2";
            // Localize & Pluralize to culture & arguments
            ITemplatePrintable printable = text.Pluralize(arguments);
            // Estimate length
            int length = printable.EstimatePrintLength(arguments);
            // Rent chars
            char[] buffer = ArrayPool<char>.Shared.Rent(length);
            // Get span
            Span<char> bufferSpan = buffer.AsSpan();
            // Print to buffer
            length = printable.PrintTo(bufferSpan, arguments);
            // Slice print
            ReadOnlySpan<char> print = bufferSpan[..length];
            // Return rentals
            ArrayPool<object>.Shared.Return(arguments);
            ArrayPool<char>.Shared.Return(buffer);
        }
        // </TextLocalizerOnePluralizedParameterMemoryPool>
    
    }
    

    MemoryPool vs Heap

    The usage of memory pool is seemingly slightly slower than allocating short lived heap objects.

    However, benchmark does not count in the CPU cycles that are expended on garbage collection in parallel thread. So the numbers for the non-pooled cases are not entirely complete, atleast not comparable with the pooled cases.

    In a server environment, where all threads are congested, I'd wager that array pool solution is marginally more efficient, but in typical use cases, array pooling is not worth the effort.

    Other

    CPU cycles on pluralization cases dropped by 70% when moving from .NET 6 to .NET 7. There are some significant improvements under the hood.

    Benchmarks were ran on Ryzen 3950x 16c32t.

    In This Article
    Back to top Copyright © Toni Kalajainen