Кортежи ValueTuple

Гарантированно (то бишь, из коробки) нет проблем совместимости с бинарниками, созданными в Паскале под нынешнюю целевую версию .NET 4.0, только у тех, кто работает под Win8 и старше – у всех остальных из коробки – .NET 3.5 или еще старее, что уже требует сознательного апгрейда со стороны пользователя не только для компиляции, но и просто для запуска скомпилированных программ из этой среды.

Значит, большинство наших пользователей как-то справлялись с этой задачей, почему же сейчас выдвигается тезис о каких-то трудноразрешимых и непонятных простому смертному “ужасных ошибках”, если мы предложим им сначала проагрейдиться автоматом в процессе установки Паскаля (хотя бы первоначальной), а потом, со временем, переключимся на .NET 4.7 (когда по собранной нами же объективной статистике кол-во XP-шек упадет ниже какого-то предела, напр. < 5…10%).

Обратите внимание на эти даты:

  • 4.7.1 17 октября 2017 года
  • 4.0 12 апреля 2010 года
  • 3.5 9 ноября 2007 года

Большинство пользователей справлялись с задачей, поскольку с 2010 года прошло 8 лет. А с момента появления ValueTuple прошло меньше года - до такой степени, что текущая версия VS не видит кортежи.

И в первые Windows 10 как следует из таблицы автоматом 4.7 не ставилась.

Проблема Паскаля - именно в том, что он стандартную библиотеку подключает автоматом.

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

Вот кстати программа где ValueTuple работает в полтора раза медленнее:

uses 
  System.Diagnostics;
const
  Cycles = 100000000;
type 
  MyTuple1 = System.Tuple<integer, real, char>;
  MyTuple2 = System.ValueTuple<integer, real, char>;

begin
  var sw1 := Stopwatch.StartNew();
  var a1 := new MyTuple1(123, 1.23, 'a');
  
  loop Cycles do
  begin
    var b1 := new MyTuple1(123, 1.23, 'a');
    a1 := b1;
  end;
    
  sw1.Stop();
  Println('Tuples =', sw1.ElapsedMilliseconds / 1000);
  
  var sw2 := Stopwatch.StartNew();
  var a2 := new MyTuple2(321, 3.21, 'z');
  
  loop Cycles do
  begin
    var b2 := new MyTuple2(321, 3.21, 'z');
    a2 := b2;
  end;
    
  sw2.Stop();
  Println('ValueTuples =', sw2.ElapsedMilliseconds / 1000);
  
  Println($'Ratio = {sw1.ElapsedTicks/sw2.ElapsedTicks :f2}');
end.

Обычные присваивания… Там были сравнения, видимо, оптимизатор кода их соптимизировал как неиспользуемые А тут нет

value-типы надо передавать по ссылке, когда они большие. То есть как var-параметры и т.п. Знаете - неправильно использовать можно всё)))

Не понял. А где тут передача параметров? Приведите свой правильный код с присваиванием

Присваивать 1 переменную другой - по умолчанию не правильно. Нет реальных случаев где это надо. А вот если надо сделать такое возвращаемое значение - надо через var-параметр. И передавать в функции тоже, как var-параметры.

Странно, у меня наоборот этот код в релиз-варианте (и без IDE) показывает выигрыш по скорости только для ValueTuple – где-то в районе 20%, хотя из-под IDE получается почти поровну – всего 1-4%.

Clipboard02

Clipboard03

1 лайк

Ну, видите, разные процессоры - по-разному.

У Вас какой процессор?

У меня Core I5 2500

Со сравнениями кстати надо написать программу, которая накапливает результаты вычислений. Тогда не соптимизирует.

Старенький мобильный 2-х ядерный Core 2 Duo 1.2 … 2.2GHz (причем принудительно ограниченный до 75% производительности, чтобы не перегревался на жаре), L1/L2 cache (32+32)КB / 2MB.

Да, похоже там раньше срабатывали какие-то лютые оптимизации. Вот обновленный динамический вариант теста:

uses 
  System.Diagnostics;

const
  Cycles = 1000000;

type
  MyTuple1 = System.Tuple<integer, real, char>;
  MyTuple2 = System.ValueTuple<integer, real, char>;

procedure MyRandomTuple1(var x: MyTuple1);
begin
  x := (Random(123), Random(123.45), ChrAnsi(Random(256)));
end;

procedure MyRandomTuple2(var x: MyTuple2);
begin
  x.Item1 := Random(123);
  x.Item2 := Random(123.45);
  x.Item3 := ChrAnsi(Random(256));
end;

begin
  Randomize;
  var a1, b1: MyTuple1;
  var sw1 := new Stopwatch;
  loop Cycles do
  begin
    MyRandomTuple1(a1);
    b1 := a1;
    sw1.Start;
    if a1 = b1 then;
    sw1.Stop;
  end;
  Println('Tuples =', sw1.ElapsedMilliseconds / 1000);
  
  var a2, b2: MyTuple2;
  var sw2 := new Stopwatch;
  loop Cycles do
  begin
    MyRandomTuple2(a2);
    b2 := a2;
    sw2.Start;
    if a2 = b2 then;
    sw2.Stop;
  end;
  Println('ValueTuples =', sw2.ElapsedMilliseconds / 1000);
  
  Println($'Ratio = {sw1.ElapsedTicks/sw2.ElapsedTicks :f2}');
end.

У меня получается выигрыш для ValueTuple в релизе стабильно около 50% +/- 3%.

Clipboard01

А у меня в 9 раз:

image

Внесу и я свои пять копеек в тестирование

Tuples = 0.334 
ValueTuples = 0.034 
Ratio = 9.61 

Имя ОС - Microsoft Windows 7 Максимальная
Версия - 6.1.7601 Service Pack 1 Сборка 7601
Тип - x64-based PC
Процессор - Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz, ядер: 2
Установленная оперативная память (RAM) - 8.00 ГБ

Ну сравнение туплов медленнее (оно реализовано с помощью Equals). ValueTuple сравниваются IL-командой ceq. А если написать без сравнений. то разницы нет никакой

uses 
  System.Diagnostics;

const
  Cycles = 10000000;

type
  MyTuple1 = System.Tuple<integer, real, char>;
  MyTuple2 = System.ValueTuple<integer, real, char>;

function MyRandomTuple1: MyTuple1;
begin
  Result := (Random(123), Random(123.45), ChrAnsi(Random(256)));
end;

function MyRandomTuple2: MyTuple2;
begin
  Result := new MyTuple2(Random(123), Random(123.45), ChrAnsi(Random(256)));
end;

begin
  Randomize;
  var a1, b1: MyTuple1;
  var sw1 := new Stopwatch;
  sw1.Start;
  loop Cycles do
  begin
    a1 := MyRandomTuple1;
    b1 := a1;
    
    //if a1 = b1 then;
   
  end;
   sw1.Stop;
  Println('Tuples =', sw1.ElapsedMilliseconds / 1000);
  
  var a2, b2: MyTuple2;
  var sw2 := new Stopwatch;
  sw2.Start;
  loop Cycles do
  begin
    a2 := MyRandomTuple2;
    b2 := a2;
    
    //if a2 = b2 then;
    
  end;
  sw2.Stop;
  Println('ValueTuples =', sw2.ElapsedMilliseconds / 1000);
  
  Println($'Ratio = {sw1.ElapsedTicks/sw2.ElapsedTicks :f2}');
end

А если так написать, то разница минимальная (1.05-1.11)

if Object.ReferenceEquals(a1,b1) or ((a1.Item1 = b1.Item1) and (a1.Item2 = b1.Item2) and (a1.Item3 = b1.Item3)) then;

Так что особого выигрыша вся эта размерщина не дает.

Я думаю, что как в любом деле, желающий аргументировать всегда найдет вымороченный пример или контрпример. Интерес представляет некое синтетическое понимание вопроса, а не частности, демонстрирующие как явное преимущество, так и полное отсутствие оного. Конечно, мое мнение ничего не решает, но пока я не пришел к выводу, полезное это дело или нет. Точнее не так: опционально иметь козыря в рукаве всегда полезно, но если тебе за это могут “накидать по щам”, нужно подумать, что перевесит.

2 лайка

Это очень удобно и лаконично :slight_smile:

Эта “размерщина”, как минимум, позволяет писать простые вещи просто и одновременно эффективно, не изобретая громоздких велосипедов на ровном месте. Разве это не преимущество?

Кроме того, в вырожденных случаях она дает возможность JIT-компилятору и/или процессору радикально оптимизировать процесс исполнения, сокращая его уже в десятки, а то и сотни раз.

Единственный серьезный аргумент против ValueTuple сейчас – отсутствие его поддержки в старых, но еще широко используемых версиях .NET и на XP.

P.S. Еще один недостаток текущей реализации ссылочных Tuples – их неизменность после создания (immutability), хотя, конечно, это зависит от контекста применения. Но если, напр., хранить в кортежах какие-то координаты, то может быть очень удобно изменять отдельные их элементы без пересоздания всего кортежа.

Думаю, моя реализация сравнения кортежей на равенство неэффективна.

function InternalEqual<T1, T2> (x: (T1,T2); y: (T1,T2)): boolean; 
begin
  var xn := Object.ReferenceEquals(x,nil);
  var yn := Object.ReferenceEquals(y,nil);
  if xn then
    Result := yn
  else if yn then 
    Result := xn
  else Result := x.Equals(y);
end;

Можно пооптимизировать.

Неизменность хороша в многопоточности. Строки в NET тоже неизменны

15 сообщений перенесены в тему Болталка PascalABC.NET