Раздел: Как стать программистом / Секреты программирования /

Ускорение циклов

Lazarus IDE: Основы программирования в Windows Lazarus IDE: Основы программирования в Windows

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

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

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

Как можно ускорить выполнение циклов? Есть несколько способов:

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

Именно о последнем способе сегодня и расскажу. Вот пример программы:

program myprog;

uses SysUtils, DateUtils;

const MAX_NUM = 50000;

var i : WORD;
    m : array[1..MAX_NUM] of WORD;
    TimeStart, TimeEnd : TDateTime;

//***************************************************************
// Просто функция, которая ничего не делает, а лишь занимает время
//***************************************************************
function SimpleFunc(x : WORD; dt : TDateTime) : boolean;
var i : WORD;
begin
  Result := TRUE;
  for i := 1 to 100 do
    begin
      IntToStr(x);
      TimeToStr(dt);
    end;
end;

//***************************************************************
// ОСНОВНАЯ ПРОГРАММА
//***************************************************************
begin
  //Заполняем массив
  for i := 1 to MAX_NUM do
    if i < 1000 then m[i] := i
    else m[i] := 0;

  //Перебор всех элементов массива
  TimeStart := Time;
  for i := 1 to MAX_NUM do
    SimpleFunc(m[i], TimeStart);
  TimeEnd := Time;
  WriteLn('Time Cycle 1 : ',
          MilliSecondsBetween(TimeStart, TimeEnd));

  //Перебор элементов массив, которые не равны 0
  TimeStart := Time;
  for i := 1 to MAX_NUM do
    if m[i] <> 0 then SimpleFunc(m[i], TimeStart);
  TimeEnd := Time;
  WriteLn('Time Cycle 2 : ',
          MilliSecondsBetween(TimeStart, TimeEnd));

  //Выводить элементы массива, пока не найдём 0
  TimeStart := Time;
  for i := 1 to MAX_NUM do
    begin
      if m[i] = 0 then Break;
      SimpleFunc(m[i], TimeStart);
    end;
  TimeEnd := Time;
  WriteLn('Time Cycle 3 : ',
          MilliSecondsBetween(TimeStart, TimeEnd));

  //Вывести элементы массива, которые не равны 0
  TimeStart := Time;
  for i := 1 to MAX_NUM do
    begin
      if m[i] = 0 then Continue;
      SimpleFunc(m[i], TimeStart);
    end;
  TimeEnd := Time;
  WriteLn('Time Cycle 4 : ',
          MilliSecondsBetween(TimeStart, TimeEnd));

  WriteLn(#10#13'The end. Press ENTER...');
  ReadLn;
end.

Программа, конечно, учебная, не имеющая какого-либо практического смысла. Но для понимания обозначенного в начале статьи вопроса вполне подходит.

Итак, у нас есть некий массив целых чисел и есть задача - сделать что-то с элементами массива, которые не равны нулю. Это “что-то” делается в функции SimpleFunc. То есть мы перебираем элементы массива в цикле и в каждой итерации вызываем эту функцию.

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

Первый цикл перебирает все элементы. Так поступают обычно новички, не обращая внимание на то, что цикл можно ускорить, то есть сократить время выполнения цикла в целом.

Остальные три цикла делают почти то же самое, но за более короткое время. И делают это разными способами.

Способ первый - проверить, не равен ли элемент массива нулю. И если это так, то выполнить действие:

for i := 1 to MAX_NUM do
  if m[i] <> 0 then SimpleFunc(m[i], TimeStart);

Это вполне приемлемый способ. Только он не всегда удобен в использовании (особенно когда в блоке if выполняется не одна инструкция, а несколько).

Второй способ такой:

for i := 1 to MAX_NUM do
  begin
    if m[i] = 0 then Break;
    SimpleFunc(m[i], TimeStart);
  end;

Здесь мы поступаем наоборот - проверяем элемент массива на равенство нулю. И если это так, то прерываем цикл.

Однако этот способ можно использовать только тогда, когда мы точно знаем, что после первого найденного нуля в массиве все остальные его элементы также равны нулю. Если же это не так, то нам пригодится третий способ:

for i := 1 to MAX_NUM do
  begin
    if m[i] = 0 then Continue;
    SimpleFunc(m[i], TimeStart);
  end;

Здесь мы также проверяем элемент на равенство нулю, но не прерываем цикл, а продолжаем его, пропуская код в теле цикла.

А теперь посмотрите на то, что выведет эта программа, и, как говорится, почувствуйте разницу:

Ускорение циклов

Даже на таких пустяках мы можем сэкономить более одной секунды. Так что всегда вдумчиво подходите к оптимизации циклов - во многих случаях можно избежать выполнения каких-то участков кода в теле цикла.

Также никогда не стоит, например, присваивать значение какой-то переменной в теле цикла, если на протяжении этого цикла её значение не меняется.

Также в подобных случаях может возникнуть соблазн, например, перенести проверку значения элемента массива в функцию:

function SimpleFunc(x : WORD; dt : TDateTime) : boolean;
var i : WORD;
begin
  Result := TRUE;
  if x = 0 then Exit;
end;

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

Ну и посмотрите видео, если что-то осталось непонятным:

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


Первые шаги в программирование Первые шаги в программирование

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

Помощь студентам. Курсовые, дипломы, чертежи (КОМПАС), задачи по программированию: Pascal/Delphi/Lazarus; С/С++; Ассемблер; языки программирования ПЛК; JavaScript; VBScript; Fortran; Python и др. Разработка (доработка) ПО ПЛК (предпочтение - ОВЕН, CoDeSys 2 и 3), а также программирование панелей оператора, программируемых реле и других приборов систем автоматизации. Подробнее...

Инфо-МАСТЕР ®
Все права защищены ©
e-mail: mail@info-master.su

Яндекс.Метрика