(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Производительность .Net миф или фантастика? (исходники)

Источник: GOT DOT NET
Nimnul

Для преобразования символов в нижний регистр мы используем паттерн «Интерпретатор», реализованный как синглтон и создаваемый с помощью фабрики классов» - такие фразы нам приходится слышать на семинарах для программистов. А что будет, если сравнить эту фразу вот с такой: «Для преобразования символов в нижний регистр необходимо вычесть дельту между началами регистровых алфавитов.

Философия

Что, по-вашему, будет работать быстрее? К сожалению, большинство современных программистов, выросших на Visual Studio 2003 и старше, все реже задают себе тот вопрос. Они считают, что современные процессоры имеют настолько большую тактовую частоту, а у компьютеров есть так много памяти, что более нет нужды уделять внимание производительности в процессе написания программы. Более того, многие программисты сознательно используют языковые конструкции, которые работают медленнее других, но, по их мнению, являются более удобными в использовании. Цель этой статьи - рассказать о том, что любое удобство программирования происходит за счет производительности программы. На мой взгляд, все современные разработчики ПО делятся на две большие группы. Первая группа программистов умеет придумывать алгоритмы, а вторая не умеет (зато владеет техникой использования чужих). Эта ситуация поддерживается Microsoft, которая выпускает все более и более высокоуровневые классы на все случаи жизни. В итоге программисту уже не нужно уметь создавать - ему нужно уметь использовать. В качестве примера рассмотрим так называемую концепцию трехуровневой бизнес-модели. Первый уровень - базы данных. Все, что нужно уметь - это читать и записывать данные в БД. Далее идет уровень классов. Здесь программисту нужно копировать прочитанные данные в классы, которые составляют логику приложения. Третьим идет уровень пользовательского интерфейса (GUI), здесь разработчику нужно только копировать данные из классов в компоненты, чтобы пользователь мог с ними взаимодействовать. По сути, такой подход к разработке софта более всего напоминает копирование файлов из одних папок в другие, и таким программистом может стать любой продвинутый пользователь. Кстати говоря, с выходом Visual Studio 2005, трехуровневая модель упростилась до одноуровневой. Теперь программист может с помощью мыши указать в DataView запрос к базе данных, а все остальное за негосделает контрол. Все было бы хорошо, но если речь идет о ASP.NET, то такой упрощенный подход приводит к значительным потерям производительности. Например, медленная загрузка страницы из-за неоправданно большого количества запросов к БД или (а часто и) большого веса страницы. К счастью, у этой тенденции развития отрасли есть существенный недостаток. Чем более высокоуровневыми становятся конструкции, тем больше сужается область их применения. В этой статье мы рассмотрим несколько примеров, которые демонстрируют ситуацию.

Перечисления

Недавно на моем любимом форуме по программированию я увидел сообщение от начинающего программиста на тему: «C# и перечисления». Дело в том, что спрашивающий не знал, как выделить определенный бинарный флаг из числа. Напомню, что флаги в числе представляют собой порядковый бит в бинарном представлении числа. Например, число 9 (в бинарном представлении 1001), обозначает, что установлен нулевой и третий флаг. Вопрос закономерен для начинающего разработчика, но каково было мое удивление, когда я увидел ответы от авторитетнейших программистов, aвторов статей, и обладателя MVP! Рассмотрим два ответа:

        public enum TaskStatus
        {
            Terminated = 1,
            Stoped = 2,
            Idle = 4,
            Working = 8,
            Stopping = 16
        }        
        
        // #1
        static bool StatusIn1(TaskStatus enm, params TaskStatus[] enms)
        {
            foreach (TaskStatus tEnum in enms)
            {
                if (enm != tEnum) return true;
            }
            return false;
        }
        //#2
        static bool StatusIn2(TaskStatus enm, params TaskStatus[] enms)
        {
            return (Array.IndexOf(enms, enm) != -1);
        }

Что же тут можно сказать? Мне становитсяне по себе от попытки представить скорость программ, которые пишут эти специалисты, превращая атомарные операции в циклы. Между прочим, второй ответ (от MVP) даже МЕНЕЕ производителен не смотря на кажущуюся простоту, поскольку IndexOf вызывает еще одну функцию, где в цикле ведется поиск необходимого значения. Ответ на данный вопрос должен быть, безусловно, таким:

         TaskStatus s = TaskStatus.Stoped / TaskStatus.Terminated;
         bool f = (TaskStatus.Stoped & s) == TaskStatus.Stoped;

Не будем голословными и посмотрим на результат теста.

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace test1
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 10000000;
            TaskStatus s = TaskStatus.Stoped / TaskStatus.Terminated;
            Stopwatch sw = new Stopwatch();
            // #1
            {
                sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    bool f = StatusIn1(TaskStatus.Stoped, s);
                }
                sw.Stop();
                Console.WriteLine("#1 - " + sw.ElapsedMilliseconds);
            }
            // #2
            {
                sw.Reset(); sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    bool f = StatusIn2(TaskStatus.Stoped, s);
                }
                sw.Stop();
                Console.WriteLine("#2 - " + sw.ElapsedMilliseconds);
            }
            // #3
            {
                sw.Reset(); sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    bool f = (TaskStatus.Stoped & s) == TaskStatus.Stoped;
                }
                sw.Stop();
                Console.WriteLine("#3 - " + sw.ElapsedMilliseconds);
            }
            Console.ReadLine();
        }
        static bool StatusIn1(TaskStatus enm, params TaskStatus[] enms)
        {
            foreach (TaskStatus tEnum in enms)
            {
                if (enm != tEnum) return true;
            }
            return false;
        }
        static bool StatusIn2(TaskStatus enm, params TaskStatus[] enms)
        {
            return (Array.IndexOf(enms, enm) != -1);
        }
    }
    public enum TaskStatus
    {
        Terminated = 1,
        Stoped = 2,
        Idle = 4,
        Working = 8,
        Stopping = 16
    }
}
  1. 439
  2. 378
  3. 8

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

Циклы

Рассмотрим два вида циклов: for и forearch. Сначало сделаем два теста, что бы потом обсудить результаты. Тест скорости перебора:

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace test4
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 10000000;
            Stopwatch sw = new Stopwatch();
            list = new List<int>(count);
            for (int i = 0; i < count; ++i)
            {
                list.Add(i);
            }
            sw.Start();
            t_for();
            sw.Stop();
            Console.WriteLine("for - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            t_foreach();
            sw.Stop();
            Console.WriteLine("foreach - " + sw.ElapsedMilliseconds);
            Console.ReadLine();
        }
        static List<int> list;
        static void t_for()
        {
            for (int i = 0; i < list.Count; ++i)
            {
                int a = list[i] + 1;
            }
        }
        static void t_foreach()
        {
            foreach (int var in list)
            {
                int a = var + 1;
            }
        }
    }
}
  1. for - 49
  2. foreach - 118

Теперь немного изменим тест так, чтобы определить, сколько нужно ресурсов для начала каждого из циклов. Перед запуском теста рекомендую открыть диспетчер задачна вкладке Perfomance.

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
namespace test4
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 10000000;
            Stopwatch sw = new Stopwatch();
            list = new List<int>();
            list.Add(1);
            sw.Start();
            
            for (int i = 0; i < count; ++i)
            {
                t_for();
            }
            sw.Stop();
            Console.WriteLine("for - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            for (int i = 0; i < count; ++i)
            {
                t_foreach();
            }
            sw.Stop();
            Console.WriteLine("foreach - " + sw.ElapsedMilliseconds);
            Console.ReadLine();
        }
        static List<int> list;
        static void t_for()
        {
            for (int i = 0; i < list.Count; ++i)
            {
                int a = list[i] + 1;
            }
        }
        static void t_foreach()
        {
            foreach (int var in list)
            {
                int a = var + 1;
            }
        }
    }
}
  1. for - 156
  2. foreach - 1246

Сделаем небольшие выводы. Во-первых, скорость перечисления foreach вдвое ниже, чем for, а во-вторых, для старта foreach требуется почти в десять раз больше времени. Кроме того, в диспетчере задач ясно видно, что затрачивается 40 мегабайт памяти (диспетчер задач это не самое точное средство для измерения количества необходимой памяти, но для обнаружения слона микроскоп и не нужен). Почему же так происходит? В случае с циклом foreach перед работой создается специальный класс-перечислитель, именно на его создание и тратится столько памяти. В случае с циклом for память не затрачивается вовсе. Внимательный читатель, может сказать: «Подумаешь, проблема высосана из пальца, поскольку этот тест на десять миллионов итераций, а в реальной программе так не бывает». На самом деле, бывают ситуации, когда программа делает частые вызовы функции, которая что-то делает в небольших циклах (от одной до ста итераций). Таким образом, эти десять миллионов вызовов будут достигнуты уже через двадцать минут… а если программа работает четыре часа? Тем не менее, в мире существует множество программистов, считающих, что такие затраты оправданы (надуманным) удобством использования foreach. Я же предлагаю читателю самому убедиться, что синтаксис практически идентичен:

            List<int> list = new List<int>();
            for (int i = 0; i < list.Count; ++i)
            {
                int a = list[i] + 1;
            }
            foreach (int var in list)
            {
                int a = var + 1;
            }

На этом недостатки foreach не заканчиваются, существуют еще проблемы архитектурного характера. Предположим, я делаю какой-нибудь класс, содержащий только итератор последовательного доступа (итератор необходим для работы циклов foreach). Другой программист, используя мой класс, может получить некоторые данные только последовательно. Но что если ему потребовался доступ в обратном порядке? Он будет просить меня сделать еще один итератор обратного доступа. Сам он это сделать не сможет либо потому, что ему долго придется вникать в логику класса, либо потому, что ему недоступен код. Хорошо, я сделаю ему… но другому программисту может потребоваться доступ через один элемент, через два или через три элемента… и что теперь, мне нужно для каждого делать эти итераторы? Другое дело индексатор, который используется в циклах for, преимущество его в том, что пользователь индексатора сам определит, в каком виде ему получить данные. В этом заключается большая гибкость, что еще раз подтверждает мои слова о том, что чем более высокоуровневая конструкция, тем меньше ее область применения.

Объектно-ориентированное программирование

Как известно, изначально языки программирования были структурного типа и отлично подходили для написания маленьких программ. Их недостаток проявился, когда появилась необходимость написания больших программ. Проблема в том, что структурный язык совершенно не предполагает каких-либо стандартов программирования, поэтому в результате работы нескольких программистов над проектом, в коде получался бардак, и развивать такую программу было чрезвычайно сложно. Более того, поиск и выявление даже простейшей логической ошибки был довольно трудоемким занятием. Объектно-ориентированное программирование (ООП) пришло на смену структурноориентированному, принеся тем самым некоторое удобство, а следовательно, и потерю производительности. Поверьте, я вовсе не противник ООП, но как говорил Будда: «Если струну дернуть слишком сильно, тогда она порвется, если слишком слабо, тогда никто не услышит звука, поэтому важно знать золотую середину». Однажды мне довелось видеть код, содержащий огромный вспомогательный класс, и если требовалось вызвать из него какуюнибудь функцию, автор программы всегда создавал его экземпляр. Представьте, какое это нерациональное использование памяти» В C# есть возможность создавать статические методы, она осталось в наследство от структурного прошлого языка C. Именно такие методы идеально подходят для вспомогательных функций, так как при их вызове нет необходимости создавать классы. В практике встречаются похожие задачи, для решения которых программистам нужно писать аналогичный код, а если этот код уже написан - нужно его просто скопировать и немного подогнать под текущую задачу. Но однажды программистам стало лень копировать, и они придумали технологию наследования в рамках ООП.

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
namespace test2
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 100000000;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            // class - 188 ; 1976
            A obj1 = new A();
            for (int i = 0; i < count; ++i)
            {
                bool f = obj1.Proc1();
            }
            sw.Stop();
            Console.WriteLine("class - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            // interface 215 ; 1889
            B obj2 = new Bi() as B;
            for (int i = 0; i < count; ++i)
            {
                bool f = obj2.Proc1();
            }
            sw.Stop();
            Console.WriteLine("interface - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
             //abstract - 232 ; 2126
            Ci obj3 = new Ci();
            for (int i = 0; i < count; ++i)
            {
                bool f = obj3.Proc1();
            }
            sw.Stop();
            Console.WriteLine("abstract - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            // virtual - 237 ; 2140
            Di obj4 = new Di();
            for (int i = 0; i < count; ++i)
            {
                bool f = obj4.Proc1();
            }
            sw.Stop();
            Console.WriteLine("virtual - " + sw.ElapsedMilliseconds);
            sw.Reset();
            sw.Start();
            // static - 175 ; 1897
            for (int i = 0; i < count; ++i)
            {
                bool f = Proc1();
            }
            sw.Stop();
            Console.WriteLine("static - " + sw.ElapsedMilliseconds);
            Console.ReadLine();
        }
        public static bool Proc1()
        {
            return true;
        }
    }
    class A
    {
        public bool Proc1()
        {
            return true;
        }
    }
    interface B
    {
        bool Proc1();
    }
    abstract class C
    {
        abstract public bool Proc1();
    }
    class D
    {
        virtual public bool Proc1()
        {
            return true;
        }
    }
    class Bi : B 
    {
        public bool Proc1()
        {
            return true;
        }
    }
    class Ci : C
    {
        public override bool Proc1()
        {
            return true;
        }
    }
    class Di : D
    {
    }
    
}
  • #1 - class - 87: Вызов метода из класса безнаследования;
  • #2 - interface - 929: Вызов через интерфейс;
  • #3 - abstract - 668: Вызов из наследника абстрактного класса;
  • #4 - virtual - 669: Вызов виртуального метода;
  • #5 - static - 83: Вызов статичного метода.

Собственно, в рамках данной статьи у меня нет возможности опубликовать полномасштабный анализ производительности ООП, поскольку получится трактат на целую книгу. Главное - обратить внимание на то, что любое удобство надо использовать разумно!.

Файловые операции Многим разработчикам кажется удобным иметь возможность сохранять и загружать классы, используя сериализацию. Удобство заключается в том, что в функцию сохранения нужно передать ссылку на класс, при этом сохраняются все public свойства и поля класса. А главное - больше ничего не нуж- но делать. Для этого существуют три класса:

  • XmlSerializer
  • BinaryFormatter
  •  SoapFormatter.

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

using System;
using System.IO;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization.Formatters.Soap;
using System.Diagnostics;
namespace test6
{
    class Program
    {
        static void Main(string[] args)
        {
            int count = 10000;
            Stopwatch sw = new Stopwatch();
            string file = "";
            // XmlSerializer
            {
                file = "XmlSerializer.test";
                FileStream fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
                XmlSerializer xml = new XmlSerializer(typeof(IOTest));
                IOTest obj = new IOTest();
                sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    xml.Serialize(fs, obj);
                }
                sw.Stop();
                
                fs.Close();
                FileInfo fi = new FileInfo(file);
                Console.WriteLine("XmlSerializer - " + sw.ElapsedMilliseconds + "; FileSize - " + fi.Length);
            }
            // BinaryFormatter
            {
                file = "BinaryFormatter.test";
                FileStream fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
                BinaryFormatter bf = new BinaryFormatter();
                IOTest obj = new IOTest();
                sw.Reset(); sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    bf.Serialize(fs, obj);
                }
                sw.Stop();
                fs.Close();
                FileInfo fi = new FileInfo(file);
                Console.WriteLine("BinaryFormatter - " + sw.ElapsedMilliseconds + "; FileSize - " + fi.Length);
            }
            // SoapFormatter
            {
                file = "SoapFormatter.test";
                FileStream fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
                SoapFormatter sf = new SoapFormatter();
                IOTest obj = new IOTest();
                sw.Reset(); sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    sf.Serialize(fs, obj);
                }
                sw.Stop();
                fs.Close();
                FileInfo fi = new FileInfo(file);
                Console.WriteLine("SoapFormatter - " + sw.ElapsedMilliseconds + "; FileSize - " + fi.Length);
            }
            // Manual
            {
                file = "Manual.test";
                FileStream fs = new FileStream(file, FileMode.OpenOrCreate, FileAccess.Write);
                BinaryWriter bw = new BinaryWriter(fs);
                IOTest obj = new IOTest();
                sw.Reset(); sw.Start();
                for (int i = 0; i < count; ++i)
                {
                    obj.Save(bw);
                }
                sw.Stop();
                fs.Close();
                FileInfo fi = new FileInfo(file);
                Console.WriteLine("Manual - " + sw.ElapsedMilliseconds + "; FileSize - " + fi.Length);
            }
            
            Console.ReadLine();
        }
    }
    [Serializable()]
    public class IOTest 
    {
        public int member1 = 0x7FFFFFFF;
        public int member2 = 0x7FFFFFFF;
        public int member3 = 0x7FFFFFFF;
        public void Save(BinaryWriter bw)
        {
            bw.Write(member1);
            bw.Write(member2);
            bw.Write(member3);
        }
        public static IOTest Load(BinaryReader br)
        {
            IOTest obj = new IOTest();
            obj.member1 = br.ReadInt32();
            obj.member2 = br.ReadInt32();
            obj.member3 = br.ReadInt32();
            return obj;
        }
    }
}

Сравним скорость сохранения и размеры генерируемых файлов в байтах:

Название Скорость Размер файла
XmlSerializer 697 2400000
BinaryFormatter 625 1520000
SoapFormatter 1411 6930000
Manual 3 120000

Тут разработчики стандартных классов совсем сплоховали. Как видно из теста, ручной способ быстрее классов XmlSerializer и BinaryFormatter примерно в 230 раз, и это притом, что файл получается меньше в 20 раз! Честно признаюсь, у меня были некоторые надежды на класс BinaryFormatter (само слово Binary в названии внушало доверие). Дело в том, что XmlSerializer сохраняет данные в формате xml, а это, как известно, не самый компактный формат. Предлагаю посмотреть на фрагменты этих файлов, что бы стало ясно, почему же размер так отличается (см. соответствующие иллюстрации). Как видно XmlSerializer сохраняет в тестовом виде. Число 2147483647 в текстовом виде весит 10 или 20 байт, в зависимости от кодировки, а в бинарном формате всего четыре байта. Плюс ко всему, каждая запись обрамляется тегами вида: <any_tag_name>any data</any_tag_name>. Это, пожалуй, самый «толстый» формат хранения данных из всех, что мне доводилось видеть, а причиной тому является обильное использование служебных данных. BinaryFormatter являете бинарным лишь отчасти, на рисунке видно, что полезные данные составляют примерно 15% от общего количества и что по-настоящему бинарныхданных здесь очень мало. Хотелось бы отметить, что при создании стандартными классами, размер файлов зависит еще и от размера названий полей. В нашем примере названия маленькие member1, member2 и т.д., но если бы они были длиннее, тогда и размер файлов был бы больше, поскольку названия полей включаются в файл. То есть, если мы сделали программу, в которой они уже используются, а потом выпустили новую версию, в которой изменились эти названия, или изменилось количество этих полей, тогда при загрузке данных произойдет ошибка, которая сделает невозможной эту загрузку. На мой взгляд, это существенный недостаток, который отсутствует при ручном сохранении. Еще один недостаток заключается в том, что эти классы сохраняют абсолютно все открытые поля, и нет возможности выбрать, что сохранять, а что нет. Довольно часто отрытые поля нужны для самой программы, и нет никакого смысла сохранять эти данные, особенно принимая в расчет такие могучие потери производительности. Отдельно хочу рассказать о SoapFormatter. Логически это более высокоуровневая конструкция класса XmlSerializer. Формат Soap был создан для реализации удаленных вызовов процедур. В .NET он используется в технологиях веб-сервисов и Remouting. Remouting сделан для облегчения разработки клиент-серверных решений. По сути, он заменяет TCP/IP. Теперь представим, что из всех способов ввода/вывода данных, Remouting использует самый плохой и это притом, что данные должны передаваться по сети, что накладывает ограничения на их размер. SoapFormatter 120 килобайт полезных данных способен превратить в шесть мегабайт бесполезных. В этом тесте сериализовались относительно маленькие объемы данных (120 Кб). Обычно приходится иметь дело с данными в десять, а то и в сто раз большими по объему. Учитывая, что процессоры нынче все же не всемогущи, можно заключить, что данная технология скоро исчезнет, поскольку вместе с мощностями железа и шириной каналов, также быстро растут и объемы данных, которые необходимо обрабатывать и пересылать.

Инициализация классов

Эту тему я не мог пропустить, поскольку она касается всех, кто использует классы. При компиляции инициализация всех переменных выносится в конструкторы. Если при этом присваивать внутренним переменным значения параметров конструктора, то переменная будет инициализироваться два раза. В принципе, если класс не создается большое количество раз (т.е. если не нужна производительность), тогда можно не обращать внимания на этот факт. Но, на мой взгляд, нужно иметь привычку всегда и все писать академически правильно. В связи с этим есть некоторые рекомендации:

  • переменные инициализировать в конструкторах;
  • если конструкторов несколько - делать передачу управления от текущего конструктора последнему, с наибольшим количеством параметров.
  • в крайнем случае, можно создать метод Init и в нем производить инициализацию.

using System;using System.Diagnostics;namespace ConsoleApplication2{
        class Program
       {
        static void Main(string[] args)
        {
            test1();
                          test2();
                        Console.ReadLine();
        }
           static int count = 10000000;
                        static void test1()
               {
                       Stopwatch sw = Stopwatch.StartNew();
                       MyClass1 m;
                       for (int i = 0; i < count; ++i)
                        {
                m = new MyClass1();
                        }
                       sw.Stop();
                        Console.WriteLine(sw.ElapsedMilliseconds);
               }
                        static void test2()
                {
            Stopwatch sw = Stopwatch.StartNew();
                        MyClass2 m;
            for (int i = 0; i < count; ++i)
                        {
                               m = new MyClass2();
                        }
                       sw.Stop();
                      Console.WriteLine(sw.ElapsedMilliseconds);
              }
                        public class MyClass1
               {
                     public MyClass1() : this("DefaultValue1", "DefaultValue2", "DefaultValue3"){}
                                     public MyClass1(string str1) : this(str1, "DefaultValue2", "DefaultValue3"){}
                      public MyClass1(string str1, string str2) : this(str1, str2, "DefaultValue3"){}
                                    public MyClass1(string str1, string str2, string str3)
                         {
                      this.str1 = str1;
                               this.str2 = str2;
                              this.str3 = str3;
                        }
                                     string str1;
                         string str2;
                         string str3;
                    }
                            public class MyClass2
                   {
            public MyClass2(): this("DefaultValue1"){}
                                          public MyClass2(string str1): this(str1, "DefaultValue2"){}
                              public MyClass2(string str1, string str2): this(str1, str2, "DefaultValue3"){}
            public MyClass2(string str1, string str2, string str3)
            {
                this.str1 = str1;
                this.str2 = str2;
                 this.str3 = str3;
            }
                                                        string str1;
             string str2;
             string str3;
        }
    }}
  • 283 - MyClass1
  • 450 - MyClass2

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

Сравнение типов

Всего существует три способа сравнения, которое можно произвести с помощью is, as и typeof:

using System;using System.Diagnostics;interface Interface { }class ClassImplementor : Interface { }class ClassImplementor2 : Interface { }static class Program {
     static void Main()
     {
         const int count = 10000000;
        ClassImplementor c = new ClassImplementor();
         Stopwatch sw = Stopwatch.StartNew();
         for (int i = 0; i < count; i++)
         {
             if (c is ClassImplementor)
             {
                int it = 1;
            }
         }
         sw.Stop();
         Console.WriteLine(sw.ElapsedMilliseconds);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
        {
             if (c as ClassImplementor != null)
             {
                int it = 1;
            }
         }
         sw.Stop();
         Console.WriteLine(sw.ElapsedMilliseconds);
        Type t = c.GetType();
        sw = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
        {
             if (t == typeof(ClassImplementor))
             {
                int it = 1;
            }
         }
        sw.Stop();
         Console.WriteLine(sw.ElapsedMilliseconds);
         Console.ReadLine();
     } }
  • is - 8
  • as - 9
  • typeof - 368

Разницу между is и as глазом сложно заметить, а вот конструкции с typeof лучше не использовать.

Проверка на инициализацию строки

Есть два популярных способа проверки: if (str != null && str != «») и if (str != string.Empty). Вопреки всем убеждениям, первый метод работает втрое быстрее, если строка равна «» и вчетверо, если строка равна null. Обратитевнимание на результат теста:.

using System;using System.Diagnostics;static class Program{
    static void Main()
    {
        const int count = 10000000;
        Stopwatch sw = Stopwatch.StartNew();
        string str = null;
        for (int i = 0; i < count; i++)
        {
            if (str != null && str != "")
            {
                int it = 1;
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
        {
            if (str != string.Empty)
            {
                int it = 1;
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.ReadLine();
    }}

Заключение

Мы рассмотрели немного способов сделать программу лучше. Каждый должен понимать, что это далеко не все способы на все не хватит целой книги. Цель даннойстатьи показать, насколько важно задавать себе вопрос «а что быстрее?». Программа работает всегда, и если программист не уделял должного внимания производительности, то она чуть-чуть тормознет здесь, чутьчуть там, а в итоге программа будет тормозить везде и всегда.

Примечание:

Все тесты необходимо запускать в Release версии без отладчика. Для получения наибольшего качества теста, необходимо закрыть все программы, выключить антивирусы, фаерволы или службы, требуещее значительное количество ресурсов. Каждый тест нужно разбивать на несколько этапов. Например в тесте for и foreach сначало нужно отдельно протестировать for, закоментировав тест foreach, а потом наоборот.



 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 03.11.2007 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft 365 Apps for business (corporate)
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Basic (corporate)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год.
Microsoft Office для дома и учебы 2019 (лицензия ESD)
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Delphi - проблемы и решения
Мастерская программиста
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100