оптимизация приложений на платформе net pdf

оптимизация приложений на платформе net pdf. . оптимизация приложений на платформе net pdf фото. оптимизация приложений на платформе net pdf-. картинка оптимизация приложений на платформе net pdf. картинка .

Статей с подобным заголовком достаточно много, поэтому постараюсь избежать банальных тем. Надеюсь, что даже опытный разработчик найдёт здесь что-то полезное для себя. В данной статье будут рассмотрены только простые механизмы и подходы оптимизации, которые позволят применить их, затратив минимум усилий. И эти изменения не увеличат энтропию вашего кода. В статье не будет уделено внимания, что и когда нужно оптимизировать, эта статья скорее о подходе к написанию кода в целом.

1. ToArray vs ToList

Согласитесь, очень типовой код для промышленных проектов. Но что в нём не так? IEnumerable интерфейс возвращает коллекцию, по которой можно «пробежаться», данный интерфейс не предполагает того, что мы можем добавлять/удалять элементы. Соответственно, нет необходимости заканчивать LINQ выражение приведением к List’у (ToList). В данном случае, предпочтительнее будет приведение к Array (ToArray). Так как List является обёрткой над Array, а все дополнительные возможности, предоставляемые этой обёрткой, мы срезаем интерфейсом. Массив потребляет меньше памяти, а доступ к его значениям быстрее. Соответственно, зачем платить больше. В описанном выше примере, интерфейс IEnumerable введён лишь для наглядности. Если в коде, вы собираетесь вызвать ToList для LINQ выражения, убедитесь, действительно ли вам нужна функциональность именно List’a. С одной стороны эта оптимизация не существенная, как говорят «оптимизация на спичках», но это не совсем так. Дело в том, что в типовом приложении, в котором сервисы возвращают модели для слоя представления, таких вызовов ToList может быть мириады.

Предвижу комментарий о том, что Array и List будут работать не эквивалентно в случае обращения к коллекции из разных потоков, с возможностью её изменения. Это действительно так. Но если вы, как разработчик, рассматриваете такой сценарий, то с высокой степенью вероятности, вам уже не подходят ни Array, ни List.

2. Параметр «путь к файлу» не всегда лучший выбор для вашего метода

При разработке API избегайте сигнатур методов, которые на вход получают путь к файлу (для последующей обработки вашим методом). Вместо этого предоставляйте возможность передать на вход массив байт или в крайнем случае Stream. Дело в том, что со временем, ваш метод может быть применён не только к файлу с диска, но и к файлу, переданному по сети, к файлу из архива, к файлу из базы данных, к файлу содержание которого сформировано динамически в памяти и т. д. Предоставляя метод с входным параметром «путь к файлу» вы обязываете пользователя вашего API предварительно сохранить данные на диск, чтобы потом прочесть их снова. Это бессмысленная операция критически влияет на производительность. Диск – крайне медленная штука. Для удобства, вы можете предоставить метод с входным параметром «путь к файлу», но внутри всегда используйте публичный перегруженный метод с массивом байт или stream’ом на входе. Есть «маркер», который может помочь найти лишние операции записи/чтения диска, попробуйте найти в вашем проекте использование стандартных методов: Path.GetTempPath() и Path.GetRandomFileName() (из System.IO). С высокой степенью вероятности, вы встретите workaround вышеописанной проблемы или похожей.

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

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

В чём здесь проблема… когда мы получаем поток из некоторого «чёрного ящика», мы должны держать в голове его состояние. Т.е. открыт ли поток? Где находится маркер чтения/записи? Может ли измениться его состояние независимо от нашего кода? Если поток объявлен как базовый класс Stream, мы даже не владеем информацией, какие операции над ним доступны. Всё это решается дополнительными проверками, а это дополнительный код и издержки. Также, неоднократно сталкивался с ситуацией, когда, получая Stream из некоторого неочевидного метода, разработчик предпочитал перестраховаться и «перегнать» данные из него в полностью контролируемый новый локальный MemoryStream. Хотя, исходный поток мог быть вполне безопасным. Может даже это и был уже любезно подготовленный для чтения MemoryStream. Иногда может доходить до абсурда – внутри метода, массив байт кладётся в MemoryStream, далее данный MemoryStream возвращается как результат метода, объявленного как базовый Stream. Снаружи этот Stream оборачивается новым MemoryStream’ом и далее ToArray() возвращает массив байт, который изначально у нас и был. Точнее это уже будет его очередная копия. Ирония в том, что внутри и снаружи нашего метода код вполне корректный. По-моему, этот пример не из головы, а встречался где-то в коммерческом коде.

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

4. Наследование enum’ов

Данная оптимизация банальная, её знают все, даже студенты. Но из моего опыта, ей крайне редко пользуются. Итак, по умолчанию enum наследуется от int. Однако его можно наследовать от byte, который вмещает 256 значений (или 8 «flaggable» значений). Что почти всегда покрывает функциональность «среднего» enum’а. Минимальное изменение в коде и все значения вашего enum’а занимают меньше памяти навсегда. Ниже иллюстрация бенчмарка по заполнению коллекции значениями enum’ов, наследуемых от int и byte.

оптимизация приложений на платформе net pdf. image loader. оптимизация приложений на платформе net pdf фото. оптимизация приложений на платформе net pdf-image loader. картинка оптимизация приложений на платформе net pdf. картинка image loader.

5. Ещё пару слов о классах Array и List

Следуя логике, итерирование по массиву всегда эффективнее итерирования по List’у, так как List это обёртка над массивом. Также, следуя логике, «for» всегда быстрее «foreach», так как «foreach» делает много действий, требуемых реализацией интерфейса IEnumerable. Здесь всё логично, но неверно! Давайте посмотрим на результаты бенчмарка:

оптимизация приложений на платформе net pdf. image loader. оптимизация приложений на платформе net pdf фото. оптимизация приложений на платформе net pdf-image loader. картинка оптимизация приложений на платформе net pdf. картинка image loader.

Дело в том, что для итерирования по массиву, «foreach» не использует реализацию IEnumerable. В этом частном случае выполняется максимально оптимизированное итерирование по индексу, без проверки на выход за границы массива, так как конструкция «foreach» не оперирует индексами, соответственно у разработчика нет возможности «накосячить» в коде. Такое вот исключение из правил. Поэтому, если в каком-то критичном участке кода вы заменили использование «foreach» на «for» ради оптимизации – вы выстрелили себе в ногу. Обратите внимание, это актуально только для массивов. На StackOverflow есть несколько веток, где обсуждается это особенность.

6. Всегда ли поиск через хеш-таблицу оправдан?

Все знают, что хеш-таблицы очень эффективны для поиска. Но часто забывают, что цена за быстрый поиск — медленное добавление в хеш-таблицу. Что из этого следует? Для того чтобы использование хеш-таблицы было оправданным, необходимо, чтобы кол-во элементов хеш-таблицы было не менее 8 (примерно). И чтобы кол-во операций поиска было хотя бы на порядок больше кол-ва операций добавления. В противном случае используйте коллекцию попроще. Качество хеш-функции внесёт свои коррективы в эффективность, но смысл от этого не измениться. На моей практике был случай, когда самым «узким местом» в нагруженном коде был вызов метода Dictionary.Add(). Ключом был обычный string, небольшой длины. Воспоминание об этом и стало триггером к написание этого пункта. Для иллюстрации, пример очень плохого кода:

Может что-то подобное встречается и в вашем проекте?

7. Встраивание методов

Код разбит на методы чаще всего по 2-ум причинам. Обеспечить повторное использование кода и обеспечить декомпозицию, когда одна задача разбивается на несколько подзадач. Для человека так проще. Inlining – это обратный процесс декомпозиции, т.е. код метода встраивается в то место, где метод должен вызываться, в итоге мы экономим на стеке вызовов и передаче параметров. Я никоим образом не рекомендую всё «запихивать» в один метод. Но те методы, которые мы могли бы теоретически «заинлайнить» можно пометить соответствующим атрибутом:

8. Оценочный Capacity

У меня (и надеюсь, у большинства разработчиков тоже) выработан рефлекс: проинициализировал коллекцию – задумался, можно ли для неё задать Capacity. Однако, далеко не всегда заранее известно точное кол-во элементов коллекции. Но это не повод игнорировать этот параметр. Например, если, рассуждая о том, какое кол-во элементов будет в вашей коллекции, вы предполагаете размытое «пару тыщ» это уже повод задать Capacity равное 1000. Немного теории, например, для List по умолчанию Capacity = 16, для того чтобы только дойти до 1000, система сделает 1008 (16 + 32 + 64 + 128 + 256 + 512) лишних копирований элементов и создаст 7 временных массивов на откуп следующему вызову GC. Т.е. вся эта работа выполнится впустую. Также, в качестве Capacity никто не запрещает использовать формулу. Если размер вашей коллекции оценочно равен одной трети другой коллекции, можно задать Capacity равное otherCollection.Count / 3. При установке Capacity стоит хорошо понимать диапазон возможного размера коллекции и насколько его значение плотно распределено. Всегда есть вероятность навредить, но при правильном использовании, оценочный Capacity даст вам хороший выигрыш.

9. Всегда конкретизируйте ваш код

Активно используйте (на первый взгляд, необязательные) ключевые слова C#, такие как: static, const, readonly, sealed, abstract и т.д. Естественно, там, где они имеют смысл. Причём здесь производительность? Дело в том, что чем более детально вы опишете компилятору свою систему, тем более оптимальный код он сможет сгенерировать. Внимательный и опытный читатель может заметить что, например ключевое слово sealed никак не влияет на производительность. Сейчас это действительно так, но в следующих версиях всё может измениться. Дайте компилятору и виртуальной машине шанс! Бонусом получите, выявление многих ошибок неправильного использования вашего кода на этапе компиляции. Общее правило: чем более чётко система описана, тем оптимальнее результат. Судя по всему, с людьми также.

Несколько полезных инструментов

В качестве заключения

Если каждый 10-ый прочитавший статью, применит вышеуказанные подходы к своему текущему проекту (или критической его части), а также будет придерживаться этих подходов в будущем, то вместе мы сможем спасти целый лес! Т.е. сэкономленные ресурсы компьютерных систем, в виде электричества, полученного от сжигания древесины, останутся неиспользованными. В данном случае «лес» это лишь некий эквивалент. Вероятно, странное заключение получилось, но, надеюсь, вы прониклись мыслью.

P.S. Обновление на основании комментариев к посту

Если enum, наследуемый от byte, используется как член класса (структуры), а не отдельно, то экономии памяти может и не быть. Из-за выравнивания занимаемой памяти всех членов класса (структуры). Эта особенность в статье упущена. Тем не менее, потенциальный выигрыш лучше его отсутствия, так как помимо занимаемой памяти enum’ы ещё и используются. Поэтому пункт 4, по-прежнему актуален, но с данной важной оговоркой.

Спасибо KvanTTT и epetrukhin за конструктивные комментарии по этим вопросам.

Также, как заметил Taritsyn, оптимизация на этапе JIT-компиляции для ключевого слова «sealed» всё же существует. Но, это только подтверждает все тезисы 9-го пункта.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *