October 7, 2022

Функции, возвращающие кортежи, против процедур с выходными параметрами

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

Сравним оба способа по читаемости и производительности.

Задача. Пусть нам следует вычислить периметр и площадь треугольника со сторонами x и y.

Решение 1. Составим процедуру с двумя входными и двумя выходными параметрами. Измерим производительность:

procedure CalcPerimeterSquare(x,y: integer; var P,S: real);
begin
  P := 2*(x+y);
  S := x*y;
end;

begin
  var n := 100000000;
  var sw := new Stopwatch;
  sw.ReStart;
  loop n do
  begin
    var P,S: real;
    CalcPerimeterSquare(2,3,P,S);
  end;  
  sw.Stop;
  Print(sw.ElapsedMilliseconds);
end.

Данный код выполняется очень быстро:

46 мс

Решение 2. Реализуем вычисления в виде функции, возвращающей кортеж из двух значений:

function PerimeterSquare(x,y: real) := (2*(x+y), x*y);

begin
  var n := 100000000;
  var sw := new Stopwatch;
  sw.Start;
  loop n do
  begin
    var (P,S) := PerimeterSquare(2,3);
  end;  
  sw.Stop;
  Print(sw.ElapsedMilliseconds + ' мс');
end.

Код гораздо короче, но будет ли он быстрее?

292 мс

В 6 раз медленнее!

Однако вычисления здесь - простые: для вычисления площади и периметра используется два умножения и одно сложение.

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

Решение 1a.

procedure CalcPerimeterSquare(x,y: integer; var P,S: real);
begin
  P := Sin(Exp(x) + Ln(y));
  S := Tan(Exp(y)-Cos(x));
end;

begin
  var n := 100000000;
  var sw := new Stopwatch;
  sw.ReStart;
  loop n do
  begin
    var P,S: real;
    CalcPerimeterSquare(2,3,P,S);
  end;  
  sw.Stop;
  Print(sw.ElapsedMilliseconds + ' мс');
end.

Вывод:

12861 мс

Решение 2a.

function PerimeterSquare(x,y: real) 
  := (Sin(Exp(x) + Ln(y)), Tan(Exp(y)-Cos(x)));

begin
  var n := 100000000;
  var sw := new Stopwatch;
  sw.Start;
  loop n do
  begin
    var (P,S) := PerimeterSquare(2,3);
  end;  
  sw.Stop;
  Print(sw.ElapsedMilliseconds + ' мс');
end.

Вывод:

12646 мс

Время работы примерно одинаково.

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