Pull to refresh

C# .ToString() – неявное ускорение

Приветствую!

Сразу хочется скачать, что речь пойдет исключительно о личном мнении и практике, которая помогла ускорить то, о чем ранее как-то не сильно приходилось задумываться.

Работая над проектом с многочисленными вычислениями и необходимостью приводить результаты к стринг, довелось делать различные замеры и проверки того, что там и как работает.

Благодаря небезызвестному доттрейсу, удалось заметить некоторое «оттягивание» времени на .ToString().

Обычная привычка, наверное, многим знакомая, если вдруг что, пишем:

const int i = 424242;
string str = i.ToString();

Console.WriteLine(str);

И лично я не задумывался над тем, что такой подход при многократных выполнениях может быть медлительным.

Понятное дело, что речь не идет о слишком существенных «тормозах», но если можно как-то просто оптимизировать с виду вполне оптимизированный метод, почему бы и да?

Для проверки нескольких гипотез были сделаны следующие методы:

Тестовые методы

public class ToStringTest
    {
        public const int MaxCount = 1000000;
        public string a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8, a_9, a_10;

        [Benchmark(Description = "CultureInfoIn")]
        public void CultureInfoIn()
        {
            for (int i = 0; i < MaxCount; i++)
            {
                a_1 = i.ToString(CultureInfo.CurrentCulture);
                a_2 = i.ToString(CultureInfo.CurrentCulture);
                a_3 = i.ToString(CultureInfo.CurrentCulture);
                a_4 = i.ToString(CultureInfo.CurrentCulture);
                a_5 = i.ToString(CultureInfo.CurrentCulture);
                a_6 = i.ToString(CultureInfo.CurrentCulture);
                a_7 = i.ToString(CultureInfo.CurrentCulture);
                a_8 = i.ToString(CultureInfo.CurrentCulture);
                a_9 = i.ToString(CultureInfo.CurrentCulture);
                a_10 = i.ToString(CultureInfo.CurrentCulture);
            }
        }

        [Benchmark(Description = "CultureInfoOut")]
        public void CultureInfoOut()
        {
            CultureInfo culture = CultureInfo.CurrentCulture;
            for (int i = 0; i < MaxCount; i++)
            {
                a_1 = i.ToString(culture);
                a_2 = i.ToString(culture);
                a_3 = i.ToString(culture);
                a_4 = i.ToString(culture);
                a_5 = i.ToString(culture);
                a_6 = i.ToString(culture);
                a_7 = i.ToString(culture);
                a_8 = i.ToString(culture);
                a_9 = i.ToString(culture);
                a_10 = i.ToString(culture);
            }
        }

        [Benchmark(Description = "NumberFormatInfoIn")]
        public void NumberFormatInfoIn()
        {
            for (int i = 0; i < MaxCount; i++)
            {
                a_1 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_2 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_3 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_4 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_5 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_6 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_7 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_8 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_9 = i.ToString(NumberFormatInfo.CurrentInfo);
                a_10 = i.ToString(NumberFormatInfo.CurrentInfo);
            }
        }

        [Benchmark(Description = "NumberFormatInfoOut")]
        public void NumberFormatInfoOut()
        {
            NumberFormatInfo culture = NumberFormatInfo.CurrentInfo;
            for (int i = 0; i < MaxCount; i++)
            {
                a_1 = i.ToString(culture);
                a_2 = i.ToString(culture);
                a_3 = i.ToString(culture);
                a_4 = i.ToString(culture);
                a_5 = i.ToString(culture);
                a_6 = i.ToString(culture);
                a_7 = i.ToString(culture);
                a_8 = i.ToString(culture);
                a_9 = i.ToString(culture);
                a_10 = i.ToString(culture);
            }
        }

        [Benchmark(Description = "ToString")]
        public void DefaultToString()
        {
            for (int i = 0; i < MaxCount; i++)
            {
                a_1 = i.ToString();
                a_2 = i.ToString();
                a_3 = i.ToString();
                a_4 = i.ToString();
                a_5 = i.ToString();
                a_6 = i.ToString();
                a_7 = i.ToString();
                a_8 = i.ToString();
                a_9 = i.ToString();
                a_10 = i.ToString();
            }
        }
    }


Далее пошли запуски на разных компьютерах и вот какие получены результаты:

Результаты тестов
image

image

image

image

Основная суть проблемы производительности при «а напишу как обычно, чего такого?» заключается в мелочах, подсказанных трейсером и кодами от товарищей из Майкрософта.

Если не передавать никаких аргументов, то выполняется неприятное дело (за нас все решают):

[System.Security.SecuritySafeCritical]
        [Pure]
        public override String ToString() {
            Contract.Ensures(Contract.Result<String>() != null);
            return Number.FormatInt32(m_value, null, NumberFormatInfo.CurrentInfo);
        }


public static NumberFormatInfo CurrentInfo {
            get {
                System.Globalization.CultureInfo culture = System.Threading.Thread.CurrentThread.CurrentCulture;
                if (!culture.m_isInherited) {
                    NumberFormatInfo info = culture.numInfo;
                    if (info != null) {
                        return info;
                    }
                }
                return ((NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)));
            }
        }

В подобной ситуации будут дополнительные накладные расходы на все, на чем только можно и очень хочется избегать подобных ситуаций, ведь оказалось, что это крайне просто – делаем выбор сами, имеем право.

При этом интересна реализация перегрузки:

[Pure]
        [System.Security.SecuritySafeCritical]
        public String ToString(IFormatProvider provider) {
            Contract.Ensures(Contract.Result<String>() != null);
            return Number.FormatInt32(m_value, null, NumberFormatInfo.GetInstance(provider));
        }

И вот тут я нашел то, что лично для меня показалось крайне удачным решением и, откровенно говоря, неявным ускорением о котором вообще не было мыслей:


        public static NumberFormatInfo GetInstance(IFormatProvider formatProvider) {
            // Fast case for a regular CultureInfo
            NumberFormatInfo info;
            CultureInfo cultureProvider = formatProvider as CultureInfo;
            if (cultureProvider != null && !cultureProvider.m_isInherited) {
                info = cultureProvider.numInfo;
                if (info != null) {
                    return info;
                }
                else {
                    return cultureProvider.NumberFormat;
                }
            }
            // Fast case for an NFI;
            info = formatProvider as NumberFormatInfo;
            if (info != null) {
                return info;
            }
            if (formatProvider != null) {
                info = formatProvider.GetFormat(typeof(NumberFormatInfo)) as NumberFormatInfo;
                if (info != null) {
                    return info;
                }
            }
            return CurrentInfo;
        }

А CultureInfo то проверяется первым, значит, есть вероятность выиграть слегка времени еще и на этом моменте, потому были сделаны те методы, которые были сделаны для тестов.

Стоит обратить внимание

        // Сводка:
        //     Возвращает или задает объект System.Globalization.CultureInfo, представляющий
        //     язык и региональные параметры, используемые текущим потоком.
        //
        // Возврат:
        //     Объект, представляющий язык и региональные параметры, используемые текущим потоком.
        //
        // Исключения:
        //   T:System.ArgumentNullException:
        //     Для свойства задано значение null.
        public static CultureInfo CurrentCulture { get; set; }


Для себя я сделал вывод, что выгодней всего делать следующим образом:

Результат

            const int i = 424242;
            CultureInfo culture = CultureInfo.CurrentCulture;
            string str = i.ToString(culture);

            Console.WriteLine(str);


Материал не претендует на научность, но позитив от чтения приветствуется.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.
Change theme settings