Помощь новичкам

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

function f1(s:string;a,b:cardinal):=s[a:b+1];

а на днях проверил через Substring:

function f2(s:string;a,b:cardinal):=(b<a)or(a<1)or(b>s.Length)?'':s.Substring(a-1,b-a+1);

и не смотря на корявость, на 100 000 замерах она оказалась минимум в четыре раза быстрее.

Это так и есть или скорее нюанс Milliseconds|Delta ?

Summary
var s:='1234567890';
var xx:string;
var a,b:cardinals; //вынес из циклов для объективности

// loop 100 do begin

    milliseconds; loop 100000 do for a := 1 to s.Length do for b := 1 to s.Length do xx := f1(s, a, b);
    print(MillisecondsDelta, ':');
    
    milliseconds; loop 100000 do for a := 1 to s.Length do for b := 1 to s.Length do xx := f2(s, a, b);    
    println(MillisecondsDelta);
  //end;
uses System;
uses System.Diagnostics;

function f1(s: string; a, b: integer) := s[a:b + 1];

function f2(s: string; a, b: integer) := (b < a) or (a < 1) or (b > s.Length) ? '' : s.Substring(a - 1, b - a + 1);

begin
  var str := '123456789' * 1000;
  var t1 := new Stopwatch();
  var t2 := new Stopwatch();
  var count := 20;
  
  var value := 5000;
  
  loop count do
  begin
    t1.Start();
    loop value do f1(str, 1, str.Length - 1);
    t1.Stop;
    
    t2.Start();
    loop value do f2(str, 1, str.Length - 1);
    t2.Stop;
  end;
  Seq(t1, t2).Select(x -> (x.ElapsedTicks / count) / TimeSpan.TicksPerSecond).PrintLines;
  $'f2 быстрее f1 в {t1.ElapsedTicks / t2.ElapsedTicks} раз'.Println;
end.

У меня разница в 14 раз в среднем. А в режиме отладки вообще 25, потому что там срезы долго работают.

У нас разные системы от старых двухядерных печаталок до недавних i7 и Xeon, поэтому указал “минимум в четыре раза быстрее” (даже без отладки).

Верно ли я использую срезы с неизменяемыми строками или это частный случай и срезы скорее для наглядности ?

Тут количество ядер вообще не влияет, запустите этот код у себя и у вас тоже будет около 14.

Да, специализированные операции всегда быстрее.

Вот реализация Substring:

[SecurityCritical]
private unsafe string InternalSubString(int startIndex, int length)
{
	string text = FastAllocateString(length);
	fixed (char* dmem = &text.m_firstChar)
	{
		fixed (char* ptr = &m_firstChar)
		{
			wstrcpy(dmem, ptr + startIndex, length);
		}
	}
	return text;
}

Вот наша реализация срезов:

function CreateSliceFromStringInternal(Self: string; from, step, count: integer): string;
begin
  var res := new StringBuilder(count);
  
  loop count do
  begin
    res.Append(Self[from]);
    from += step;
  end;
  Result := res.ToString;
end;

Думаю, понятно, почему быстрее

1 лайк

Ускорили s[a:b], заменив его на вызов s.Substring

1 лайк

(я тоже хотел написать, но тут у меня сдох инет… ну, шоб уже зря не пропадало:)

  1. MillisecondsDelta никогда не было сильно точным. Для замеров времени правильно использовать StopWatch.

Это не правильно. f2("abc",-2,2) вернёт пустую строку вместо "ab". А вообще самый правильный вариант - кидать исключение, что уже делает .Substring.

Все особые случаи, как выход индекса за границы - надо просчитывать на стороне, вызывающей f2.

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

  1. Что, собственно, вы тестируете? a=0<1, а значит ваша f2 проверяет всего 2 условия и больше ничего не делает.
function f1(s: string; a, b: cardinal) := s[a:b + 1];

function f2(s: string; a, b: cardinal) := (b < a) or (a < 1) or (b > s.Length) ? '' : s.Substring(a - 1, b - a + 1);

begin
  var s := '1234567890';
  var a, b: cardinal;
  a := 2;
  b := 7;
  
  var lc1 := 1000;
  var lc2 := 10000;
  
  var sw1 := new System.Diagnostics.Stopwatch;
  var sw2 := new System.Diagnostics.Stopwatch;
  
  for var i := 1 to lc1 do
  begin
    
    sw1.Start;
    loop lc2 do f1(s, a,b);
    sw1.Stop;
    
    sw2.Start;
    loop lc2 do f2(s, a,b);
    sw2.Stop;
    
    System.Console.Title := (i/lc1).ToString('P');
  end;
  
  Writeln(sw1.Elapsed);
  Writeln(sw2.Elapsed);
  
  Readln;
end.

Ну и, запускать это надо в режиме без компиляции отладки и в Shift+F9.

Теперь результаты точные - f2 быстрее не в 4, а в 5 раз!

А вообще, тестить скорость мало. В первую очередь надо было посмотреть на реализацию срезов:

function SystemSliceStringImpl(Self: string; situation: integer; from, &to: integer; step: integer := 1): string;
begin
  var fromv := from - 1;
  var tov := &to - 1;
  var count := CheckAndCorrectFromToAndCalcCountForSystemSlice(situation, Self.Length, fromv, tov, step);
  
  Result := CreateSliceFromStringInternal(Self, fromv + 1, step, count)
end;
function CheckAndCorrectFromToAndCalcCountForSystemSlice(situation: integer; Len: integer; var from, &to: integer; step: integer): integer;
begin
  // situation = 0 - все параметры присутствуют
  // situation = 1 - from отсутствует
  // situation = 2 - to отсутствует
  // situation = 3 - from и to отсутствуют
  if step = 0 then
    raise new ArgumentException(GetTranslation(PARAMETER_STEP_MUST_BE_NOT_EQUAL_0));
  
  if (situation = 0) or (situation = 2) then
    if (from < 0) or (from > Len - 1) then
      raise new ArgumentException(GetTranslation(PARAMETER_FROM_OUT_OF_RANGE));
  
  if (situation = 0) or (situation = 1) then
    if (&to < -1) or (&to > Len) then
      raise new ArgumentException(GetTranslation(PARAMETER_TO_OUT_OF_RANGE));
  
  CorrectFromTo(situation, Len, from, &to, step);
  
  var count: integer;
  
  if step > 0 then
  begin
    var cnt := &to - from;
    if cnt <= 0 then 
      count := 0
    else count := (cnt - 1) div step + 1;
  end
  else
  begin
    var cnt := from - &to;
    if cnt <= 0 then 
      count := 0
    else count := (cnt - 1) div (-step) + 1;
  end;
  
  Result := count;
end;
procedure CorrectFromTo(situation: integer; Len: integer; var from, &to: integer; step: integer);
begin
  if step > 0 then
  begin
    case situation of
      1: from := 0;
      2: &to := Len;
      3: (from, &to) := (0, Len)
    end;  
  end
  else
  begin
    case situation of
      1: from := Len - 1;
      2: &to := -1;
      3: (from, &to) := (Len - 1, -1);
    end;
  end;
end;
function CreateSliceFromStringInternal(Self: string; from, step, count: integer): string;
begin
  var res := new StringBuilder(count);
  
  loop count do
  begin
    res.Append(Self[from]);
    from += step;
  end;
  Result := res.ToString;
end;

Надеюсь, теперь нет вопроса почему они медленнее?))

Посмотрите сейчас после обновления и потестите

1 лайк

С Наступающими и европейским Рождеством!

Я пока ещё лишь изучаю возможности и особенности Pascal ABC . Net и всё писалось под себя, поэтому на идеал и не претендую.

Был вариант с флагом или exception, но для меня пустая строка самодостаточный признак.

Сергей, а почему вы заменили беззнаковый cardinal на integer? И что, по вашему, значит под/строка длиной минус 5 символов, реверс?

Ещё интереснее, что даже при беззнаковых параметрах в объявлении можно задавать минусовые значения без вызова ошибок.

Благодарю всех за ответы.

На основе моего теста:

Summary

Было:

image

Стало:

image

Это просто замечательно :grinning:

Потому что аргументы нужных методов имеют тип int32. А в вашем случае будут ещё лишние преобразования. Хотя, конечно, на скорость это практически не влияет.

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

    internal const STD_INPUT_HANDLE: longword = -10;
    internal const STD_OUTPUT_HANDLE: longword = -11;
    internal const STD_ERROR_HANDLE: longword = -12;

Нежели шарпийское:

        internal const uint STD_INPUT_HANDLE = uint.MaxValue - 9;
        internal const uint STD_OUTPUT_HANDLE = uint.MaxValue - 10;
        internal const uint STD_ERROR_HANDLE = uint.MaxValue - 11;

@Admin, как программно получить номер строки, в которой произошла ошибка компиляции?

Не знаю, думаю, никак

Хорошо. Если вдруг найдёте способ это сделать - можете сообщить. Это - не срочно.

Если вы о программной компиляции, то

Аннотация

Платформа .NET Framework предоставляет классы, которые позволяют осуществлять программный доступ к компилятор языка C#. Это может быть полезно, если требуется написать собственные средства компиляции кода. Эта статья содержит пример кода, который позволяет компилировать код с исходным текстом. Приложение позволяет либо просто сборки исполняемого файла или сборки исполняемого файла и запустить его. На форме отображаются все ошибки, возникающие во время процесса предварительной компиляции.

@Admin, мне нужен API компилятора PascalABC.NET. Требуется работать с ним напрямую.

Мне нужно логгировать список ошибок компиляции .pas файлов и выдавать в виде предупреждения в IDE, такой как Visual Studio/Rider. При каждой сборке проекта NETSquirrel все связанные .pas файлы должны перекомпилироваться.

Мы никогда специально его не делали.

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

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

Понял.

Как проще всего исправить форматирование TXT, удалив лишний NewLine, соединяя все строки с прописной буквой с предыдущей? Размер 22 мегабайта.

Также есть ли способ или директива управления компиляцией, например, временно отключить (для определённой программы) Debug ?

В смысле, Вы хотите, убрать пустые строки, отделяющие абзацы?