Проекты на PascalABC.NET

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

  1. Есть ли в PascalABC.Net что-то типа move? А то для больших массивов поэлементное копирование такое себе удовольствие. Ну мне-то сойдёт для моих целей.
  2. А если нужно результат не в динамический , а в статический массив поместить? Что-то в отличие от Delphi с указателями на массивы проблемы определённые.

KBuf.rar (1,2 КБ)

System.Buffer. Но это работает только для стандартных типов.

Ещё вариант - создать динамическую функцию через System.Reflection.Emit, в IL вроде есть что то для такого копирования.

Не используйте статические массивы. Они хранят внутри динамический массив, но делают кучу лишних телодвижений. Да и JIT-у проще родные массивы оптимизировать.

Указатели на элемент массива нельзя получать через @a[...]. Эта конструкция разрешена для совместимости. Но она вызывает утечки памяти, а если элементы массива это записи с полем типа класса (как string) - ещё и небезопасно, сборщик мусора может в любой момент переместить массив и сломать указатель.

Используйте GCHandle чтоб блокировать массив напрямую и потом не забывайте разблокировать. Но если использовать это для копирования памяти между 2 массивами - будет всё равно медленно.

Это неверное утверждение. Статические массивы столь же эффективны по времени, что и динамические. Вы давно об этом говорите. Причём везде. Это неверно. Если надо использовать статические массивы, их можно использовать.

Конечно, лишаешься ряда преимуществ с огромным количеством методов. В этом - основная проблема статических массивов.

Да не сильно, они же тоже IEnumerable, значит вполне можно использовать методы последовательностей

var
  a: array[1..10] of integer := (9, 5, 3, 2, 7, 5, 4, 8, 9, 0);
  
begin
  a.PrintLines
end.

Только Intelisense не работает

System.Array.Copy

Да. В NET нельзя вот так просто напрямую обращаться к памяти, как в Delphi. Не используйте указатели. В статический массив у нас кроме как поэлементно ничего не скопируешь. Статические массивы сделаны для совместимости.

Кстати, почему нигде не сказано, что у статических массивов есть константы?

var
  a: array[1..10] of integer;
  
begin
  for var i := a.LowerIndex to a.UpperIndex do Write(i);
end.

Причём, это и с многомерными работает:

var
  a: array[1..10, 1..10] of integer;
  
begin
  for var i := a.LowerIndex to a.UpperIndex do
  begin
    for var j := a[i].LowerIndex to a[i].UpperIndex do Write(a[i, j]);
    Writeln;
  end;
end.

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

Но это так, мысли вслух, просто предположение

Мы не хотим развивать статические массивы - они оставлены только для совместимости.

2 лайка

Ещё из замечаний по коду:

  1. Не передавайте массивы var-параметрами, если не перевыделяете память под массив. Переменные с типом-классом (в том числе массивом) и так хранят только ссылку на содержимое.

SetLength(Data,aSize);

Лучше, по возможности:

Data := new byte[aSize];

SetLength ещё пытается копировать все элементы из предыдущего массива в новый. А это не всегда надо.

procedure TRingBuf.Free;
begin
   SetLength(Data,0);
end;

Освобождение памяти это Data := nil. А вы вместо этого создаёте новый массив. Длина у него 0, но заголовок никто не отменял.


Это всё было как ни крути - плохо. Теперь по стилю кода:

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

Методы Init и Free - это для языков без сборщика мусора. В .Net это должны быть конструктор и Finalize:

    public constructor(aSize:Integer);
    begin
      Data := new byte[aSize];
      Size := aSize;
      Head := 0;
      Filled := 0;
    end;
    
    protected procedure Finalize; override;
    begin
      // это вообще бесполезно, когда сборщик мусора будет удалять
      // данный экземляр - он и так не забудет удалить Data
//      Data := nil
    end;

При этом Finalize не надо вызывать. Этот метод вызывается сам, когда сборщик мусора удаляет ваш объект.

Остальное по мелочи:

  1. Ради юзабельности, основные Push и Pop должны быть для 1 элемента. А для массивов - что то типа PushArr.
  2. Нет вывода всего содержимого.
  3. И если вы не хотите писать по типу буфера на каждый тип данных - используйте generic-типы.

KBuf.rar (1,9 КБ)

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

1 лайк

А Finalize-то зачем???

Он вообще не для того

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

Туда не суют освобождение памяти. Никогда. Путаете. Только неуправляемых ресурсов.

Я имел в виду неуправляемую память. Она тоже очень память!

В целом спасибо за замечания. Единственно как класс без надобности я не люблю объявлять. У нас же для обычного массива не нужно конструктор и деструктор объявлять. Как правило , классы больше багов порождают из-за того, что вовремя не вызван конструктор или деструктор.

"Методы Init и Free - это для языков без сборщика мусора. " - ну я код по возможности делаю совместимым с другими диалектами Паскаль . Не уверен, что Data := nil в всех диалектах освобождает память.

Какой смысл писать на языке, который от остальных диалектов отличатся в разы и цепляться за какую-то “совместимость”? Ну и пишите на FPC тогда.

3 лайка

Отсюда поподробнее. Я не первый год программирую и ничего страшнее NullReferenceException представить не могу. А про деструкторы забудьте, в PABC.Net это обычные процедуры, они ничего особого не делают.

Тем временем аналоги деструктора и конструктора в вашем коде есть - это Init и Free. Вот только в отличии от забытого конструктора - забытый Init даст вам не NullReferenceException при первом использовании переменной, а какое то странное значение в 1 из последующих операций.

Далее - 1 раз забытый Free невозможно отследить. А сборщик мусора никогда не забудет уничтожить не используемый объект.

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

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

Но да, если уже хотите пользоваться каменными инструментами, ради чего то типа “единства с природой” (в том числе жуками) - лучше и программировать в старых языках.
А то постоянно будете спотыкаться о что то типа невозможности использовать указатели “по-старому” (то есть везде где не надо).

Но я порекомендую скорее C++ чем FPC. Он хотя бы живой. Немного. Вроде.

[quote=“Sun_Serega, post:167, topic:2272”] Я не первый год программирую и ничего страшнее NullReferenceException представить не могу. [/quote] -вылетание программы иногда хуже, чем “глючный” результат. Т.к. глючный результат может и использоваться нигде не будет , а если вся программа вылетит из-за его вычисления, то она уже вылетела.

Сделал оптимизация по преждоженным методам + тест производительности. Использованы System.Buffer.BlockCopy и System.Array.Copy ( в одном месте , когда целый массив копируем).

Прирост производительности довольно ощутимый. С 494/319 мс. (запись чтение) до 2/2(3/3) мс.

Стандартный Queue правда быстрее. Он даёт 1(2) мс. на чтение. На запись я не нашёл, как загружать массив целиком, поэтому получил 464 мс. на загрузку.

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

Queue.rar (14,3 КБ)

KBuffast.rar (17,4 КБ)

KBuf.rar (16,1 КБ)