Pull to refresh

Comments 17

Чему меня в своё время научила IDE Pascal 5.5, так это стилю кодирования.

Там, к слову, ещё не было подсветки синтаксиса. Зато был энтузиазм автора в написании формул.

Я долго пытался понять, почему компилятор мне говорит Unexpected end of file без каких-либо полезных подробностей. Через несколько часов отладки и расстановки пробелов я обнаружил, что вместо )*( в коде написал )(*.

Надеюсь, код в статье не повторяет стилистически production и вообще от прода на расстоянии пушечного выстрела.

Раз уж вы не побоялись выложить свой код, не побоюсь его покритиковать.

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

Вы уверены? Не будет ли у вас проблем с точностью?

Соглашусь с автором выше, код у вас оформлен не то, чтобы читабельно. Что за массив q? Почему 4081 итерация?

Еще в глаза бросилось, зачем вы выводите chr(13) вместо использования writeln?

Использование Longint для формирования загловка bmp, серьезно? Как-то костыльно уж слишком.

ordi? ordi2? Менее понятные имена было бы сложно придумать.

Основной цикл по t не стоит писать весь на одной строке. У вас какое-то жесткое ограничение по строчкам в ТЗ прописано?

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

Еще, в паскале, по-моему, был тип compex. Так что можно было бы использовать его в ваших вычислениях.

chr(13) тут переводит каретку в начало, что позволяет затирать строку без очистки всего вывода

program Project53;

{$APPTYPE CONSOLE}

const
  Width = 1024;
  Height = 1024;
  LineSize = Width * (4 * 15);
  absc: Extended = -1.96680095;
  ordi: Extended = 0.00000478;
  size: Extended = 0.00000014;
  q: array[0..254] of Byte = (234, 94, 198, 83, 178, 216, 183, 78, 41, 84, 119, 63, 211, 71, 123, 38,
    223, 73, 197, 249, 126, 227, 211, 5, 36, 36, 128, 5, 151, 2, 198, 166, 197, 181, 142, 52, 174, 151,
    244, 164, 255, 62, 173, 75, 21, 197, 126, 225, 130, 146, 244, 175, 86, 1, 180, 253, 198, 191, 50, 36,
    233, 200, 150, 221, 176, 73, 23, 161, 71, 224, 41, 69, 139, 245, 44, 40, 68, 45, 147, 127, 73, 39,
    156, 189, 191, 20, 19, 15, 27, 203, 206, 97, 156, 111, 189, 126, 87, 209, 209, 36, 23, 17, 15, 49,
    172, 58, 146, 65, 93, 214, 80, 80, 168, 177, 231, 81, 122, 75, 196, 189, 141, 185, 105, 138, 152,
    225, 171, 28, 120, 103, 29, 13, 140, 172, 20, 235, 214, 174, 237, 70, 179, 188, 127, 169, 40, 58,
    242, 93, 32, 252, 78, 13, 26, 221, 141, 178, 25, 171, 4, 79, 231, 137, 83, 113, 230, 88, 90, 124, 18,
    217, 194, 46, 236, 126, 32, 228, 103, 53, 206, 150, 28, 159, 237, 138, 83, 114, 58, 47, 176, 254,
    161, 177, 138, 24, 191, 38, 59, 225, 182, 112, 250, 39, 10, 47, 2, 96, 101, 93, 21, 116, 129, 117,
    172, 209, 145, 64, 95, 149, 223, 155, 65, 178, 142, 224, 92, 93, 159, 199, 116, 52, 178, 214, 192,
    230, 47, 198, 68, 75, 179, 0, 8, 220, 51, 69, 171, 81, 41, 95, 108, 20, 121, 18, 215, 15, 75);

var
  BMP: file;
  ScanLine: array[0..LineSize - 1] of Byte;
  Pallete: array[0..255] of array[0..3] of Byte;
  Header: array[0..13] of Longint = ($4D42, 0, 0, 1078, 40, 0, 0, $080001, 0, 0, 2834, 2834, 0, 0);

begin
  // Prepare bitmap
  Header[5] := Width;
  Header[6] := Height;
  var Pitch := Width;
  if Pitch and 3 <> 0 then
    Pitch := (Pitch + 4) and $FFFFFFFC;
  Pitch := Pitch * Height;
  Header[9] := Pitch;
  Header[1] := Header[9] + 1078;

  Assign(BMP, 'Mandelbrot.bmp');
  Rewrite(BMP, 1);
  BlockWrite(BMP, Header, 2);
  BlockWrite(BMP, Header[1], 52);
  for var i := 0 to 254 do
  begin
    Pallete[i][0] := Round(127 + 127 * Cos(2 * Pi * (i + 16) / 255));
    Pallete[i][1] := Round(127 + 127 * Sin(2 * Pi * (i + 16) / 255));
    Pallete[i][2] := q[i];
    Pallete[i][3] := 0
  end;
  for var i := 0 to 2 do
    Pallete[255][i] := 255;
  Pallete[255][3] := 0;
  BlockWrite(BMP, Pallete, 256 * 4);
  //
  var step: Extended := size / Width;
  for var Y := 0 to Height - 1 do
  begin
    var n: Extended := (ordi - step * (Height - 1) / 2) + Y * step;
    for var X := 0 to Width - 1 do
    begin
      var m: Extended := (absc - step * (Width - 1) / 2) + X * step;
      var c: Extended := m;
      var d: Extended := n;
      var t: LongInt := 4081;
      var cc: Extended;
      var dd: Extended;
      repeat
        cc := c * c;
        dd := d * d;
        d := (c + c) * d + n;
        c := cc - dd + m;
        Dec(t)
      until (t = 0) or (cc + dd > 1000000.0);
      if t = 0 then
        ScanLine[X] := 255
      else
        ScanLine[X] := t mod 255;
    end;
    BlockWrite(BMP, ScanLine, Pitch div Height);
    Write('Process: ', Y + 1, Chr(13));
  end;
  Close(BMP);
  Writeln('');
  Writeln('Done');
  Readln;
end.

Пролил чуть света, но все равно ничего не понятно)

Код, конечно, праздник математики: количество непонятных магических констант зашкаливает. Но да, всё работает и картинка рисуется. Правда, в бесплатной Delphi Community Edition не работает консольный компилятор. Придётся создать новый проект Console Application, и вставить туда код из статьи.

Посмотрел код и вспомнил как в школе программировал. Даже стиль тот же. Тогда был важен только результат. И чтобы работало быстро...

Репозиторий на github не завели? Ну и сделайте GUI-приложение. Раз у вас получается сформировать файл BMP- пишите его в память и загружайте в TImage. А по клику на изображение делайте масштабирование. Или по таймеру.

И будет у вас видео как на YouTube.
Удачи!

Вообще, я думаю, что целью была программа, которая ничего не тянет. Даже графику. Здесь нет ни канваса, ни енкодера для битмапа. Ничего вообще не подключено, кроме модуля, который подключен автоматически (System.pas).


procedure TForm1.DisplayButtonClick(Sender: TObject);
var
  MS: TMemoryStream;
begin

MS:=TMemoryStream.Create;
try

 h[5]:=horiz; h[6]:=vert;
  a:=horiz; if (a and 3<>0) then a:=(a+4) and $FFFFFFFC; h[9]:=a*vert; h[1]:=h[9]+1078;
  //assign(f,'Mandelbrot.bmp'); rewrite(f,1);
  MS.Write(h, 2);
  MS.Write(h[1], 52);
  //blockwrite(f,h,2); blockwrite(f,h[1],52);
  for a:=0 to 254 do
  begin
    pal[a][0]:=round(127+127*cos(2*pi*(a+16)/255)); pal[a][1]:=round(127+127*sin(2*pi*(a+16)/255)); pal[a][2]:=q[a]; pal[a][3]:=0
  end;
  for a:=0 to 2 do pal[255][a]:=255; pal[255][3]:=0;

  //blockwrite(f,pal,1024);
  MS.Write(pal, 1024);
  step:=size/horiz;
  absc2:=absc-step*(horiz-1)/2; ordi2:=ordi-step*(vert-1)/2;
  for b:=0 to vert-1 do
  begin
    n:=ordi2+b*step;
    for a:=0 to horiz-1 do
    begin
      m:=absc2+a*step;
      c:=m; d:=n; t:=4081;
      repeat cc:=c*c; dd:=d*d; d:=(c+c)*d+n; c:=cc-dd+m; dec(t) until (t=0) or (cc+dd>1000000.0);
      if (t=0) then s[a]:=255 else s[a]:=t mod 255;
    end;
    MS.Write(s, h[9] div vert);
    //blockwrite(f,s,h[9] div vert);
    //write('Done: ',b+1,chr(13))
  end;

  MS.Position:=0;
  Image1.Picture.LoadFromStream(MS);
finally
  MS.Free;
end;
  //close(f)
end;

procedure TForm1.ZoomInButtonClick(Sender: TObject);
begin
 size := size-size*0.2;
 DisplayButtonClick(Sender);
end;

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

Если тут:

repeat
  cc:=c*c;
  dd:=d*d;
  d:=(c+c)*d+n;
  c:=cc-dd+m;
  dec(t);
until (t=0) or (cc+dd>1000000.0);

Сделать так:

repeat
  dd:=d*d;
  d:=(c+c)*d+n;
  cc:=c*c;
  c:=cc-dd+m;
  dec(t);
until (t=0) or (cc+dd>1000000.0);

Это даст ускорение ~ 10-13% (FPC 3.3.1)
(правда это при компиляции под x64, при компиляции под x32 разницы нет)

Это что-то специфичное для Паскаля?

В каком смысле? Просто конкретно эта перестановка позволила оптимизатору FPC выкинуть одну ассемблерную инструкцию и это дало такое ускорение, там в цикле было около 12 инструкций, одну выкинули, примитивный расчет дает надежду в 8%
Но это примитивный расчет, всё зависит от микроархитектуры процессора, конкретно как инструкции перераспределились и т.п.
К примеру, можно сделать ещё одну перестановку, которая даст ещё меньше ассемблерных инструкций (-1), но конкретно на моём процессоре - это наоборот увеличивает время выполнения.
Кстати, анализатор llvm-mca - это предсказывает

В общем отвечая на вопрос - и да и нет.
Специфично - потому-что у FPC/Delphi оптимизатор не такой мощный, конечно как у LLVM/GCC/MSVC/Intel/etc.
Не специфично - потому-что в любом языке я думаю можно найти такой вариант, когда перестановка действий местами - даёт ускорение.

В общем вопрос встречный остается в силе - что вы имеете ввиду под "специфичное для паскаля?"

Это очень интересно. Для меня открытие, что перестановка математических выражений местами может повысить производительность на 10%. Обычно инструкции хотя бы связаны, это же перестановка независимых выражений. Мне все еще непонятно, почему здесь играет роль разрядность системы, 32 или 64 бита, я так полагаю, что просто так оно работает без общего правила. Я посмотрю, что такое llvm-mca, спасибо за наводку.

Ну так тут всё просто - это самый горячий цикл
в нём условно 10 команд, которые процессору нужно исполнить
убираем одну - остаётся 9, соотв.скорость увеличили на 10%
(но это всё условно т.к. современные процессоры не так просты)

и для языков, где бэкенд GCC/LLVM - там такие перестановки редко могут что-то дать, т.к. они сами переставляют как нужно чаще всего (но не всегда)

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

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

  1. cc:=c*c; dd:=d*d; d:=(c+c)*d+n; c:=cc-dd+m;

  2. dd:=d*d; d:=(c+c)*d+n; cc:=c*c; c:=cc-dd+m;

В первом варианте ты работаешь с "c" и "cc", а потом с "d" и "dd", а во втором, ты работаешь c "cc"/"c", потом с "c" и "d", потом c "d"/"dd". Т.е. у тебя лишний раз не перекладывается c/d при операциях и компилятор может оставить "c" в том же регистре для след. операции.

Вот оно что, спасибо большое за пояснение!

Занимался переписыванием кода с Delphi на ассемблер для ускорения вычислений. Получал где-то двухкратное ускорение. И наблюдал ускорение вычислений по мере появления новых версий компилятора. Потом был долгий перерыв с Delphi. И теперь поставил Community Edition - скорость работы кода стала примерно равна моим примитивным оптимизациям на ассемблере. Т.е. в новом коде я уже этого делать не буду)

Sign up to leave a comment.

Articles