Игрострой. Программирование. Оптимизация как камень преткновения

от автора

Всем привет! Для тех кто не знает, меня зовут Ш. Сергей!

Я хоть и программирую на Pascal/Assembler, но думаю что для людей, использующих другие ЯП, данная информация может быть полезна. Полностью рассмотреть вопросы оптимизации программ/игр практически не возможно, думаю для этого надо написать достаточно не малую книгу и всё равно что-нибудь да будет упущено.

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

Когда не смог пройти мимо.

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

В очередной раз перебирая код ZenGL и демо-версии в нём мой взгляд случайно остановился на спрайтовом движке (8-я демка) и, увидев некоторые огрехи движка я хотел узнать как этот код работает в 32-х битном Linux, потому что в 64-х битной системе работало всё более-менее приемлемо. Был лишь один маленький момент… у меня нет 32-х битного Linux и приключения мои начались с того, что я создал для себя очередную виртуальную машину (ВМ) Devuan-x86 основанную на Qemu.

Скрытый текст

Большинство ВМ которые я сделал основаны на Qemu. В данный момент это порядка 10 виртуальных машин, с разными ОС и разной архитектурой. В том числе KolibriOS, Debian-ARM32, Debian-ARM64, Windows, Raspberri PI. Слишком много иногда надо проверять кода на разных архитектурах, а перезагружать машину каждый раз ради очередной ВМ, не очень хорошая затея.

Основная моя рабочая ОС на данный момент Debian 12. Возможно я бы сменил его на какой-то другой дистрибутив, но уже столько всего настроено, что уже нет сильного желания переустанавливать ОС в очередной раз.

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

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

Многих вообще не будут интересовать ВМ ни в каком виде. Большинству нужна работа с определённой ОС и это не неправильно. О чём вы должны знать это о том, что если вы решили делать мобильные игры, то тут без ВМ практически не обойтись. Опять же в большинстве случаев они уже готовы для вас. Берёте Android Studio и там уже есть свои ВМ, которые надо только настроить. Так же и на машине Mak, там есть свои ВМ.

… наверно для ВМ надо будет выделить свою, отдельную, статью… )))

Установил FPC/Lazarus, дополнительные необходимые компоненты и запустил демку. Результат оказался не очень впечатляющим, 1000 спрайтов уже показывали просадки. Что вы можете увидеть на 5:44 (чуть дальше).

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

Когда я «вынырнул» из попытки оптимизации, прошло почти две недели. Результат был немного лучше чем на видео. Для слабых машин оптимизации почти не было, а для более-менее мощной машины получил порядка 17-20 кадров на 200 000 объектов (спрайтов). Это примерно в 2 раза лучше чем было, но смысла от подобного мало, так как эти объекты ещё не нагружены, а будет дополнительная нагрузка, всё просто упадёт.

Теперь для вашего понимания, ZenGL это в основном однопоточный движок, потому 200 000 объектов, это только на один поток/процессор.

Но всё это отступление от дела, давайте переходить к делу.

А нужна ли оптимизация?

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

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

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

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

Я вот пишу пишу, и всё больше фактов о том, что оптимизированием не нужно заниматься… Ну давайте добавим сюда ещё один факт: сейчас очень сильно оптимизируют код компиляторы и, то что вы делаете вручную, такие компиляторы могут сделать за более короткий срок и без вашего участия.

Так нужно ли оптимизировать программы? Когда их нужно оптимизировать?

Да оптимизация нужна. Вы один решили сделать большой проект, но на какой-то стадии проект начал тормозить на вашем компьютере. Без оптимизации, дальше ваш код будет работать только хуже. Потому оптимизировать код надо, и надо знать как.

Пусть пример будет всего один, но многим это будет важно (хотя может вы из группы, где в вашем проекте вы и занимаетесь оптимизацией? Вот и второй пример.).

Алгоритмы.

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

Хотите изучить получше алгоритмы? Посмотрите популярную книгу: Дональд Кнут «Искусство программирования», первый том. Возможно это то, что вам нужно.

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

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

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

Но для большинства алгоритмами будут считаться те, что уже установлены повсеместно.

Переменные, константы, массивы, структуры.

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

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

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

Что же я хотел вам этим сказать? Давайте посмотрим это на примере кода Pascal.

Скрытый текст
unit zgl_font;  {$I zgl_config.cfg}  interface uses   zgl_textures,   zgl_math_2d,   zgl_file,   zgl_memory,   zgl_types;  const   ZGL_FONT_INFO : array[ 0..13 ] of AnsiChar = ( 'Z', 'G', 'L', '_', 'F', 'O', 'N', 'T', '_', 'I', 'N', 'F', 'O', #0 ); type   zglPCharDesc = ^zglTCharDesc;   zglTCharDesc = record     Page      : Word;     Width     : Byte;     Height    : Byte;     ShiftX    : Integer;     ShiftY    : Integer;     ShiftP    : Integer;     TexCoords : array[ 0..3 ] of zglTPoint2D;   end;    zglPFont = ^zglTFont;   zglTFont = record     Count      : record       Pages : Word;       Chars : Word;                  end;      Pages      : array of zglPTexture;     CharDesc   : array[ 0..65535 ] of zglPCharDesc;     MaxHeight  : Integer;     MaxShiftY  : Integer;     Padding    : array[ 0..3 ] of Byte;      prev, next : zglPFont;   end;    zglPFontManager = ^zglTFontManager;   zglTFontManager = record     Count : Integer;     First : zglTFont;   end;  procedure font_Load( var fnt : zglPFont; var fntMem : zglTMemory );  var   managerFont : zglTFontManager;  implementation  //... procedure font_Load( var fnt : zglPFont; var fntMem : zglTMemory );   var     i     : Integer;     c     : LongWord;     fntID : array[ 0..13 ] of AnsiChar; begin   fntID[ 13 ] := #0;   mem_Read( fntMem, fntID, 13 );   if fntID <> ZGL_FONT_INFO Then     begin       if Assigned( fnt ) Then         FreeMemory( fnt );       fnt := nil;       exit;     end;    if not Assigned( fnt ) Then     fnt := font_Add();   mem_Read( fntMem, fnt.Count.Pages,  2 );   mem_Read( fntMem, fnt.Count.Chars,  2 );   mem_Read( fntMem, fnt.MaxHeight,    4 );   mem_Read( fntMem, fnt.MaxShiftY,    4 );   mem_Read( fntMem, fnt.Padding[ 0 ], 4 );   SetLength( fnt.Pages, fnt.Count.Pages );   for i := 0 to fnt.Count.Pages - 1 do     fnt.Pages[ i ] := nil;   for i := 0 to fnt.Count.Chars - 1 do     begin       mem_Read( fntMem, c, 4 );       zgl_GetMem( Pointer( fnt.CharDesc[ c ] ), SizeOf( zglTCharDesc ) );       {$IFDEF ENDIAN_BIG}       forceNoSwap := TRUE;       {$ENDIF}       mem_Read( fntMem, fnt.CharDesc[ c ].Page, 4 );       {$IFDEF ENDIAN_BIG}       forceNoSwap := FALSE;       {$ENDIF}       mem_Read( fntMem, fnt.CharDesc[ c ].Width, 1 );       mem_Read( fntMem, fnt.CharDesc[ c ].Height, 1 );       mem_Read( fntMem, fnt.CharDesc[ c ].ShiftX, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].ShiftY, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].ShiftP, 4 );       {$IFDEF ENDIAN_BIG}       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ].X, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ].Y, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 1 ].X, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 1 ].Y, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 2 ].X, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 2 ].Y, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 3 ].X, 4 );       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 3 ].Y, 4 );       {$ELSE}       mem_Read( fntMem, fnt.CharDesc[ c ].TexCoords[ 0 ], SizeOf( zglTPoint2D ) * 4 );       {$ENDIF}     end; end;
unit zgl_text;  {$I zgl_config.cfg}  interface uses   zgl_font,   zgl_math_2d,   zgl_types;  const   TEXT_HALIGN_LEFT    = $000001;   TEXT_HALIGN_CENTER  = $000002;   TEXT_HALIGN_RIGHT   = $000004;   TEXT_HALIGN_JUSTIFY = $000008;   TEXT_VALIGN_TOP     = $000010;   TEXT_VALIGN_CENTER  = $000020;   TEXT_VALIGN_BOTTOM  = $000040;   TEXT_CLIP_RECT      = $000080;   TEXT_FX_VCA         = $000100;   TEXT_FX_LENGTH      = $000200;  procedure text_Draw( Font : zglPFont; X, Y : Single; const Text : UTF8String; Flags : LongWord = 0 ); //... implementation  //... procedure text_Draw( Font : zglPFont; X, Y : Single; const Text : UTF8String; Flags : LongWord = 0 );   var     i, c, s  : Integer;     charDesc : zglPCharDesc;     quad     : array[ 0..3 ] of zglTPoint2D;     sx       : Single;     lastPage : Integer;     mode     : Integer; begin   if ( Text = '' ) or ( not Assigned( Font ) ) Then exit;   for i := 0 to Font.Count.Pages - 1 do     if not Assigned( Font.Pages[ i ] ) Then exit;    glColor4ubv( @textRGBA[ 0 ] );    Y := Y - Font.MaxShiftY * textScale;   if Flags and TEXT_HALIGN_CENTER > 0 Then     X := X - Round( text_GetWidth( Font, Text, textStep ) / 2 ) * textScale   else     if Flags and TEXT_HALIGN_RIGHT > 0 Then       X := X - Round( text_GetWidth( Font, Text, textStep ) ) * textScale;   sx := X;    if Flags and TEXT_VALIGN_CENTER > 0 Then     Y := Y - ( Font.MaxHeight div 2 ) * textScale   else     if Flags and TEXT_VALIGN_BOTTOM > 0 Then       Y := Y - Font.MaxHeight * textScale;    FillChar( quad[ 0 ], SizeOf( zglTPoint2D ) * 4, 0 );   charDesc := nil;   lastPage := -1;   c := utf8_GetID( Text, 1, @i );   s := 1;   i := 1;   if Flags and TEXT_FX_VCA > 0 Then     mode := GL_TRIANGLES   else     mode := GL_QUADS;   if not b2dStarted Then     begin       if Assigned( Font.CharDesc[ c ] ) Then         begin           lastPage := Font.CharDesc[ c ].Page;           batch2d_Check( mode, FX_BLEND, Font.Pages[ Font.CharDesc[ c ].Page ] );            glEnable( GL_BLEND );           glEnable( GL_TEXTURE_2D );           glBindTexture( GL_TEXTURE_2D, Font.Pages[ Font.CharDesc[ c ].Page ].ID );           glBegin( mode );         end else           begin             glEnable( GL_BLEND );             glEnable( GL_TEXTURE_2D );             glBegin( mode );           end;     end;   while i <= Length( Text ) do     begin       if Text[ i ] = #10 Then         begin           X := sx;           Y := Y + Font.MaxHeight * textScale;         end;       c := utf8_GetID( Text, i, @i );        if ( Flags and TEXT_FX_LENGTH > 0 ) and ( s > textLength ) Then         begin           if s > 1 Then             begin               if Assigned( textLCoord ) Then                 begin                   textLCoord.X := quad[ 0 ].X + Font.Padding[ 0 ] * textScale;                   textLCoord.Y := quad[ 0 ].Y + Font.Padding[ 1 ] * textScale;                 end;               if Assigned( textLCharDesc ) Then                 textLCharDesc^ := charDesc^;             end;           break;         end;       INC( s );        charDesc := Font.CharDesc[ c ];       if not Assigned( charDesc ) Then continue;        if lastPage <> charDesc.Page Then         begin           lastPage := charDesc.Page;            if ( not b2dStarted ) Then             begin               glEnd();                glBindTexture( GL_TEXTURE_2D, Font.Pages[ charDesc.Page ].ID );               glBegin( mode );             end else               if batch2d_Check( mode, FX_BLEND, Font.Pages[ charDesc.Page ] ) Then                 begin                   glEnable( GL_BLEND );                    glEnable( GL_TEXTURE_2D );                   glBindTexture( GL_TEXTURE_2D, Font.Pages[ charDesc.Page ].ID );                   glBegin( mode );                 end;         end;        quad[ 0 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;       quad[ 0 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;       quad[ 1 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;       quad[ 1 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale;       quad[ 2 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale;       quad[ 2 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;       quad[ 3 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale;       quad[ 3 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;        if Flags and TEXT_FX_VCA > 0 Then         begin           glColor4ubv( @fx2dVCA1[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 0 ] );           glVertex2fv( @quad[ 0 ] );            glColor4ubv( @fx2dVCA2[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 1 ] );           glVertex2fv( @quad[ 1 ] );            glColor4ubv( @fx2dVCA3[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 2 ] );           glVertex2fv( @quad[ 2 ] );            glColor4ubv( @fx2dVCA3[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 2 ] );           glVertex2fv( @quad[ 2 ] );            glColor4ubv( @fx2dVCA4[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 3 ] );           glVertex2fv( @quad[ 3 ] );            glColor4ubv( @fx2dVCA1[ 0 ] );           glTexCoord2fv( @charDesc.TexCoords[ 0 ] );           glVertex2fv( @quad[ 0 ] );         end else           begin             glTexCoord2fv( @charDesc.TexCoords[ 0 ] );             glVertex2fv( @quad[ 0 ] );              glTexCoord2fv( @charDesc.TexCoords[ 1 ] );             glVertex2fv( @quad[ 1 ] );              glTexCoord2fv( @charDesc.TexCoords[ 2 ] );             glVertex2fv( @quad[ 2 ] );              glTexCoord2fv( @charDesc.TexCoords[ 3 ] );             glVertex2fv( @quad[ 3 ] );           end;        X := X + ( charDesc.ShiftP + textStep ) * textScale;     end;    if not b2dStarted Then     begin       glEnd();        glDisable( GL_TEXTURE_2D );       glDisable( GL_BLEND );     end; end; //...

Исходный код здесь.

Обратите внимание на второй модуль zgl_text. Этот модуль использует данные полученные из первого модуля и постоянно их просчитывает. Если модуль zgl_font используется только при загрузке приложения, то zgl_text используется постоянно, где надо вывести текст. И код приведённый ниже, будет отрабатывать каждый раз, когда надо прорисовать очередной символ.

quad[ 0 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale; quad[ 0 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale; quad[ 1 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale; quad[ 1 ].Y := Y + ( charDesc.ShiftY + ( Font.MaxHeight - charDesc.Height ) - Font.Padding[ 1 ] ) * textScale; quad[ 2 ].X := X + ( charDesc.ShiftX + charDesc.Width + Font.Padding[ 2 ] ) * textScale; quad[ 2 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale; quad[ 3 ].X := X + ( charDesc.ShiftX - Font.Padding[ 0 ] ) * textScale; quad[ 3 ].Y := Y + ( charDesc.ShiftY + charDesc.Height + ( Font.MaxHeight - charDesc.Height ) + Font.Padding[ 3 ] ) * textScale;

А если таких символов будет тысячи? Десятки тысяч?

Нет, ну конечно, текст обычно статичен и нет необходимости его постоянно перерисовывать. Только вот этот пример из игрового движка… И чем чаще будет обновляться окно в игре (зачастую), тем лучше. Но ведь тогда и код работать будет чаще! И нагрузка будет больше.

В данном случае я использовал другую оптимизацию.

Скрытый текст
unit zgl_font;  {$I zgl_config.cfg}  interface uses   zgl_textures,   zgl_types,   zgl_file,   zgl_log,   zgl_memory;  const   ZGL_FONT_INFO: array[0..13] of AnsiChar = ('Z', 'G', 'L', '_', 'F', 'O', 'N', 'T', '_', 'I', 'N', 'F', 'O', #0);   MAX_USE_FONT = 10;   Enable       = 1;   UseFnt       = 2;    PaddingX1    = 0;   PaddingX2    = 2;   PaddingY1    = 1;   PaddingY2    = 3;  type   zglPCharDesc = ^zglTCharDesc;   zglTCharDesc = record     Page     : Word;     Width    : Byte;     Height   : Byte;     ShiftX   : Integer;     ShiftY   : Integer;     ShiftP   : Integer;     TexCoords: array[0..3] of zglTPoint2D;     xx1, xx2, yy1, yy2: Single;     _x1, _x2, _y1, _y2: Single;   end;  type   zglPFont = ^zglTFont;   zglTFont = record     Count     : record       Pages: Word;       Chars: Word;                  end;      Flags     : LongWord;     Scale     : Single;     ScaleNorm : Single;     Pages     : array of zglPTexture;     CharDesc  : array[0..65535] of zglPCharDesc;     MaxHeight : Integer;     MaxShiftY : Integer;     Padding   : array[0..3] of Byte;     TextScaleStandart : Single;   end;  type   zglPFontManager = ^zglTFontManager;   zglTFontManager = record     Count: Integer;     Font: array[0..MAX_USE_FONT - 1] of zglPFont;   end; //... procedure font_Load(var fnt: LongWord; var fntMem: zglTMemory);  var   managerFont: zglTFontManager;  implementation  //... procedure font_Load(var fnt: LongWord; var fntMem: zglTMemory); var   i    : Integer;   c    : LongWord;   fntID: array[0..13] of AnsiChar;   useFont: zglPFont;   charDesc9, charDesc32, charDescC: zglPCharDesc; begin   if fnt <> zglUError then     exit;   fntID[13] := #0;   mem_Read(fntMem, fntID, 13);   if fntID <> ZGL_FONT_INFO Then   begin     exit;   end;   fnt := font_Add;   if fnt = zglUError then     exit;   useFont := managerFont.Font[fnt];   useFont^.TextScaleStandart := 0;   mem_Read(fntMem, useFont^.Count.Pages,  2);   mem_Read(fntMem, useFont^.Count.Chars,  2);   mem_Read(fntMem, useFont^.MaxHeight,    4);   mem_Read(fntMem, useFont^.MaxShiftY,    4);   mem_Read(fntMem, useFont^.Padding[0],   4);   SetLength(useFont^.Pages, useFont.Count.Pages);    for i := 0 to useFont^.Count.Pages - 1 do     useFont.Pages[i] := nil;   for i := 0 to useFont^.Count.Chars - 1 do   begin     mem_Read(fntMem, c, 4);     zgl_GetMem(Pointer(useFont^.CharDesc[c]), SizeOf(zglTCharDesc));     charDescC := useFont^.CharDesc[c];     mem_Read(fntMem, charDescC.Page, 4);     mem_Read(fntMem, charDescC^.Width, 1);     mem_Read(fntMem, charDescC^.Height, 1);     if useFont^.TextScaleStandart < charDescC^.Width then       useFont^.TextScaleStandart := charDescC^.Width;     mem_Read(fntMem, charDescC^.ShiftX, 4);     mem_Read(fntMem, charDescC^.ShiftY, 4);     mem_Read(fntMem, charDescC^.ShiftP, 4);     mem_Read(fntMem, charDescC.TexCoords[0], SizeOf(zglTPoint2D) * 4);     charDescC^._x1 := charDescC^.ShiftX - useFont^.Padding[PaddingX1];     charDescC^._x2 := charDescC^.ShiftX + charDescC^.Width + useFont^.Padding[PaddingX2];     charDescC^._y1 := charDescC^.ShiftY + useFont^.MaxHeight - charDescC^.Height - useFont^.Padding[PaddingY1];     charDescC^._y2 := charDescC^.ShiftY + useFont^.MaxHeight + useFont^.Padding[PaddingY2];   end;   if useFont^.CharDesc[32] <> nil then     charDesc32 := useFont^.CharDesc[32]   else     charDesc32 := useFont^.CharDesc[49];   if useFont^.CharDesc[32] <> nil then     charDescC := useFont^.CharDesc[33]   else     charDescC := useFont^.CharDesc[49];   charDesc32^.Width := charDescC^.Width;   charDesc32^.Height := charDescC^.Height;   charDesc32^.ShiftX := charDescC^.ShiftX;   charDesc32^.ShiftY := charDescC^.ShiftY;   charDesc32^.ShiftP := charDescC^.ShiftP;   charDesc32^._x1 := charDescC^._x1;   charDesc32^._x2 := charDescC^._x2;   charDesc32^._y1 := charDescC^._y1;   charDesc32^._y2 := charDescC^._y2;    // "tab"   zgl_GetMem(Pointer(useFont.CharDesc[9]), SizeOf(zglTCharDesc));   charDesc9 := useFont.CharDesc[9];   charDesc9^.Page := charDesc32^.Page;   charDesc9^.Width := charDesc32^.Width * 4;   charDesc9^.Height := charDesc32^.Height;   charDesc9^.ShiftX := charDesc32^.ShiftX;   charDesc9^.ShiftY := charDesc32^.ShiftY;   charDesc9^.ShiftP := charDesc32^.ShiftP * 4;   charDesc9^.TexCoords[0] := charDesc32^.TexCoords[0];   charDesc9^.TexCoords[1] := charDesc32^.TexCoords[1];   charDesc9^.TexCoords[2] := charDesc32^.TexCoords[2];   charDesc9^.TexCoords[3] := charDesc32^.TexCoords[3];   charDesc9^._x1 := charDesc32^._x1;   charDesc9^._x2 := charDesc9^.ShiftX + charDesc9^.Width + useFont^.Padding[PaddingX2];   charDesc9^._y1 := charDesc32^._y1;   charDesc9^._y2 := charDesc32^._y2;    if useFont^.MaxHeight > useFont^.TextScaleStandart then     useFont^.TextScaleStandart := useFont^.MaxHeight;   useFont := nil; end;
unit zgl_text;  {$I zgl_config.cfg}  interface //...  procedure text_Draw(fnt: LongWord; X, Y: Single; const Text: UTF8String; Flags: LongWord = 0);  procedure setFontTextScale(_Scale: LongWord; fnt: LongWord); //... implementation  //... procedure setFontTextScale(_Scale: LongWord; fnt: LongWord); var   i: Integer;   charDesc: zglPCharDesc; begin   if fnt > MAX_USE_FONT then     exit;   useFont := managerFont.Font[fnt];   useFont.Scale := useFont.ScaleNorm * _Scale / 10;   for i := 0 to 65535 do   begin     if Assigned(useFont.CharDesc[i]) then       charDesc := useFont.CharDesc[i]     else       Continue;      charDesc^.xx1 := charDesc^._x1 * useFont.Scale;     charDesc^.yy1 := charDesc^._y1 * useFont.Scale;     charDesc^.xx2 := charDesc^._x2 * useFont.Scale;     charDesc^.yy2 := charDesc^._y2 * useFont.Scale;   end; end;  procedure text_Draw(fnt: LongWord; X, Y: Single; const Text: UTF8String; Flags: LongWord = 0); var   i, c, s : LongWord;   charDesc: zglPCharDesc;   quad    : array[0..3] of zglTPoint3D;   sx      : Single;   lastPage: Integer;   mode    : Integer; begin   if fnt > MAX_USE_FONT then     exit;   if (Text = '') or ((managerFont.Font[fnt].Flags and UseFnt) = 0) Then     exit;   useFont := managerFont.Font[fnt];   for i := 0 to useFont.Count.Pages - 1 do     if not Assigned(useFont.Pages[i]) Then exit;    glColor4ubv(@textRGBA);    if Off_TextScale then     Y := Y - useFont.MaxShiftY * useScaleEx   else     Y := Y - useFont.MaxShiftY * useFont.Scale;    if Flags and TEXT_HALIGN_CENTER > 0 Then     X := X - Round(text_GetWidth(fnt, Text, textStep) / 2)   else     if Flags and TEXT_HALIGN_RIGHT > 0 Then       X := X - Round(text_GetWidth(fnt, Text, textStep));   sx := X;    if Flags and TEXT_VALIGN_CENTER > 0 Then     Y := Y - (useFont.MaxHeight div 2)   else     if Flags and TEXT_VALIGN_BOTTOM > 0 Then       Y := Y - useFont.MaxHeight;    FillChar(quad[0], SizeOf(zglTPoint2D) * 4, 0);   charDesc := nil;   lastPage := -1;   c := utf8_GetID(Text, 1, @i);   s := 1;   i := 1;   if Flags and TEXT_FX_VCA > 0 Then     mode := GL_TRIANGLES   else     mode := GL_QUADS;   if not b2dStarted Then   begin     if Assigned(useFont.CharDesc[c]) Then     begin       lastPage := useFont.CharDesc[c].Page;       batch2d_Check(mode, FX_BLEND, useFont.Pages[useFont.CharDesc[c].Page]);        glEnable(GL_BLEND);       glEnable(GL_TEXTURE_2D);       glBindTexture(GL_TEXTURE_2D, useFont.Pages[useFont.CharDesc[c].Page].ID);       glBegin(mode);     end else     begin       glEnable(GL_BLEND);       glEnable(GL_TEXTURE_2D);       glBegin(mode);     end;   end;   while i <= Length(Text) do   begin     if Text[i] = #10 Then     begin       X := sx;       if Off_TextScale then         Y := Y + useFont.MaxHeight * useScaleEx       else         Y := Y + useFont.MaxHeight * useFont.Scale;     end;     c := utf8_GetID(Text, i, @i);      if (Flags and TEXT_FX_LENGTH > 0) and (s > textLength) Then     begin       if s > 1 Then       begin         if Assigned(textLCoord) Then         begin           if Off_TextScale then           begin             textLCoord.X := quad[0].X + useFont.Padding[PaddingX1] * useScaleEx;             textLCoord.Y := quad[0].Y + useFont.Padding[PaddingY1] * useScaleEx;           end           else begin             textLCoord.X := quad[0].X + useFont.Padding[PaddingX1] * useFont.Scale;             textLCoord.Y := quad[0].Y + useFont.Padding[PaddingY1] * useFont.Scale;           end;         end;         if Assigned(textLCharDesc) Then           textLCharDesc^ := charDesc^;       end;       break;     end;     INC(s);      charDesc := useFont.CharDesc[c];     if (c = 10) and (c = 13) then       Continue;     if not Assigned(charDesc) Then       charDesc := useFont.CharDesc[63];       if lastPage <> charDesc.Page Then     begin       lastPage := charDesc.Page;        if (not b2dStarted) Then       begin         glEnd();          glBindTexture(GL_TEXTURE_2D, useFont.Pages[charDesc.Page].ID);         glBegin(mode);       end else         if batch2d_Check(mode, FX_BLEND, useFont.Pages[charDesc.Page]) Then         begin           glEnable(GL_BLEND);            glEnable(GL_TEXTURE_2D);           glBindTexture(GL_TEXTURE_2D, useFont.Pages[charDesc.Page].ID);           glBegin(mode);         end;     end;      if Off_TextScale then     begin       quad[0].X := X + (charDesc.ShiftX - useFont.Padding[PaddingX1]) * useScaleEx;       quad[0].Y := Y + (charDesc.ShiftY + useFont.MaxHeight - charDesc.Height - useFont.Padding[PaddingY1]) * useScaleEx;        quad[1].X := X + (charDesc.ShiftX + charDesc.Width + useFont.Padding[PaddingX2]) * useScaleEx;       quad[2].Y := Y + (charDesc.ShiftY + useFont.MaxHeight + useFont.Padding[PaddingY2]) * useScaleEx;     end else     begin       quad[0].X := X + charDesc.xx1;       quad[0].Y := Y + charDesc.yy1;        quad[1].X := X + charDesc.xx2;       quad[2].Y := Y + charDesc.yy2;     end;     quad[1].Y := quad[0].Y;     quad[2].X := quad[1].X;     quad[3].X := quad[0].X;     quad[3].Y := quad[2].Y;     quad[0].Z := 0;     quad[1].Z := 0;     quad[2].Z := 0;     quad[3].Z := 0;      if Flags and TEXT_FX_VCA > 0 Then     begin       {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[0, 0], fx2dVCA[0, 1], fx2dVCA[0, 2], fx2dVCA[0, 3]);       glTexCoord2fv(@charDesc.TexCoords[0]);       glVertex3fv(@quad[0]);        {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[1, 0], fx2dVCA[1, 1], fx2dVCA[1, 2], fx2dVCA[1, 3]);       glTexCoord2fv(@charDesc.TexCoords[1]);       glVertex3fv(@quad[1]);        {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[2, 0], fx2dVCA[2, 1], fx2dVCA[2, 2], fx2dVCA[2, 3]);       glTexCoord2fv(@charDesc.TexCoords[2]);       glVertex3fv(@quad[2]);        {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[2, 0], fx2dVCA[2, 1], fx2dVCA[2, 2], fx2dVCA[2, 3]);       glTexCoord2fv(@charDesc.TexCoords[2]);       glVertex3fv(@quad[2]);        {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[3, 0], fx2dVCA[3, 1], fx2dVCA[3, 2], fx2dVCA[3, 3]);       glTexCoord2fv(@charDesc.TexCoords[3]);       glVertex3fv(@quad[3]);        {$IfDef USE_GLES}_glColor4f{$Else}glColor4f{$EndIf}(fx2dVCA[1, 0], fx2dVCA[1, 1], fx2dVCA[1, 2], fx2dVCA[1, 3]);       glTexCoord2fv(@charDesc.TexCoords[0]);       glVertex3fv(@quad[0]);     end else     begin       glTexCoord2fv(@charDesc.TexCoords[0]);       glVertex3fv(@quad[0]);        glTexCoord2fv(@charDesc.TexCoords[1]);       glVertex3fv(@quad[1]);        glTexCoord2fv(@charDesc.TexCoords[2]);       glVertex3fv(@quad[2]);        glTexCoord2fv(@charDesc.TexCoords[3]);       glVertex3fv(@quad[3]);     end;      if Off_TextScale then       X := X + (charDesc.ShiftP + textStep) * useScaleEx     else       X := X + (charDesc.ShiftP + textStep) * useFont.Scale;   end;    if not b2dStarted Then   begin     glEnd();      glDisable(GL_TEXTURE_2D);     glDisable(GL_BLEND);   end; end;

Исходный код здесь.

и получил в конечном итоге вот такой код:

if Off_TextScale then begin   quad[0].X := X + (charDesc.ShiftX - useFont.Padding[PaddingX1]) * useScaleEx;   quad[0].Y := Y + (charDesc.ShiftY + useFont.MaxHeight - charDesc.Height - useFont.Padding[PaddingY1]) * useScaleEx;    quad[1].X := X + (charDesc.ShiftX + charDesc.Width + useFont.Padding[PaddingX2]) * useScaleEx;   quad[2].Y := Y + (charDesc.ShiftY + useFont.MaxHeight + useFont.Padding[PaddingY2]) * useScaleEx; end else begin   quad[0].X := X + charDesc.xx1;   quad[0].Y := Y + charDesc.yy1;    quad[1].X := X + charDesc.xx2;   quad[2].Y := Y + charDesc.yy2; end; quad[1].Y := quad[0].Y; quad[2].X := quad[1].X; quad[3].X := quad[0].X; quad[3].Y := quad[2].Y;

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

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

Я хотел обратить внимание именно на код! А не на то, что используется устаревший OpenGL! Данная проблема в данном коде явно показана и подобные действия можно использовать в достаточно не малом количестве кода.

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

Что же всё-таки по оптимизации?

Что вы должны запомнить: это то, что зачастую не стоит тратить время на оптимизацию. Оптимизация может занять у вас очень много времени, возможно заставит вас изучить подробно код, который вы хотите оптимизировать и код связанный с кодом, который вы хотите оптимизировать (а так же может придётся изучить код связанный с кодом, который связан с кодом который вы оптимизируете). Это не лёгкая задача!

Постарайтесь использовать уже готовые оптимизации. Изучите ЯП который вы используете, изучите движок, который вы используете. Многое реализовано до нас.

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

Знайте, профилировщик сможет вам показать нагрузку в определённых процедурах/функциях, но оптимизировать код в нагруженных функциях, возможно надо разными путями, а не только изменяя код в самой нагруженной функции.

Советов может быть множество, по оптимизации определённых действий:

  • развернуть циклы.

  • постараться избавится от циклов.

  • избавится от удаления/создания объектов (если в этом нет необходимости).

  • использовать статичные (уже вычисленные) данные.

  • используйте ссылочные данные.

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

  • многое другое.

Плохо ли то что занимаешься оптимизацией для своего общего образования?

Нет, такие вещи ни когда не будут плохи. Они плохи тем, что вы тратите на них время. Но вы таким образом более полно начинаете понимать как работает код (особенно если это вам интересно и важно).

Возможно стоит изучить данную тему? Ведь многие люди уже старались оптимизировать разный код, возможно есть множество статей на просторах интернета? Множество книг?

Ни когда не забывайте использовать общедоступные ресурсы для изучения информации! Сейчас очень много её в общем доступе!

Окончание.

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

На этом я завершаю данную статью. Задел я очень малую часть оптимизации, но надеюсь достаточно понятно изложил данную тему. Очень большую статью я и не хотел делать.

Так же я выкладываю видео на своём канале, где задеваю разные темы по программированию и не только.

Если понравилась статья — лайкайте.

Не понравилась? — Дизлайкайте! )))

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

Успехов!


ссылка на оригинал статьи https://habr.com/ru/articles/840968/