Рубрика «оптимизация кода»

image

На уровне могут находиться тысячи врагов.

У игры Defender's Quest: Valley of the Forgotten DX всегда были давние проблемы со скоростью, и мне наконец удалось их решить. Основным стимулом к масштабному повышению скорости стал наш порт на PlayStation Vita. Игра уже вышла на PC и хорошо, если не идеально, работала на Xbox One с PS4. Но без серьёзного усовершенствования игры нам ни за что бы не удалось запустить её на Vita.

Когда игра тормозит, комментаторы в Интернете обычно винят язык программирования или движок. Справедливо то, что языки наподобие C# и Java связаны с большими издержками, чем C и C++, а у инструментов наподобие Unity есть не решаемые проблемы, например со сборкой мусора. На самом деле люди придумывают такие объяснения потому, что язык и движок являются наиболее явными свойствами ПО. Но истинными убийцами производительности могут оказаться глупые крошечные детали, никак не связанные с архитектурой.
Читать полностью »

image

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

Оптимизация ради игроков и собственного психического здоровья

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

Основы оптимизации кода

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

Привет, меня зовут Билл «LtRandolph» Кларк. Я работаю техническим руководителем команды создания чемпионов LoL. За последние несколько лет я успел поработать в разных отделах разработки League, но единственное, чем я был постоянно одержим — это технический долг. Мне нужно найти его, понять его и, при возможности, устранить его.

Когда разработчики обсуждают любую существующую технологию, например патч 8.4 League of Legends, то часто упоминают технический долг. Я называю техническим долгом код или данные, за которые придётся расплачиваться будущим разработчикам. Этой печальной стороне разработки ПО посвящено бесчисленное количество постов, статей и определений. В своём посте я хочу обсудить виды технического долга, с которыми мне пришлось встретиться при работе в Riot, и рассказать о модели, которую мы начали использовать в компании. Если бы меня попросили выделить самый важный урок, который можно извлечь из этой статьи, то я сказал бы, что это описанная ниже метрика «инфицирования».

Riot Games: анатомия технического долга - 1

Метрики

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

Низкоуровневая оптимизация кода на платформе Эльбрус: векторное сложение uint16_t с помощью интринсиков - 1

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

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

Однако в текущей версии EML мы не нашли некоторых интересных нам функций, поэтому приняли решение написать их сами.Читать полностью »

Намедни работая над одной ошибкой в одном опенсорсном проекте, увидел как коллега (тоже работающий параллельно над той же проблемой) залил такой вот коммит [31a078bec7]:

   	/*
-	 * Select the list item based on the index. Negative operand means
-	 * end-based indexing (-2, ...), and -1 means out of range.
+	 * Decode end-offset index values.
   	 */
-	if (opnd < -1) {
-	    index = opnd+1 + objc;
-	} else {
-	    index = opnd;
-	}
+	index = opnd + (opnd <= TCL_INDEX_END)*(objc - 1 - TCL_INDEX_END);
   	pcAdjustment = 5;

Изменение само по себе правильное (теперь TCL_INDEX_END есть константное определение (-2)).
И грубо говоря в уме это разворачивается в следующее (все переменные int):

index = opnd + cmp(opnd, (-2))==>(0 | 1) * (objc - 1 - (-2));

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

Во первых, это изменение касается самой «главной» функции в этом проекте (TEBCresume), ибо она ответственна за исполнение байт-кода (JIT скомпилированных инструкций языка TCL). По этой причине эта функция еще и самая большая (порядка 6 тысяч строк + примитивы и макросы) и одна из самых сложных в кодовой базе проекта, с множественными `goto`, головоломными макросами для работы со «стеком» исполнения, свёртка/развертка NRE (nonrecursive evaluation) и т.д. и т.п.
Т.е. изменения этой функции нередко рассматриваются под лупой, а то и под микроскопом (т.к. бывало что даже незначительные модификации могут перевернуть весь код этой функции с ног на голову)…

Во вторых, по роду деятельности мне часто приходится оптимизировать сишный код, разглядывая его ассемблерное отражение, выжимая доли микро- а то и нано-секунд, и я часто вижу, что там очень всё совсем не однозначно бывает. Как минимум иногда разворачивая такие вот «экономящие» условный jump конструкции обратно в if или даже if/else, я видел улучшение как и в результирующем ассемблерном коде, так и явно при конечном сравнении производительности результатов исполнения.

Собственно к чему я все это писал — хотелось на примере показать как оно бывает, ну и раз уж коснулись этой темы, собрать немного статистики. Посему пара опросов в конце статьи…
Читать полностью »

Многие программисты не понаслышке знают о том, что программа на языке C и C++ собирается очень долго. Кто-то решает эту проблему, сражаясь на мечах во время сборки, кто-то — походом на кухню «выпить кофе». Это статья для тех, кому это надоело, и он решил, что пора что-то предпринять. В этой статье разобраны различные способы ускорения сборки проекта, а также лечение болезни «поправил один заголовочный файл — пересобралась половина проекта».

Picture 1

Читать полностью »

В этой статье я покажу, как написать рудиментарный, нативный x86-64 just-in-time компилятор (JIT) на CPython, используя только встроенные модули.

Код предназначен для UNIX-систем, таких как macOS и Linux, но его должно быть легко транслировать на другие системы, типа Windows. Весь код опубликован на github.com/cslarsen/minijit.

Цель — сгенерировать в рантайме новые версии нижеприведённого ассемблерного кода и выполнить их.

48 b8 ed ef be ad de  movabs $0xdeadbeefed, %rax
00 00 00
48 0f af c7           imul   %rdi,%rax
c3                    retq

В основном, мы будем иметь дело с левой частью кода — байтовой последовательностью 48 b8 ed ... и так далее. Эти 15 байтов в машинном коде составляют функцию x86-64, которая умножает свой аргумент на константу 0xdeadbeefed. На этапе JIT будут созданы функции с разными такими константами. Такая надуманная форма специализации должна продемонстрировать базовую механику JIT-компиляции.
Читать полностью »

Что бы ни сказал тебе твой дракон, он солгал. Драконы лживы. Ты не знаешь, что ждет тебя на другой стороне.
Майкл Суэнвик. «Дочь железного дракона»

Не так давно на хабре был опубликован пост под названием "Как может вызваться никогда не вызываемая функция?". Выводы из статьи простые: в случае undefined behaviour компилятор вправе предпринимать любые действия, даже если они будут совершенно неожиданными. Однако меня заинтересовал сам механизм этой оптимизации. Результатом своего небольшого исследования я хочу поделиться с уважаемым сообществом хабра.

Почему LLVM может вызвать никогда не вызываемую функцию? - 1
Читать полностью »

Виталий vithar Харисов — один из ключевых разработчиков и руководителей Яндекса. На московском Я.Субботнике по фронтенду Виталий рассказал про лёгкую версию поиска для медленных соединений и способы оптимизации кода, позволяющие уложиться в 10 килобайт.

Читать полностью »

В сегодняшнем посте мы предлагаем вам расшифровку доклада Андрея DreamWalker Акиньшина с DotNext 2017 Piter о памяти, в котором Андрей разбирает, как работает память с точки зрения производительности приложений. Пост получился огромный, так что запасайтесь кофе и терпением.

Весь код лежит здесь, а сама презентация — здесь.

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

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

С точки зрения перформанса память — штука очень коварная и непонятная. Будем разбираться с тем, как она работает.

Разбираемся с памятью: тесты и оптимизация - 1

В этом докладе с DotNext 2017 Piter мы поговорим о том, что влияет на скорость работы с памятью. Обсудим как низкоуровневые хардварные штуки (CPU cache и его ассоциативность, выравнивание, store forwarding, 4K aliasing, prefetching, cache/page splits, cache bank conflicts и т.п.), так и более .NET-специфичные проблемы (pinned objects, large object heap, особенности работы кучи в полном .NET Framework и Mono).
Читать полностью »