Как стать автором
Обновить

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);


Материал не претендует на научность, но позитив от чтения приветствуется.
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.