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

А теперь ближе к вашему случаю, если самое важное это работоспособность при объёме оперативки меньше объёма файла - надо загружать его кусками, что то типо такого:

function ReadStreamInBatches(f:System.IO.Stream; BatchSize:integer):sequence of array of byte;
begin
  var res := new byte[BatchSize];
  var left := f.Length;
  while true do
  begin
    if left >= BatchSize then
    begin
      f.Read(res,0,BatchSize);
      yield res;
    end else
    begin
      if left <> 0 then
      begin
        res := new byte[left];
        f.Read(res,0,left);
        yield res;
      end;
      break;
    end;
    left -= BatchSize;
  end;
end;

begin
  var f := System.IO.File.OpenRead('my file.txt');
  var batches := ReadStreamInBatches(f,100*1024);//считываем по 100 килобайт
  foreach var batch in batches do
    writeln(batch);
end.

Если же вам надо текстом и кодировками:

function ReadStreamInBatches(f:System.IO.StreamReader; BatchSize:integer):sequence of array of char;
begin
  while not f.EndOfStream do
  begin
    var res := new char[BatchSize];
    f.ReadBlock(res,0,BatchSize);
    
    for var i := 0 to BatchSize-1 do
      if res[i] = char(0) then
      begin
        if i = 0 then exit;
        var nres := new char[i];
        System.Array.Copy(res,nres,i);
        res := nres;
        break;
      end;
    
    yield res;
  end;
end;

begin
  var str := new System.IO.MemoryStream;//это поток-заглушка


  var sw := new System.IO.BinaryWriter(str,System.Text.Encoding.UTF8);//записываем в него 'abcd' в кодировке UTF8
  //sw.Write(new char[4]('a','b','c','d'));
  sw.Write('abcd'.ToCharArray);//так пожалуй покрасивше
  str.Position := 0;//обнуляем позицию в потоке, после того как в него что то записывали она в конце
  
  
  var sr := new System.IO.StreamReader(str,System.Text.Encoding.UTF8);
  var batches := ReadStreamInBatches(sr,3);//считываем по 3 буквы
  foreach var batch in batches do
    writeln(batch);
end.

Тут я заполнил переменную str потоком - заглушкой, заполнив его несколькими буквами. Но вы можете присвоить ему результат функции System.IO.File.OpenRead. Это чтоб показать что работает, не создавая для этого дополнительных файлов.

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

2 лайка

Ваш тест не очень правильный, во первых у вас время создания последовательности и работы Range, Select и SeqGen. 2 из которых ещё и вызывают функцию по указателю, ужас! А ещё все они (кроме Select, он казёный из C#) создают экземпляр нового класса - циклера.

Вот точнее:

procedure DoNothing(i64: int64) := exit;

begin
  var ST: System.DateTime;
  var sum: int64;
  
  var k := 50;
  var el_c := 1000000;
  var s := SeqGen(el_c, i -> int64(i + 1));
  
  ST := System.DateTime.Now;
  loop k do
    s.Sum;
  var t1 := System.DateTime.Now - ST;
  
  ST := System.DateTime.Now;
  loop k do
  begin
    foreach var t in s do
      sum += t;
  end;
  var t2 := System.DateTime.Now - ST;
  
  ST := System.DateTime.Now;
  loop k do 
    for var i := 1 to el_c do
      sum += i;
  var t3 := System.DateTime.Now - ST;
  
  DoNothing(sum);//иначе оптимизатор всё портит
  
  writeln((t1, t2, t3));
  readln;
end.

Первый и второй пример должны работать одинаково (я посмотрел реализацию sum декомпилятором), но из за #807 туда ещё добавляет лишнее System.Convert.ToInt64, что удваивает время.

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

У меня тест не работы какого-то фрагмента - интересовало общее время, за которое можно найти сумму последовательности чисел, включая ее создание. Первая проблема в том, что нет иного способа дать понять Sum, что нужно работать с int64, кроме как подать на вход данные такого типа. Вторая - в том, что нет способа генерировать последовательность элементов типа int64 (они все для integer сделаны), кроме как вставлять принудительное преобразование.

Массива у меня нет ни в одном из случаев - только последовательности. Я еще не сошел с ума, чтобы заниматься тестами создания и заполнения массива на миллион элементов.

А зачем вам вообще решать эту задачу последовательностью? По моему они тут не к месту, если речь идёт про скорость…

Вы увидели, что там арифметическая прогрессия? Хорошо, но это ведь пример, для простоты. Представьте, что там не натуральный ряд чисел, а некая произвольная последовательность.

Если у вас на вход идёт именно последовательность то лучше Sum решения вы не найдёте.

Если нет - любую yield - функцию можно заменить на алгоритм без yield - функции. Собственно это и делает компилятор кода видит yield - функцию. Только он оборачивает всё в 2 класса, а если сделать без них - работать будет в разы быстрее.

А последовательности надо применять где важна красота кода и то насколько он короткий.

Спасибо, Сергей, то есть, в описании это хаскелообразные моменты

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

Чуть сложнее, чем в презентации, но логично при максимальной оптимизации на память, хотя batch нужен покрупнее и пока непривычно “лениво запрашивать” по отношению к внешней памяти (файлу).

Это ваше оптимальное решение или же сама новая парадигма .NET диктует такие методы? Если преимущество перед старыми вариантами вроде брутфорса и сортировки кусками более-менее очевидна, то как насчёт “дозировнной” dictionary/collection, GetHashCode и, возможно, других?

Другими словами, вы бы именно так решили подобную задачу на PascalABC .NET? Благодарю

Я не до конца понял при чём тут хеш коды и словари, но именно такую задачу, и именно в такой постановке (упором на маленький объём оперативки) - да, я решил бы её именно так, потому что это именно то что я делал.

Но прошу, кстати, обратить внимание, что последовательности не обязательно использовать тут, можно написать на прямую:

begin
  var str := new System.IO.MemoryStream;//это поток-заглушка


  var sw := new System.IO.BinaryWriter(str,System.Text.Encoding.UTF8);//записываем в него 'abcd' в кодировке UTF8
  //sw.Write(new char[4]('a','b','c','d'));
  sw.Write('abcd'.ToCharArray);//так пожалуй покрасивше
  str.Position := 0;//обнуляем позицию в потоке, после того как в него что то записывали она в конце
  
  
  var sr := new System.IO.StreamReader(str,System.Text.Encoding.UTF8);
  
  //var batches := ReadStreamInBatches(sr,3);
  var f := sr;//вместо запуска ReadStreamInBatches инициализируем переменные тут
  var BatchSize := 3;
  
  while not f.EndOfStream do//и копируем весь код из ReadStreamInBatches
  begin
    var res := new char[BatchSize];
    f.ReadBlock(res,0,BatchSize);
    
    for var i := 0 to BatchSize-1 do
      if res[i] = char(0) then
      begin
        if i = 0 then exit;
        var nres := new char[i];
        System.Array.Copy(res,nres,i);
        res := nres;
        break;
      end;
    
    writeln(res);//тут было "yield res", но вместо этого мы его сразу используем
  end;
  
  
end.

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

  while not f.EndOfStream do
  begin
    var res := new char[BatchSize];
    f.ReadBlock(res,0,BatchSize);
    
    for var i := 0 to BatchSize-1 do
      if res[i] = char(0) then
      begin
        if i = 0 then exit;
        var nres := new char[i];
        System.Array.Copy(res,nres,i);
        res := nres;
        break;
      end;
    
    writeln(res);
  end;
  

Но что если вам с res надо делать что то другое, не writeln? Тогда придётся создавать ещё 1 процедуру или добавлять аргументы в эту же.

1 лайк

Сергей, вполне доходчиво) Перечислители мне подходили как раз методом Distinct, чтобы глобально отсеять повторы строк, не вдаваясь в повторное изобретение велосипеда, но не всё так просто, что даже интересней.

Раньше подобное решалось через приоритетную очередь сигнатур-хешей строк (часто в индекс-файл) с последующим пропуском номеров дубликатов, но когда объёмы на гигабайты, хотелось бы оптимизировать процесс.

Кстати, может знаете как в .NET динамически контролировать нагрузку на процессор, например, чтобы процедура не превышала 75% одного процессора? А в некоторый случаях может потребоваться обратное - держать минимум 99%, чтобы работал Turbo-Boost. Спасибо

Ребят, я извиняюсь. А можно как-то поменять настройки фона и цвета букв в паскале на такой: http://mojainformatika.ru/images/stories/pascal/String/procedura%20Str.png

Черное на белом утомляет глаза, когда по вечерам долго пишу…

Возможно, можно с гитхаба скачать исходники и через Visual Studio пошаманить, но на сколько это сложно я не знаю. А так настроек таких нет и как я понял разработчики добавлять не собираются.

1 лайк

Ну, Distinct тоже не на волшебстве работает, вот его реализация (я декомпилировал и полученное перевёл на паскаль):

function MyDistinct<T>(self:sequence of T):sequence of T; extensionmethod;
begin
  var SetOfElem := new HashSet<T>;
  foreach var el in self do
    if SetOfElem.Add(el) then
      yield el;
end;

То есть действительно, вы были правы, тут используется последовательность с хешкодами для быстрого поиска элементов (чтоб проверять если ли элемент в последовательности). Вот только вместо HashSet, в оригинальном Distinct используется System.Linq.Set (упрощённый HashSet). Он быстрее, но не годится для использования вне стандартных библиотек .Net. Впрочем, вам ничего не мешает скопировать и перевести на паскаль его исходники.

У меня тоже когда то были к этому вопросы, я сейчас поискал на msdn и похоже это невозможно сделать “честно”, но есть несколько бесплатных внешних программ, вот что я нашёл.

Но тем не менее, какой то контроль надо затратами есть. К примеру у меня 3-х ядерный процессор, а значит, к примеру запустив какой то действие в 2 потока я ограничиваю его затраты до 67% процессорного времени. Так же в System.Diagnostics.Process, да и просто в System.Diagnostics есть полно всего, позволяющего видеть когда и сколько оперативки и процессорного времени тратит процесс, и если что уменьшать его приоритет и т.п. А ещё, в System.Threading.Thread есть такие же настройки для потоков.

Пара уточнений по PascalABC .NET:

1) для запуска финального exe всегда требуется NET Framework или же только при использовании методов .NET?

2) можно ли без MS Office работать с doc/xls (docx/xlsx), например, как ускоренный VBA/RegEx?

3) есть ли эффективные/рекомендуемые средства для структуры вида:

  • Цикл_читать_из_файла
  •   Если длина_строки>5 +
    
  •   Если строка_содержит_табуляцию +
    
  •   Если подстроки_разделённые_по_таб_неравны +
    
  •   Если строка_не_повторяется ....записать_строку в новый_файл
    
  • Конец_цикла  
    

? Пока что решается через флаг OK с последующими if OK then … OK:=, А смысл в валидности последующей проверки только при истине текущего условного перехода, вроде укороченной логики - если что-то не так, перейти в начало цикла - следующая итерация.

Спасибо

Приведите пример кода для 3). То есть то как вы его сейчас решили.

Не понял как обрамлять код:

while not f.SeekEof do

begin

  Readln(f, S);
  S := Trim(S);
  ok := Length(S) > 5;
  if ok then ok:=abc(S);
  if ok then ok:=S.Contains(#9);
  if ok then begin L := S.split(#9);ok := L[0] <> l[1] end;
  if ok then begin Hash := ad32(S);ok:=not d.ContainsKey(Hash) end;
  if ok then begin
     d.Add(Hash,1);
     Writeln(_file,l[0]+#9+l[1]);
  end;//if

end;//while

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

Function IFF(it:array of conditions):byte; //возвращающая номер сработавшего аргумента-триггера

Суть в том, что если условие не сработало, то далее нет смысла проверять. Также хотелось бы узнать, насколько в PABC .NET разумно/экономно использовать декларацию CONST в аргументах функций.

  1. Всегда, т.к. финальный exe, генерируемый любым .net-компилятором, не содержит в себе машинного кода целевой платформы (т.е. команд конкретного процессора), а только промежуточный байт-код (платформо-независимые команды виртуальной машины .NET).

  2. Можно, с помощью сторонних библиотек, но все приличные – платные.

  3. cм. операторы break/continue

Выделять код так:

```

begin

end.

```

При чём каждый ``` должен быть на отдельной строке, иначе его глючит. Знак ` находится за ё, в англ. раскладке.

А зачем присваивать переменной? Сразу результат ставите в if и будет вам счастье.

  while not f.SeekEof do
  begin
    Readln(f, S);
    S := Trim(S);
    if Length(S) > 5 then
      if abc(S) then
        if S.Contains(#9) then
        begin
          L := S.split(#9);
          if L[0] <> l[1] then
          begin
            Hash := ad32(S);
            if not d.ContainsKey(Hash) then
            begin
              d.Add(Hash, 1);
              Writeln(_file, l[0] + #9 + l[1]);
            end;
          end;
        end;
  end;

Вообще, с тем как сделали вы произойдёт несколько лишних проверок.

В справке написано:

Выражения с and и or вычисляются по короткой схеме: 

в выражении x and y если x ложно, то все выражение ложно, и y не вычисляется;
в выражении x or y если x истинно, то все выражение истинно, и y не вычисляется.

Но прямо сейчас что то тут не так

Понял, спасибо)

Да, можно и так, но без сокращения получается много вложений if A then if B then if C then if D then if E then... и без предыдущего TRUE последующая проверка может быть ошибочной (тупиковой), так как, например, без символа TAB в строке не будет второго сплита подстроки и так далее. Можно критические проверки вынести на потом, но это лишнее:

ok:=(Length(S) > 5)+(abc(S))+...+ (S.Contains(#9));

if ok=5 then ...

Получается вариант с continue волне ничего, хотя в описании почему-то сбило break и примеры цикла с параметром, а не с пред/постусловием. Вроде как-то маловато в справке разнообразных примеров, поэтому и решают по старинке (==неэффективно==неправильно).

Да, с issue действительно неприятная оказия.

Без A=true в конструкции if A then if B then и не будет вычислять B, и так же должно (но сейчас нет из за той issue) работать и if A and B then.

Вы имеете в виду

ok:=(Length(S) > 5?1:0)+(abc(S)?1:0)+...+ (S.Contains(#9)?1:0);

? Но это вообще не вариант. Это и медленнее и как вы и сказали, будет пытаться разделить строку табами, которых нету (кстати ошибку это не вызовет, вместо этого вернёт копию строки, но всё равно потратит процессорное время на это).

С continue я не уверен, но возможно будет медленнее из за того что оно реализовано через лэйблы.

Правильно, потому что примеры надо искать не в справке а в папке примеров:

image

1 лайк

Есть ли способ в GraphWPF сохранить изображение в переменную? DrawImage загружает его из файла каждый раз чтоб нарисовать, это как то медленно.