• Оптимизация программного кода. Основные возможности оптимизации кода программистом и компилятором. Вред и польза оптимизаций

    Иногда бывает сложно решить, какую конструкцию лучше использовать i++ или ++i, либо выбрать между конструкцией if-else и switch. В этой статье, написанной специально для сообщества iT works, представлены наиболее реальные средства оптимизации кода, которые должен знать каждый профессиональный программист.

    Некоторые считают, что времена оптимизации на уровне кода прошли навсегда, однако это не так. Сейчас существует множество платформ в которых нет таких могущественных компиляторов как в Microsoft Visual Studio. Например шейдерные языки (hlsl, glsl) или код для CUDA, PlayStation3, SPU или мобильные платформы. В зависимости от организации кода, может в десятки раз отличаться его эффективность иногда из-за неэффективности компилятора, на чаще из-за доступа к памяти.

    Программируя для разных платформ изучите возможности вашего компилятора и особенности архитектуры процессора (если вы пишите для конкретной консоли). Проведите тесты производительности разных вариантов оптимизации. Часто сложно предполагать, какие именно способы будут наиболее эффективными. А эта статья подскажет разные приемы, которые могут вам помочь. Однако не следует оптимизировать слепо, без предварительного анализа и профилирования. Помните, что преждевременная оптимизация - это зло.

    Если вы являетесь программистом в VS под Windows, то скорее всего со многими описанными приемами оптимизации компилятор эффективно справится. Обратите внимание на пункты работы с памятью, а так же я рекомендую ознакомиться с техникой Data oriented design . Некоторые советы по ее использования ищите в статье .

    Итак, начнем:

    1. Используйте векторизацию данных и векторные команды их обработки (например SSE в CPU или упаковывайте данные если используете шейдеры или CUDA). Это позволит использовать SIMD (Single Instruction, Multiple Data) архитектуру, что значительно повысит скорость вычислений. Если вы решите использовать этот метод, то не забывайте про выравнивание данных в памяти.

    2. Эффективнее складывать и умножать в перемешку, чем сначала все сложить, а потом все умножить. Это происходит от того, что сложение и умножение выполняются разными модулями процессора и могут выполняться одновременно.
    int a,b,c,k,m,n,t,f1,f2,f3,g1,g2,g3; a = b + с; k = m + n; t = a + k; f1 = f2 * f3; g1 = g2 * g3; Менее эффективно чем: a = b + с; f1 = f2 * f3; k = m + n; g1 = g2 * g3; t = a + k;

    3. Нет разницы по скорости в работе с float и double при сложении и умножении. Они выполняются за равное число тактов процессора и используют одни и те же регистры. При делении и извлечении корня, float работает быстрее. Однако если вы будете использовать большие объемы данных, то за счет кеша быстрее будет работать тот тип, который занимает меньше памяти (т.е. float), поэтому в общем случае именно его предпочтительно использовать. Выбирать double имеет смысл когда нужна большая точность.

    4.
    Допустим есть. const float a = 100.0f; float some1 = some3 * 1.0f / a; float some2 = some4 * 1.0f / a; более эффективно написать не: const float a_inv = 1.0f / a; some1 = some3 * a_inv; some2 = some4 * a_inv; а так: some1 = some3 * (1.0f / a); some2 = some4 * (1.0f / a); Почему это будет эффективнее? Операторы с равным приоритетом выполняются последовательно. Это значит, что будет выполнено сначала умножение, а затем деление. Если же обрамить операцию деления в скобки, то ее выполнит компилятор, а в реальном времени будет выполняться только операция умножения. Что качается отличий варианта 3 от варианта 2, то в 3-ем варианте не создается дополнительной переменной, нет нужны глядя на код думать о том, что это за переменная. А эффективность 2-го и 3-го варианты будет одинаковой.

    5. На больших объемах данных и вычислений на них float выгоднее чем double (из за cache miss"ов, см. пункт 3).

    6.
    a, b - любое выражение Func1, Func2 - функции, которые вызовутся для вычисления условия if(a && b) - нужно первым ставить менее вероятное условие, для большей эффективности if(a || b) - нужно первым ставить более вероятное условие, для большей эффективности if(Func1() && Func2()) - нужно ставить более быстрый оператор первым

    7.
    void Func(int* a) { int b = 10; Следующие строки одинаковые по эффективности (по времени выполнения): b = 100; *a = 100; } Это происходит по тому, что доступ к стековой переменной осуществляется по указателю на стек. Тоже идет разыменование указателя.

    8. Если есть большой массив структур, то нужно делать размер его элементов равным степени двойки. Тогда проход по такому массиву будет значительно быстрее (в 4 -6 раз), за счет выравнивания указателя на каждую структуру в массиве.

    9.
    int a[ 1000 ]; for(int i =0; i= b ? -1: 0); Можно заменить на: 1. int x = (b - a) >> 31; 2. int x = (b - a) & 0x80000000;

    25.
    i32 iIndex; Условие: if(iIndex < 0 && iIndex >= iSize) Можно заменить таким: if((u32)iIndex >= iSize) Условие: if(i >= min && i li.

    Теперь для лучшего представления попробуем создать макет правильно оптимизированной страницы.

    Название страницы

    • Категория 1
    • Категория 2
      • Категория 2.1
      • Категория 2.2
    • Категория 3
    Заголовок страницы

    Основной контент с применением тегов -, , , ,
    ,

    .

    Боковой блок с дополнительной информацией. ...

    Что еще нужно учесть при SEO-верстке сайта
    • Плохое влияние на страницу может оказать большое количество ошибок валидации. Не желательно использовать пустых тегов и css, js файлов, которые не используются на странице. Чем легче будет код, тем легче поисковым системам его проанализировать.
    • Не стоит использовать флеш и фреймы, которые очень не дружелюбны с поисковыми системами. Также поисковые системы не распознают текст, который нарисован с помощью картинки.
    • Кроссбраузерность сайта влияет на поведение пользователей и заставляет их покидать сайт не получив нужную информацию или не сделав покупку. Как следствие ухудшаются поведенческие факторы, которые сказываются на оптимизации всего сайта.
    • Наличие мобильной версии сайта или его адаптивность стала фактором ранжирования и, как и кроссбраузерность, позволяет уменьшить показатель отказов и увеличить конверсию сайта на мобильных устройствах. Google начал учитывать наличие мобильной версии в 2015, году (mobile-friendly), а Яндекс в 2016, назвав алгоритм ранжирования «Владивосток».
    • Основной контент на странице должен быть размещен в html коде ближе к началу, так он будет более ревалентный с точки зрения поисковой системы.
    • Контент не должен быть спрятан с помощью display:none .
    • Если с помощью тегов можно повысить значимость ключевого слова, то также можно и получить отрицательный эффект, если некоторые теги будут пересекаться, например
      1. h1-h6 & strong, b, em
      2. h1-h6 & a href=…
      3. strong, b, em & a href=…
    Заключение

    Заглянув на страницы поисковых систем можно увидеть ряд ошибок, связанных с версткой сайта, в том числе ошибки валидации. Но здесь следует понимать, что они ставят перед собой совсем другие цели. SEO оптимизированная вёрстка требуется сайтам, одним из основных источников трафика которых — поисковые системы и какие бы крутые ссылки не ссылались на сайт, без хорошей оптимизации кода о первым позициях можно не мечтать.

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

    А что сейчас? Вычислительные мощности современных компьютеров достигли фантастических (если сравнивать с тем, что было) значений, и даже такие «монстры», как Windows 7 не в силах их затормозить. И зачем нам оптимизировать, если так все нормально работает? Многие так и считают. Сейчас программирование дошло до такой стадии, что важнее стала скорость написания программ, чем скорость их работы. Потому что скорость их работы будет заведомо высокой. Но это относится лишь к обычным прикладным программам. Совсем другое дело драйвера (которые мало изменились еще со времен DOS), программы обработки аудио, видео, и графики, генерации паролей… В них про оптимизацию забывать ни в коем случае нельзя. Да и в обычных программах она никогда не бывает лишней. Куда приятней использовать более эффективную программу, чем идти в магазин за новым процессором. Или ждать, пока она соизволит загрузится, кому как лучше. Большинство пользователей выбирает первый вариант.

    Оптимизация

    В оптимизации есть несколько важных моментов:

    Оптимизация должна быть естественной. Оптимизированный фрагмент кода должен легко вливаться в программу, не нарушая логики ее работы. Он должен легко вводится в программу, изменятся или удаляться из нее.
    Оптимизация должна приносить существенный прирост производительности. Оптимизированная программа должна работать минимум на 20%-30% эффективней, чем ее неоптимизированный аналог, иначе она теряет смысл. Зачем мучится и вносить изменения в уже готовый код, если результата это практически не даст?
    Разработка (и отладка) критических областей не должна увеличивать время разработки программы более чем на 10%-15%.
    Как уже писалось ранее, сейчас на первый план выходит скорость разработки программ, так что все же не нужно отставать от остальной массы программистов. Себе же хуже.
    Так же, перед тем как писать оптимизированный вариант, полезно иметь его неоптимизированный аналог. Обычно, оптимизированный код очень тяжел для восприятия, и если после его внедрения в программе появятся ошибки, то, подставив вместо него его менее эффективного собрата, мы можем определить, кто виноват в ошибках.

    Логика оптимизации программного кода

    Теперь перейдем к самой философии оптимизации. Считается, что критические области следует писать на ассемблере, поскольку он дает наивысшую скорость работы. Но зачастую результат работы оптимизирующего компилятора работает медленней своего ассемблерного аналога на 2%-7% (не более 20%). И стоило ли из-за такого мизерного прироста тратить время на разработку ассемблерной реализации? Из этого следует, что намного лучше оптимизировать код, написанный на языке высокого уровня, оптимизировать саму логику работы программы.

    Основные постулаты оптимизации:

  • Начинать оптимизацию нужно с самых «узких» мест программы. Если мы будем оптимизировать те места, где и без нашего вмешательства все быстро работает, то прирост производительности будет минимален. Это основной закон оптимизации, от него мы и будем отталкиваться, разбирая остальные.
  • Оптимизировать лучше те места, которые регулярно повторяются в ходе работы. Этот закон относится к циклам и подпрограммам. Если можно хотя бы немного оптимизировать цикл, то делайте это не задумываясь. Если в одной итерации мы добьемся прироста в 2%, то после 1000 повторений это уже будет достаточно большое значение.
  • Старайтесь не слишком злоупотреблять оптимизацией единичных операций. Этот закон – своеобразное продолжение предыдущего. Оптимизируя фрагмент, который будет вызван лишь один раз, мы вряд ли добьемся ощутимого прироста (но если эффект будет ощутим (>10%, что бывает крайне редко), то оптимизация лишней не будет).
  • Используйте ассемблер только там, где скорость работы очень важна. Как уже писалось ранее, сейчас ассемблер не дает огромного прироста скорости. Поэтому использовать его стоит лишь в самых «узких» местах программы.
  • Задумывайтесь над оптимизацией. Неправильная оптимизация может даже навредить программе, увеличить время ее разработки, практически не уменьшив скорость ее работы.
  • Конечно, в современном мире сверхбыстрых вычислений на первый план выходит скорость разработки программ. Но все же, не стоит забывать об оптимизации, которая, несмотря на общепринятое мнение, никогда не уходила на второй.

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

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

    2. Оптимизацию всегда надо начинать со слабых мест в коде программы. Обычно оптимизировать то, что и так быстро работает, необходимости не возникает. Да и эффект такой оптимизации будет минимален.

    3. При оптимизации нужно разбирать все операции, каждый оператор, ничего не пропуская. Обычно оптимизацию начинают с тех мест в коде, где находятся регулярно повторяющиеся операции, циклы. То, что находится внутри цикла, будет повторена n количество раз, поэтому, чем меньше кода находится в цикле, тем быстрее процессор просчитает его. Если цикл получается слишком большой, его можно разложить на несколько более маленьких. В данном случае размер нашей программы повыситься, зато скорость увеличиться.

    4. Старайтесь поменьше использовать вычисления с плавающей запятой. Любые операции с целыми числами выполняются на порядок быстрее. Операции умножения или деления также выполняются достаточно долго. Вместо умножения лучше использовать сложение, а деление можно заменить сдвигом. Сдвиг работает намного быстрее и умножения, и деления. Это связано с тем, что все числа хранятся в двоичной системе. Если перевести число из десятичной системы счисления в двоичную и сдвинуть число вправо на одну позицию, то можно заметить, что данная операция аналогична делению на 2. При сдвиге влево происходит деление числа на 2. Хоть эти операции и аналогичны, но сдвиг работает в несколько раз быстрее.

    5. При создании процедур не надо обременять их большим количеством входных параметров. А всё потому, что при каждом вызове процедуры её параметры подымаются в специальную область памяти, стек, а после выхода изымаются оттуда. Также необходимо действовать аккуратно и с самими параметрами. Не надо пересылать процедурам переменные, содержащие данные большого объёма в чистом виде. Лучше передать адрес ячейки памяти, где хранятся данные, а внутри процедуры уже работать с этим адресом.

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

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

    8. Если ты пишешь достаточно большую и громоздкую программу, добавляй в неё комментарии. Компилятор их всё равно игнорирует. И если вдруг тебе захочется продать исходные коды своих программ, комментарии повысят им цену, да и самому будет легче в них ориентироваться.

    9. Для достижения хорошего эффекта ты должен знать IDE, интегрированную среду разработчика, языка, на котором ты программируешь, в нашем случае Delphi. Обычно в опциях IDE разрешается выбирать различные типы компиляторов, а по умолчанию стоит самый простой, быстро компилирующий, но создающий менее оптимизированный код. Поэтому всегда ставь самый оптимизирующий вид компилятора.

    10. Старайся делать в программах стандартный интерфейс. Ну не надо делать треугольные кнопочки, нестандартные меню и прочие графические навороты. Всё это очень сильно тормозит программу, расходует большое количество ресурсов компьютера и требует дополнительного времени на разработку. К примеру, настоящий UNIX – это вообще обычный shell – строка для ввода команд.

    Вот вроде и всё. Желаю, удачи в написании своих программ, просто следуй этим советам, и всё у тебя получиться.