Здравствуйте, WolfHound, Вы писали:
_>>Ну вообще то ссылочные типы гораздо менее эффективны по определению, из-за введения дополнительной косвенности. Т.е. грубо говоря, если ты сравнишь проход по vector<int*> и vector<optional<int>>, то первый будет в разы медленнее. ) Так что как раз засилье ссылочных типов — это и есть инженерный идиотизм. Но да, если уж это бредовое засилье есть (причём как в Java и C#, безальтернативно — без возможности скажем разместить объект на стеке), то тогда смысла в отдельном классе типа optional действительно уже мало. WH>1)В .НЕТ есть возможность создавать value типы которые живут в стеке, в массиве или в полях объектов. И там, где они подходят используются именно они. Но часто нужны именно ссылочные типы.
Я вроде как вполне ясно написал: "без возможности разместить объект на стеке". Объект — это общепризнанное название для экземпляра класса. И их в Java и C# никак не разместить на стеке. Кстати, в случае C# сюда стоило бы ещё добавить массивы, строки и делегаты.
И да, я совершенно не против использования ссылочных данных в тех случаях, когда они реально удобнее. Я против отсутствия выбора у программиста что использовать.
WH>2)Если мы пытаемся получить производительность любой ценой, то массив структур за который ты тут агитируешь тоже инженерный идиотизм. WH>Вот такой код будет работать медленней и занимать больше памяти WH>
WH>Посчитай промахи кэша в первом и во втором случае если нам нужно пройтись в цикле по полю field1. WH>Также посчитает сколько памяти съест выравнивание.
Не очень понял какое отношения эти примеры имеют к данной теме. Ведь в C/C++ (а у тебя стоит для этого кода подсветка именного этого языка) эти два примера не имеют абсолютно никакой разницы в уровне косвенности — тут же нет вообще ссылочный данных.
Кстати, я что-то ничего не слышал про тип данных int2 в C++. Ну точнее я и про int32 не слышал, но подозреваю что тут у тебя просто опечатка и имелось в виду int32_t. Но вот int2_t просто не существует.
Ну и да, т.к. разницы в косвенности между этими примерами нет, то выбирать из этих двух вариантов надо в зависимости от планируемых в будущем итераций.
WH>В случае с Option<int32> мы в первом случае получим перерасход памяти и увеличение промахов кэша почти в 2 раза.
Верно. Но и во втором случае будет абсолютно тоже самое.
WH>А если в массиве много None, то промахов кэша будет ещё больше.
Эээ что? С чего это? )
WH>Причем руками можно это всё и не писать. WH>Можно сделать что-то типа такого: WH>
Здравствуйте, alex_public, Вы писали:
_>Я вроде как вполне ясно написал: "без возможности разместить объект на стеке". Объект — это общепризнанное название для экземпляра класса. И их в Java и C# никак не разместить на стеке. Кстати, в случае C# сюда стоило бы ещё добавить массивы, строки и делегаты.
А какое же общепризнанное название экземпляра структуры?
Здравствуйте, Klikujiskaaan, Вы писали:
_>>Я вроде как вполне ясно написал: "без возможности разместить объект на стеке". Объект — это общепризнанное название для экземпляра класса. И их в Java и C# никак не разместить на стеке. Кстати, в случае C# сюда стоило бы ещё добавить массивы, строки и делегаты. K>А какое же общепризнанное название экземпляра структуры?
Никакого) А с чего ты решил, что оно должно быть? Как бы наличие у понятия "экземпляр класса" синонима "объект" не означает обязательного наличия какого-то синонима у "экземпляра структуры".
Здравствуйте, D. Mon, Вы писали:
V>>А если ты имел ввиду, что "ссылочность" значения прячется от системы типов, то ты в этом случае НЕ можешь рассуждать о ссылочном типе — это будет просто некая тонкость реализации. DM>Ничего не понял тут.
Например, если значение по ссылке нельзя изменять и ссылка не может быть нулевой, то семантика такого ссылочного типа ничем не отличается от value-типа.
А семантика у ссылочного типа одна — это хранить в себе т.н. "индекс" (или ключ) некоего значения.
Прикладное применение — организация абстракций, — работаем со значением не напрямую, а через индекс/ключ.
Если язык Rust контролирует значение этого индекса через систему типов, то поздравляю — это зачатки зависимых типов в языке.
А если нет, то это не ссылочный тип.
DM>Еще раз: что именно у тебя здесь означает Т+1?
Тоже, что у тебя — сумма типов, т.е. алгебраик {A t:T | B}, где A и B произвольные идентификаторы, например Just и Nothing.
V>>Правда, в этом случае не нужен промежуточный Box<T>, т.е. достаточно лишь Option<T>, не? DM>Для чего достаточно?
Для семантики Option<Box<T>>.
Это твои слова?
Например, в Расте Box<T> и Option<Box<T>> представлены в памяти одинаково
Ты в них уверен?
Просто я залез, таки, в доку к Rust и вижу:
pub enum Option<T> {
None,
Some(T),
}
Обычный алгебраик, но как он реализован в Rust?
Есть же куча способов, например :
struct OptionHolder {
TypeId discriminator_; // или int discriminator_
};
struct None : OptionHolder {};
template<typename T>
class Some : OptionHolder { T value_; };
struct Option { OptionHolder * value_; };
— никакого null, значение None будет представлено ссылкой на единственное значение.
Но да, учитывая возможность оптимизации именно такого сценария, когда у нас сумма из двух типов, причем, один из них представлен единственным значением, то кодируем это единственное значение через null, и тогда Some(T) будет хранится в памяти как T*, а None будет просто 0.
Точно ли Rust умеет оптимизировать такие сценарии до описанного?
Потому что вот с таким определением:
pub enum Option2<T> {
None,
None2,
Some(T),
}
уже такой фокус не получится.
DM>Это разные вещи получатся с разным поведением и разным представлением в памяти.
Ты же выше писал, что Option<Box<T>> и Box<T> имеют одинаковое представление?
V>>Ссылочный тип по-определению — это зависимый тип. V>>Тот самый, угу. V>>Т.е., тип, зависящий от значения: V>>* валидный адрес — тип T V>>* нулевой адрес — тип None.
DM>Стоп-стоп, во-первых у тебя тут Т уже что-то другое начал обозначать.
Почему "другое"?
T — это тип некоего значения по адресу.
DM>Во-вторых, ссылочный тип не обязательно содержит null, я ж об этом говорю. См. тот же Котлин или Раст.
Ссылочный тип обязательно содержит.
Я, таки, посмотрел на Rust только что.
Так вот, Box<T> — это НЕ ссылочный тип ни в одном из мест. Ты меня обманул. ))
Это почти точный аналог моего Strong<T>, который я давал выше.
Вот тебе твой растовский Box<T>, переписанный на Шарпе:
// ссылочный тип-указательclass Ptr<T> where T : struct {
public T Value;
}
// тип-обертка над ссылочным типом, хранящим value-типstruct Box<T> where T : struct {
public Ptr<T> Value { get; private set; }
public Box(T value) {
Value = new Ptr<T> { Value = value };
}
}
// тип-обертка над обычным ссылочным типомstruct Strong<T> where T : class {
public T Value { get; private set; }
public Strong(T value) {
if(value == null) throw ...;
Value = value;
}
}
Обрати внимание на struct Box и struct Strong.
Оба типа хранят внутри себя ссылочный тип, но сами НЕ являются ссылочным типом.
Итого, гарантии соблюдаются не на уровне системы типов, а сугубо на прикладном.
Обрати внимания, что в моём случае невозможно добиться того, чтобы значение (Value) у Box или у Strong было равно null.
В Rust аналогично.
DM>Ну вот есть ссылочный тип string в условной джаве или шарпе. DM>Мы можем написать string s = null;
Можем.
DM>Теперь напиши Option<string> по твоей задумке, да так, чтобы получилось T + 1, а не T.
String в дотнете и джаве — он уже является Option изкаробки, как любой настоящий ссылочный тип.
Если нужен тип, который достоверно не будет nullable, то используй Strong<string>.
Этот Strong<string> можно создать лишь однажды, а потом передавать м/у методами, распространяя однажды полученную гарантию.
Здравствуйте, alex_public, Вы писали:
_>Не очень понял какое отношения эти примеры имеют к данной теме. Ведь в C/C++ (а у тебя стоит для этого кода подсветка именного этого языка) эти два примера не имеют абсолютно никакой разницы в уровне косвенности — тут же нет вообще ссылочный данных.
Похоже ты не понимаешь почему между vector<int*> и vector<int> есть существенная разница в производительности.
Разница в том, что в первом случае намного больше промахов кэша.
А косвенность, к которой ты прицепился сама по себе особого значения не имеет.
_>Кстати, я что-то ничего не слышал про тип данных int2 в C++. Ну точнее я и про int32 не слышал, но подозреваю что тут у тебя просто опечатка и имелось в виду int32_t. Но вот int2_t просто не существует.
Тут не про С++. Тут про некий похожий язык в которых такие типы есть.
Просто раскраска С++ подходит.
_>Ну и да, т.к. разницы в косвенности между этими примерами нет, то выбирать из этих двух вариантов надо в зависимости от планируемых в будущем итераций.
А разница в потреблении памяти и количестве промахов кеша есть.
WH>>В случае с Option<int32> мы в первом случае получим перерасход памяти и увеличение промахов кэша почти в 2 раза. _>Верно. Но и во втором случае будет абсолютно тоже самое. WH>>А если в массиве много None, то промахов кэша будет ещё больше. _>Эээ что? С чего это? )
Option<int32> занимает 33 бита. 32 на int32 и один для определения есть значение или нет.
Но для выравнивания компилятор добавить ещё 31 бит в которых ничего не будет. Они будут тупо жрать память и увеличивать трафик памяти.
А если Option<int32> преобразовать в структуру массивов, то у нас будут два массива. Один состоящий из int32. А второй состоящий из однобитных значений.
Те компилятор фактически сгенерирует вот такой код:
int32 options_Value[1024*1024];
bool options_IsValue[1024*1024];//bool однобитный тип. Массив упакован плотно без дырок.
//Те в каждом байте у нас лежит 8 bool'ов.for (int32 i = 0; i < 1024*1024; ++i)
{
if (options_IsValue[i])
{
//делаем что-то с options_Value[i]
}
}
Тут у нас есть два крайних случая.
1)Везде Some(что-то)
2)Везде None
В первом случае мы прочитаем в 33 раза больше памяти чем во втором.
А если без разбиения на массивы, то в 64 раза больше. Причём всегда.
А доступ к памяти удовольствие дорогое.
ЗЫ Следи за цитированием.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, fddima, Вы писали:
F>Просто забавно читать как все упёрлись в 0. А как же 1, 2... где же фантазия.
Всё уперлось в некий тип None, у которого будет ровно одно валидное значение.
Итого, тут два варианта:
либо использовать единственный инстанс этого None и его адрес как признак этого самого None;
либо "сократить его в уме" и вместо ссылки на некое (никому не нужное) единственное значение использовать некий зарезервированный адрес.
ИМХО, выбор 0 — неплох. Например, FFFF был бы хуже, т.к. проверка была чуть дороже и вообще, значение зависит от битовой ширины адреса.
А еще язык С (и С++) так хитро устроен, что способен приводить целочисленное значение 0 к типу указателя на любой тип. О как! )) Эдакий хак. В Джава и дотнете для аналогичного ввели специальное ключевое слово, чтобы не насиловать систему типов.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, Klikujiskaaan, Вы писали:
_>>>Я вроде как вполне ясно написал: "без возможности разместить объект на стеке". Объект — это общепризнанное название для экземпляра класса. И их в Java и C# никак не разместить на стеке. Кстати, в случае C# сюда стоило бы ещё добавить массивы, строки и делегаты. K>>А какое же общепризнанное название экземпляра структуры?
_>Никакого) А с чего ты решил, что оно должно быть? Как бы наличие у понятия "экземпляр класса" синонима "объект" не означает обязательного наличия какого-то синонима у "экземпляра структуры".
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, meadow_meal, Вы писали:
_>>Optional<Optional<ClanID>> clan; _>>В этом случае наличие значения clan означает, что информация передана клиенту, наличие значения clan.value означает, что пришел новый клан игрока, а его отсутствие — что игрок больше не состоит в клане.
V> V>За такое увольнять нафик.
Так предложи альтернативу.
Задача: имеется сущность Xxx, содержащая следующие свойства:
T1 field1;
T2 field2;
...
В IDL нужно объявить протокольный дельта-рекорд XxxUpdate для пересылки изменившихся (и только изменившихся полей). T1, T2 и т.п. могут быть различными типами, в том числе опциональными. Используются дельта-рекорды достаточно часто, поэтому требуется максимально простой паттерн с минимальным количеством частных случаев.
Решение в лоб:
record XxxUpdate
{
Optional<T1> value1;
Optional<T2> value2;
...
}
и если T это какой-нибудь Optional<int>, то так тому и быть. Вот здесь и образуется Optional<Optional<int>>.
V>Представляю все масштабы "удовольствия" от сопровождения такого кода. ))
Удовольствие на практике в C# выглядит примерно так:
public int Field1 { get; private set; }
public int? Field2 { get; private set; }
void Update(XxxUpdate update)
{
if (update.Field1.HasValue)
this.Field1 = update.Field1.Value;
if (update.Field2.HasValue)
this.Field2 = update.Field2.Value;
}
На этом ручной код сопровождения заканчивается (все остальное — сгенерировано), а вместе с ним и масштабы удовольствия. Да и этот код можно генерировать.
Здравствуйте, WolfHound, Вы писали:
_>>Не очень понял какое отношения эти примеры имеют к данной теме. Ведь в C/C++ (а у тебя стоит для этого кода подсветка именного этого языка) эти два примера не имеют абсолютно никакой разницы в уровне косвенности — тут же нет вообще ссылочный данных. WH>Похоже ты не понимаешь почему между vector<int*> и vector<int> есть существенная разница в производительности. WH>Разница в том, что в первом случае намного больше промахов кэша. WH>А косвенность, к которой ты прицепился сама по себе особого значения не имеет.
"Похоже ты не понимаешь, почему машина останавливается. Это происходит потому, что отключается двигатель. А тот факт, что в машине закончилось топливо, особого значения не имеет" (C) ПрактическиТы )))
_>>Кстати, я что-то ничего не слышал про тип данных int2 в C++. Ну точнее я и про int32 не слышал, но подозреваю что тут у тебя просто опечатка и имелось в виду int32_t. Но вот int2_t просто не существует. WH>Тут не про С++. Тут про некий похожий язык в которых такие типы есть. WH>Просто раскраска С++ подходит.
Что-то не припомню языка с реальным (т.е. чтобы массив из 4-ёх штук занимал один байт) двухбитовым типом. Максимум что вспоминается, это битовые поля C++, но они опять же не позволяют задавать массивов. Снова фантазируешь? )
_>>Ну и да, т.к. разницы в косвенности между этими примерами нет, то выбирать из этих двух вариантов надо в зависимости от планируемых в будущем итераций. WH>А разница в потреблении памяти и количестве промахов кеша есть.
Естественно есть и для эффективного кода надо выбирать правильный вариант, в зависимости от деталей архитектуры приложения (итераций какого вида больше всего). Только опять же оба эти варианта работают на базе нессылочных данных, так что совершенно непонятно какое отношение их сравнение имеет к обсуждаемой нами теме неэффективности ссылочных данных? )
WH>Option<int32> занимает 33 бита. 32 на int32 и один для определения есть значение или нет. WH>Но для выравнивания компилятор добавить ещё 31 бит в которых ничего не будет. Они будут тупо жрать память и увеличивать трафик памяти. WH>А если Option<int32> преобразовать в структуру массивов, то у нас будут два массива. Один состоящий из int32. А второй состоящий из однобитных значений. WH>Те компилятор фактически сгенерирует вот такой код: WH>
WH>int32 options_Value[1024*1024];
WH>bool options_IsValue[1024*1024];//bool однобитный тип. Массив упакован плотно без дырок.
WH>//Те в каждом байте у нас лежит 8 bool'ов.
WH>for (int32 i = 0; i < 1024*1024; ++i)
WH>{
WH> if (options_IsValue[i])
WH> {
WH> //делаем что-то с options_Value[i]
WH> }
WH>}
WH>
WH>Тут у нас есть два крайних случая. WH>1)Везде Some(что-то) WH>2)Везде None WH>В первом случае мы прочитаем в 33 раза больше памяти чем во втором. WH>А если без разбиения на массивы, то в 64 раза больше. Причём всегда.
Не верно, будет 64 и 32 раза, но это не суть, просто мелкое замечание. )
WH>А доступ к памяти удовольствие дорогое.
И какое отношение данный код (кстати вполне реальный и эффективный) имеет даже уже не к нашей дискуссии о неэффективности ссылочных данных, а хотя бы к двум твоим предыдущим примерам? )))
Если мы уходим от использование optional (причём не в сторону ссылочных данных, а наоборот на ещё более низкий уровень битовых полей и т.п.), то возникают уже совершенно другие расклады по производительности (причём это применимо и к варианту без разбиения на массивы — там же тоже можно сделать более интересный код без optional).
Здравствуйте, Klikujiskaaan, Вы писали:
_>>Никакого) А с чего ты решил, что оно должно быть? Как бы наличие у понятия "экземпляр класса" синонима "объект" не означает обязательного наличия какого-то синонима у "экземпляра структуры". K>Т.е. экземпляр структуры — это не объект?
Смотря в каком языке. Скажем в C и C# — нет, т.к. не удовлетворяют требованиям ООП. А в том же C++, где классы и структуры почти не отличимы, вполне можно обозвать экземпляр структуры объектом.
P.S. Естественно мы тут говорим о компьютерной терминологии (в частности об ООП), а не о бытовом значение слова объект, которое можно применить почти к любой сущности в коде. )))
Здравствуйте, vdimas, Вы писали:
DM>>Еще раз: что именно у тебя здесь означает Т+1? V>Тоже, что у тебя — сумма типов, т.е. алгебраик {A t:T | B}, где A и B произвольные идентификаторы, например Just и Nothing.
Ок. Тогда утверждение о том, что любой ссылочный тип таков, я считаю ошибочным. Чуть ниже о том же.
V>>>Правда, в этом случае не нужен промежуточный Box<T>, т.е. достаточно лишь Option<T>, не? DM>>Для чего достаточно? V>Для семантики Option<Box<T>>.
Ну так она будет отличаться от Option<T>.
V>Это твои слова? V>
Например, в Расте Box<T> и Option<Box<T>> представлены в памяти одинаково
V>Ты в них уверен?
Вот из доки слова:
This usage of Option to create safe nullable pointers is so common that Rust does special optimizations to make the representation of Option<Box<T>> a single pointer. Optional pointers in Rust are stored as efficiently as any other pointer type. https://doc.rust-lang.org/1.10.0/std/option/index.html
V>Точно ли Rust умеет оптимизировать такие сценарии до описанного?
А ты точно читал доку?
DM>>Это разные вещи получатся с разным поведением и разным представлением в памяти. V>Ты же выше писал, что Option<Box<T>> и Box<T> имеют одинаковое представление?
Я про Option<Box<T>> и Option<T>.
DM>>Во-вторых, ссылочный тип не обязательно содержит null, я ж об этом говорю. См. тот же Котлин или Раст. V>Ссылочный тип обязательно содержит.
Нет, это твои иллюзии. Есть немало языков, где это не так. Или просто альтернативное определение ссылочного типа у тебя. Попробуй его сформулировать точно.
V>Обрати внимание на struct Box и struct Strong. V>Оба типа хранят внутри себя ссылочный тип, но сами НЕ являются ссылочным типом.
Почему именно не являются?
DM>>Ну вот есть ссылочный тип string в условной джаве или шарпе. DM>>Мы можем написать string s = null; V>Можем.
DM>>Теперь напиши Option<string> по твоей задумке, да так, чтобы получилось T + 1, а не T.
V>String в дотнете и джаве — он уже является Option изкаробки, как любой настоящий ссылочный тип.
Нет. Еще раз напомню про T+1, T+1+1 и т.д.
Попробуй сформулировать точно определение ссылочного типа и определение Option<T>.
Эмм, с null/0 то понятно.
Я говорил, что для ссылочного T некий Option2<T> с None и None2 вполне представим в виде всё того же указателя с двумя спец. значениями.
Безусловно — проверки дороже, но хотя бы с точки зрения хранения — явно компактнее чем тянуть байт и указатель.
Здравствуйте, meadow_meal, Вы писали:
_>Так предложи альтернативу.
Если речь об IDL, то проблемно-ориентированное кодирование всей твоей матрешки идёт вот так:
enum ClanUpdate { NoUpdate, EnterClan, ExitClan };
union ClanEvent switch (ClanUpdate) {
case NoUpdate: ;
case EnterClan: int ClanId;
case ExitClan: ;
};
Если разновидность IDL с автоопределением типа дискриминанта, то будет даже проще:
union ClanEvent {
case NoUpdate: ;
case EnterClan: int ClanId;
case ExitClan: ;
};
(не надо явно вводить `enum ClanUpdate`)
Итого, для случая выхода из клана тоже достаточно одного байта, а не два, как у тебя сейчас.
Но можно еще проще.
Считаем, что "игрок больше не состоит в клане" — это тоже клан такой. Эдакий клан бесклановых игроков.
Выдели для него некий отдельный ID.
И далее можно унифицировано обновлять поля в плоском сообщении без геммороя со вложенными Nullable в другие Nullable.
_>Задача: имеется сущность Xxx, содержащая следующие свойства:
_>и если T это какой-нибудь Optional<int>, то так тому и быть. Вот здесь и образуется Optional<Optional<int>>.
А вот решение так себе.
Ты в сетку как передаёшь?
По байту на признак наличия каждого поля? Если брать IDL, то меньше байта не выйдет, верно? А если не проконтроллировать размер дискриминанта в union, то может легко быть и 2 байта и 4 байта. ))
В общем, использовать IDL в 2017-м (CORBA IDL небось?) — это вообще признак оторванности от реальности.
А полей сколько? Предположим, что у тебя 30 полей, а в среднем за раз передаются 2-3 поля?
И ты на каждое обновление пробегаешься по всем 30-ти полям, что ле, вот в этой манере:
void Update(XxxUpdate update)
{
if (update.Field1.HasValue)
this.Field1 = update.Field1.Value;
if (update.Field2.HasValue)
this.Field2 = update.Field2.Value;
...
Очевидно же, что стратегий упаковки обновлений может быть несколько.
Когда полей пересылается мало, то можно применить альтернативную кодировку — вначале прогнать номера полей (тут 5 бит на номер хватит), а потом их значения.
Когда полей посылается много, то можно вначале прогнать битовую маску. Сетевые драйвера баз данных, кста, зачастую делают точно так же: для каждой передаваемой записи, если в ней встречаются Nullable-поля, передаётся сначала битовая маска. И да, в этой битовой маске участвуют только Nullable поля. Например, если полей 16, но Nullable из них всего два, то обе стороны знают, что на маску будет выделен 1 байт в потоке.
Ну или можно взять какой-нить ASN.1 с хорошим профилём упаковки.
Но можно и самому набросать сериализатор. А еще лучше самому набросать утилиту генерации сериализатора по метаинформации, раз уж речь о дотнете, благо по состоянию на 2017-й год инфраструктура развита сильно (можно брать BLToolkit как маппер), там трудоемкости, скорее всего, будет меньше, чем возни с сопряжением c каким-нить IDL-фреймворком.
Через BLToolkit можно сделать так, чтобы код обновления полей вообще ручками писать не надо было.
Здравствуйте, D. Mon, Вы писали:
DM>>>Еще раз: что именно у тебя здесь означает Т+1? V>>Тоже, что у тебя — сумма типов, т.е. алгебраик {A t:T | B}, где A и B произвольные идентификаторы, например Just и Nothing. DM>Ок. Тогда утверждение о том, что любой ссылочный тип таков, я считаю ошибочным.
Ты не можешь считать чьи-то утверждения ошибочными или нет, пока не обоснуешь.
"Пролетарское чутьё" не считается.
V>>Это твои слова? V>>
Например, в Расте Box<T> и Option<Box<T>> представлены в памяти одинаково
V>>Ты в них уверен? DM>Вот из доки слова: DM>This usage of Option to create safe nullable pointers is so common that Rust does special optimizations to make the representation of Option<Box<T>> a single pointer. Optional pointers in Rust are stored as efficiently as any other pointer type. DM>https://doc.rust-lang.org/1.10.0/std/option/index.html
V>>Точно ли Rust умеет оптимизировать такие сценарии до описанного? DM>А ты точно читал доку?
Именно что читал определения Optional и Box. А ты читал?
В общем, если Rust умеет оптимизировать конкретную связку только этих 2-х типов — то это неинтересно. Это лишь некрасивый костыль.
Вот если бы он из исходных определений Optional и Box получил то же самое, причем, воспроизводимое на любых других пользовательских Optional_2 и Box_3, то это был бы респект.
DM>>>Во-вторых, ссылочный тип не обязательно содержит null, я ж об этом говорю. См. тот же Котлин или Раст. V>>Ссылочный тип обязательно содержит. DM>Нет, это твои иллюзии.
Опять пролетарское чутьё подсказывает? ))
Разве тебя не впечатлило, что я еще в глаза не видя этого Rust сразу сказал, где у тебя ошибка?
Если язык Rust контролирует значение этого индекса через систему типов, то поздравляю — это зачатки зависимых типов в языке. А если нет, то это не ссылочный тип.
Речь шла о твоём Box<T>.
Процитированное ты не переплюнешь вааще никак, бо только языки с поддержкой зависимых типов умеют контролировать строгость указателя на уровне типов. А все остальные языки порождают аналогичные гарантии сугубо на прикладном уровне.
Поэтому, ты и сам должен был бы препроверить, без моей помощи, что там в Rust происходит (зависимые типы в языке или Box<> не является ссылочным типом) — ведь это ты взялся приводить примеры из этого языка, верно? Я ж за тебя столько работы сделал, получается. ))
DM>Есть немало языков, где это не так.
Ну, с языком Rust уже разобрались — мимо.
Котлин не смотрел, а что там? Зависимые типы? Или строки имеют семантику value-type? ))
Ну вот читаю вводную статью по Котлин:
Нулевые указатели — одна из наиболее недопонятых концепций в языках программирования. Нет ничего плохого в том, что некоторое значение может быть установлено, а может отсутствовать. Такая концепция есть во многих языках программирования. В Хаскеле она называется Maybe, в F# это option, в T-SQL это null.
Блин, прямо с языка снимают — я именно это и говорил в предыдущих постах. ))
Почитал далее — всё ясно. Они разнесли nullable и non-nullable ссылочные типы по ТИПАМ, а не по значениям одного и того же типа.
ЧТД. Нубы. Ниасилили ЗТ. ))
Этот трюк полностью аналогичен Box<T> из Rust или любому показанному мною StrongRef<T>, скажем, в С++ или в другом, сколь-угодно "опасном" языке.
Подтерждается это так же тем, что отсутствует возможность сравнивать сами ссылки.
И переменные иммутабельны. (я правильно понял?)
Итого — ссылочного типа в языке нет.
V>>Обрати внимание на struct Box и struct Strong. V>>Оба типа хранят внутри себя ссылочный тип, но сами НЕ являются ссылочным типом. DM>Почему именно не являются?
Потому что RTFM.
Типы Box и Optional определены средствами языка.
Первый — это struct Box, второй — enum Optional.
Ни один из них не является ссылочным типом в системе типов Rust.
А как конкретная комбинация типов представлена в памяти — да какая разница? Пусть даже займут ячеек размером с указатель, но в системе типов они никогда не станут эквивалентны указателю T*.
Абсолютно точно так же, если в С++ обернуть структурой указатель:
template<typename T>
struct Box { T * ptr_; };
То представление в памяти этой структуры и указателя будет идентичным. А вот в системе типов — дудки.
DM>>>Теперь напиши Option<string> по твоей задумке, да так, чтобы получилось T + 1, а не T. V>>String в дотнете и джаве — он уже является Option изкаробки, как любой настоящий ссылочный тип. DM>Нет. Еще раз напомню про T+1, T+1+1 и т.д.
Лучше было бы помедитировать, прежде чем опять неткать и опять мимо.
Потому что T+1 != T+1+1
Дальше озвучивать или уже догадался?
DM>Попробуй сформулировать точно определение ссылочного типа и определение Option<T>.
Здравствуйте, fddima, Вы писали:
F> Эмм, с null/0 то понятно. F>Я говорил, что для ссылочного T некий Option2<T> с None и None2 вполне представим в виде всё того же указателя с двумя спец. значениями. F> Безусловно — проверки дороже, но хотя бы с точки зрения хранения — явно компактнее чем тянуть байт и указатель.
Здравствуйте, vdimas, Вы писали:
V>>>Тоже, что у тебя — сумма типов, т.е. алгебраик {A t:T | B}, где A и B произвольные идентификаторы, например Just и Nothing. DM>>Ок. Тогда утверждение о том, что любой ссылочный тип таков, я считаю ошибочным. V>Ты не можешь считать чьи-то утверждения ошибочными или нет, пока не обоснуешь.
Ну считать-то я могу что угодно, еще как могу. Но обосновать стоит, конечно.
Сначала несколько примеров.
Пример первый. Окамл:
let a = object
val mutable n = 1
method say = Printf.printf "n is %d\n" n
method inc = n <- n + 1
end;;
let b = a in
a#say;
b#inc;
a#say;;
Выводит:
n is 1
n is 2
Тут переменные a и b ссылаются на один объект в памяти, это переменные ссылочного типа. Мы вызываем метод, мутирующий объект b, и видим, что метод say у a теперь возвращает другое значение. Теперь попробуй в переменную такого типа записать null (ну или создать новую переменную того же типа и с нулевым значением). Не выйдет, как ни старайся, в языке Окамл ссылки не бывают нулевыми и объекты нуллами.
Пример второй: типы вроде String и вообще классы в языке Kotlin. Это тоже ссылочные типы, с ссылочной семантикой, и null эти типы не содержат, в отличии от других типов, со знаком вопроса на конце. На уровне системы типов опять же сделаны ссылочные типы без null.
Пример третий: Раст. Там есть ссылки вроде &T, это тоже ссылочный тип, тоже не включает null. Там есть Box<T> ("A pointer type for heap allocation." — первая же строка в описании), который хоть и описан как struct, на самом деле имеет специальную поддержку со стороны компилятора (см. ключевые слова box, owned_box и пр.) и фактически является указателем с дополнительным поведением. И он тоже всегда ненулевой.
Короче, давай засунь null в переменную объектного типа в окамле, в &i32 в Расте и в String в Котлине. Или GTFO.
V>Разве тебя не впечатлило, что я еще в глаза не видя этого Rust сразу сказал, где у тебя ошибка?
У меня что-то с глазами сегодня. Я что-то не вижу ошибки, извини.
V>Процитированное ты не переплюнешь вааще никак, бо только языки с поддержкой зависимых типов умеют контролировать строгость указателя на уровне типов.
Выше три примера, где языки без зависимых типов именно это и делают.
V>Почитал далее — всё ясно. Они разнесли nullable и non-nullable ссылочные типы по ТИПАМ, а не по значениям одного и того же типа. V>ЧТД. Нубы. Ниасилили ЗТ. ))
Это я тут должен говорить ЧТД и называть тебя нубом. Ибо тут именно ты сел в лужу.
DM>>>>Теперь напиши Option<string> по твоей задумке, да так, чтобы получилось T + 1, а не T. V>>>String в дотнете и джаве — он уже является Option изкаробки, как любой настоящий ссылочный тип. DM>>Нет. Еще раз напомню про T+1, T+1+1 и т.д.
V>Лучше было бы помедитировать, прежде чем опять неткать и опять мимо. V>Потому что T+1 != T+1+1 V>Дальше озвучивать или уже догадался?
Ты опять вперед забегаешь. Давай сперва начало: Option<T> — это не тип, это конструктор типа, функтор, шаблон типа, генерик (выбери знакомое слово). Покажи, где джавский или шарповый string это генерик.
DM>>Попробуй сформулировать точно определение ссылочного типа и определение Option<T>.
V>Я уже дал тебе определения V>тут: http://www.rsdn.org/forum/philosophy/6705938.1 V>и тут: http://www.rsdn.org/forum/philosophy/6706396.1
Там плохие, неправильные определения. Они не описывают ссылочных типов как минимум в трех упомянутых выше языках. Давай еще подкину:
4) &int в С++
5) ref int в D
Ссылочные типы? Еще как ссылочные. Содержат null / None / Nothing ? Нет.
Здравствуйте, vdimas, Вы писали:
V>Если речь об IDL, то проблемно-ориентированное кодирование всей твоей матрешки идёт вот так: V>
V>enum ClanUpdate { NoUpdate, EnterClan, ExitClan };
V>union ClanEvent switch (ClanUpdate) {
V> case NoUpdate: ;
V> case EnterClan: int ClanId;
V> case ExitClan: ;
V>};
V>
Сразу вопросы:
1) А для обязательных полей ты тоже рекомендуешь объявлять enum и union? Или мы обязательные поля обновляем одним способом, а опциональные другим? Если так, то разве это — не проблема сопровождения? (Для меня одно из базовых требований при выборе паттерна — то, что придет новый человек и сможет сходу решать любую простую задачу "по аналогии".)
2) Вот эти 6 лишних строк и один-два типа на ровном месте для каждого поля — вот ради чего это? При том, что использоваться в пользовательском коде это богатство и не будет.
V>Итого, для случая выхода из клана тоже достаточно одного байта, а не два, как у тебя сейчас.
У меня сейчас 1 бит в хедере + 1 байт для поля. Свести к двум битам — не проблема, только вот единственным наблюдаемым эффектом будет усложнение документации по бинарному протоколу.
V>Но можно еще проще. V>Считаем, что "игрок больше не состоит в клане" — это тоже клан такой. Эдакий клан бесклановых игроков. V>Выдели для него некий отдельный ID.
Это хуже по следующим причинам:
1) Игрок больше не состоит в клане — это не тоже клан такой. Из него выйти нельзя, он в UI не отображается, у него нет ни одного атрибута клана. Я считаю, что если это различие можно выразить в системе типов, то его нужно выразить.
2)
public class Player
{
// 1public int ClanID { get; private set; }
// 2public int? ClanID { get; private set; }
}
Нужно проверить, состоит ли игрок в клане. В каком из случаев очевидно, как это сделать, а в каком остается чесать репу?
3) Теперь для каждого случая обновления опционального по своей природе поля мы будем придумывать новый хак?
На самом деле это мы как раз пробовали в первую очередь. Люди плевались и просили Optional<Optional<>>.
V>И далее можно унифицировано обновлять поля в плоском сообщении без геммороя со вложенными Nullable в другие Nullable.
Вот это называется унифицированно? Когда на каждый чих нужно придумывать клан который не клан?
V>Ты в сетку как передаёшь? V>По байту на признак наличия каждого поля? Если брать IDL, то меньше байта не выйдет, верно?
По биту.
V>В общем, использовать IDL в 2017-м (CORBA IDL небось?) — это вообще признак оторванности от реальности.
А как в 2017 нужно описывать сетевой протокол с поддержкой сериализации для разных языков и форматов и возможностью генерации схемы?
IDL внутренней разработки.
V>И ты на каждое обновление пробегаешься по всем 30-ти полям, что ле, вот в этой манере: V>
V>void Update(XxxUpdate update)
V>{
V> if (update.Field1.HasValue)
V> this.Field1 = update.Field1.Value;
V> if (update.Field2.HasValue)
V> this.Field2 = update.Field2.Value;
V>...
V>
Да. А что именно смущает? Я уже писал, этот код можно сгенерировать.
V>Когда полей посылается много, то можно вначале прогнать битовую маску.
Для дельта-рекордов мы так и делаем.
V>Ну или можно взять какой-нить ASN.1 с хорошим профилём упаковки.
Нельзя. У ASN.1 крайне неудобный язык и нет возможности удобно контролировать кодогенерацию через атрибуты. С сериализацией в JSON или XML тоже беда. А упаковка у нас компактнее и быстрее.
V>А еще лучше самому набросать утилиту генерации сериализатора по метаинформации, раз уж речь о дотнете, благо по состоянию на 2017-й год инфраструктура развита сильно (можно брать BLToolkit как маппер), там трудоемкости, скорее всего, будет меньше, чем возни с сопряжением c каким-нить IDL-фреймворком.
Я писал, что сервер у нас на Эрланге. И вообще, дотнет-клиент (на самом деле Unity3d, у которого до сих пор рантайм 2.0) — лишь один из, кроме него есть большой набор приложений, работающих с данными IDL. Поэтому внешний IDL — это обязательное требование.
Возни с ним нет. Это отработано годами.
V>Через BLToolkit можно сделать так, чтобы код обновления полей вообще ручками писать не надо было.
Так и приведенный код обновления полей я могу сгенерировать скриптом расширения к нашему кодогенератору строк так на восемь. Но хватает и тонкостей, например с обновлением коллекций (а там пока каждый второй случай — частный), поэтому проще в итоге писать руками.
Резюмируя: я просил привести альтернативы и описать проблемы с текущим решением. Две альтернативы ты привел, одна из них очень многословная и спорная (а компенсирующих преимуществ я пока не вижу). Вторая — грязноватый хак, уже опробованный на практике с негативным эффектом. А вот в чем проблема с текущим решением и его якобы тяжелой поддержкой ты так и не объяснил.
Здравствуйте, meadow_meal, Вы писали:
V>>Если речь об IDL, то проблемно-ориентированное кодирование всей твоей матрешки идёт вот так: V>>
V>>enum ClanUpdate { NoUpdate, EnterClan, ExitClan };
V>>union ClanEvent switch (ClanUpdate) {
V>> case NoUpdate: ;
V>> case EnterClan: int ClanId;
V>> case ExitClan: ;
V>>};
V>>
_>Сразу вопросы:
_>1) А для обязательных полей ты тоже рекомендуешь объявлять enum и union? Или мы обязательные поля обновляем одним способом, а опциональные другим?
Я уже ответил тебе исчерпывающе.
1. Идея использовать union — она твоя, я лишь показал кривизну этого сценария с Opntional<Optional<ClanId>>.
2. Затем предложил еще два варианта упаковки сообщений и даже дал тебе как раз сценарий из сетевых дров БД, когда в одном потоке иду обязательные и необязательные поля.
_>Если так, то разве это — не проблема сопровождения? (Для меня одно из базовых требований при выборе паттерна — то, что придет новый человек и сможет сходу решать любую простую задачу "по аналогии".)
А я тебе предложил взять BLToolkit и всё что потребуется сделать новому человеку — это правильно расставить атрибуты в добавляемых/исправляемых структурах и их полях.
_>2) Вот эти 6 лишних строк и один-два типа на ровном месте для каждого поля — вот ради чего это?
Чтобы пришедшие после тебя не материли тебя, не проклинали, не выбросили нахрен твоё поделие и не писали своё.
_>У меня сейчас 1 бит в хедере + 1 байт для поля. Свести к двум битам — не проблема, только вот единственным наблюдаемым эффектом будет усложнение документации по бинарному протоколу.
Если у тебя 1 бит в заголовке, то ты морочишь мне голову и насчет IDL и насчет Optional.
Получается, насосал из пальца несуществующий сценарий.
_>Это хуже по следующим причинам: _>1) Игрок больше не состоит в клане — это не тоже клан такой. Из него выйти нельзя, он в UI не отображается, у него нет ни одного атрибута клана. Я считаю, что если это различие можно выразить в системе типов, то его нужно выразить.
Для этого надо, чтобы язык такое позволял, а дотнет тебе такое НЕ позволит. В системе типов дотнета у тебя всегда будет Optional<ClanId> — т.е. алгебраическая сумма всех валидных значений ClanId плюс одно специальное.
А у тебя точно ВСЕ допустимые значения ClanId будут являться валидными?
_>2) _>
_>public class Player
_>{
_> // 1
_> public int ClanID { get; private set; }
_> // 2
_> public int? ClanID { get; private set; }
_>}
_>
_>Нужно проверить, состоит ли игрок в клане. В каком из случаев очевидно, как это сделать, а в каком остается чесать репу?
Вот так будет очевидно:
enum ClanId {
NotSet = 0;
}
_>3) Теперь для каждого случая обновления опционального по своей природе поля мы будем придумывать новый хак?
Т.е. ты не понял ни строчки из сообщения, на которое отвечаешь? ))
_>На самом деле это мы как раз пробовали в первую очередь. Люди плевались и просили Optional<Optional<>>.
Из пальца вытяжки пошли уже. ))
Потому что чаще люди плюются на вложенность Optional.
И вообще на любую вложенность.
V>>И далее можно унифицировано обновлять поля в плоском сообщении без геммороя со вложенными Nullable в другие Nullable. _>Вот это называется унифицированно? Когда на каждый чих нужно придумывать клан который не клан?
А как же они пользуются методом IndexOf, бедные, если оно возвращает специальное значение -1?
V>>Ты в сетку как передаёшь? V>>По байту на признак наличия каждого поля? Если брать IDL, то меньше байта не выйдет, верно? _>По биту.
ЧТД. Наврал с три короба про IDL.
Итого, в отсутствии IDL-компилятора ты предлагаешь ручками оперировать с discriminated union?
И после этого у тебя поднимается рука обвинять кого-то в сложности?
V>>В общем, использовать IDL в 2017-м (CORBA IDL небось?) — это вообще признак оторванности от реальности. _>А как в 2017 нужно описывать сетевой протокол с поддержкой сериализации для разных языков и форматов и возможностью генерации схемы?
Нет это ты мне ответь, как ты умудрился в IDL закодировать Optional через один бит.
_>IDL внутренней разработки.
Т.е. вы разработали синтаксический и семантический анализатор собственной версии IDL, а так же кодогенератор для разных языков, а так же для каждого из языков разработали однозначные правила маппинга типов вашего IDL на типы языка. Я ничего не пропустил?
Ребят, вы динамите инвестора, реально.
Занимаетесь не делом, а тем, что вам интересно — побочными какими-то вещами.
И после этого у тебя хватает совести задавать вопросы из разряда:
Когда на каждый чих нужно придумывать клан который не клан?
V>>И ты на каждое обновление пробегаешься по всем 30-ти полям, что ле, вот в этой манере: V>>
V>>void Update(XxxUpdate update)
V>>{
V>> if (update.Field1.HasValue)
V>> this.Field1 = update.Field1.Value;
V>> if (update.Field2.HasValue)
V>> this.Field2 = update.Field2.Value;
V>>...
V>>
_>Да. А что именно смущает? Я уже писал, этот код можно сгенерировать.
Смущает, что если это ручной код, то за это вон из профессии.
ORM слышал?
Ручками в 2017-м не принято.
А если это автогенерённый код, то вон из профессии уже за применение Optional в сугубо внутреннем промежуточном типе XxxUpdate, который живет сугубо в недрах вашего сетевого "драйвера".
В общем, автогенерённому коду никакой Optional и даром не нужен. Автогенерённый код может пробегаться прямо по битовой маске и не допускать ошибок из разряда "опять человеческий фактор подвёл".
V>>Когда полей посылается много, то можно вначале прогнать битовую маску. _>Для дельта-рекордов мы так и делаем.
А для обновления отдельных полей или их групп лучше ввести отдельные же сообщения { MessageId, field1, field2 }. Или унифицировать до пар { FieldId, field }, но передавать не попарно, а сначала все FieldId из пакета, а потом все поля. Потому что FieldId можно упаковывать в битовую ширину меньше байта.
V>>Ну или можно взять какой-нить ASN.1 с хорошим профилём упаковки. _>Нельзя. У ASN.1 крайне неудобный язык и нет возможности удобно контролировать кодогенерацию через атрибуты.
Чего-чего???
Определение структур и union в ASN.1 практически точно такое же, как в IDL.
А уж за ручную кодогенерацию на разные языки из своей "версии" IDL на месте инвестора я бы с вами уже судился на компенсацию убытков.
_>Я писал, что сервер у нас на Эрланге.
Тем более.
Можно описать поля в виде того же "JSON без кавычек", и не париться с разработкой парсера.
Ведь вам из всей функциональности IDL требуется описать только типы данных, но не интерфейсы.
Так с какого боку вы потратили кучу ресурсов на Interface Definition Language?
Тем более, что именно под Эрланг и именно под дотнет есть сразу несколько хороших решений ASN.1.
И твой аргумент насчет XML:
ASN.1 standard XML Encoding Rules (XER) makes it possible for messages defined using ASN.1 to be encoded in XML format.
А у вас случился синдром NIH.
Хотя огромное кол-во именно сетевых протоколов (Bluetooth, обмен SSL-сертификатами и т.д.) юзают ASN.1.
Он для этого и был придуман.
И он подходит для сетевого общения НАМНОГО лучше любого мыслимого IDL.
Ну разве что я бы понял единственный сценарий: разработка совсем простого проблемно-ориентированного языка описания данных (но не IDL) и генерацию из этого самописного языка ASN.1. И ву а ля.
_>Поэтому внешний IDL — это обязательное требование.
Насосанное из пальца, а не обязательное.
Не вы одни сетевые протоколы пишете.
_>Возни с ним нет. Это отработано годами.
Кошмар. Вот это поделие стоило годы человеколет?
Однозначно надо подавать на компенсацию убытков. ))
V>>Через BLToolkit можно сделать так, чтобы код обновления полей вообще ручками писать не надо было. _>Так и приведенный код обновления полей я могу сгенерировать скриптом расширения к нашему кодогенератору строк так на восемь. Но хватает и тонкостей, например с обновлением коллекций (а там пока каждый второй случай — частный), поэтому проще в итоге писать руками.
Т.е. годы человеколет потрачены, а всё-равно маппинг данных на объекты происходит ручками. ЧТД.
_>Резюмируя: я просил привести альтернативы и описать проблемы с текущим решением. Две альтернативы ты привел, одна из них очень многословная и спорная (а компенсирующих преимуществ я пока не вижу). Вторая — грязноватый хак, уже опробованный на практике с негативным эффектом. А вот в чем проблема с текущим решением и его якобы тяжелой поддержкой ты так и не объяснил.
Самая главная у тебя проблема — это много недоговариваний, преувеличений и откровенного вранья в итоге. Вот уже выясняется, что твои Optional<Optional<ClanId>>, оказываются, никуда далее слоя "драйвера сетевого протокола" не пролезают и никому нафик не уперлись. У-у-упс? Причем, это тот слой, который должен быть автогенерён в любом случае, потому что именно там живёт куча ошибок у всех вот этих сетевых приблуд.
Здравствуйте, alex_public, Вы писали:
_>Что-то не припомню языка с реальным (т.е. чтобы массив из 4-ёх штук занимал один байт) двухбитовым типом. Максимум что вспоминается, это битовые поля C++, но они опять же не позволяют задавать массивов. Снова фантазируешь? )
Я даже в С++ могу сделать шаблон который будет это делать.
А уж сделать компилятор, который такое может вообще не проблема.
_>Естественно есть и для эффективного кода надо выбирать правильный вариант, в зависимости от деталей архитектуры приложения (итераций какого вида больше всего). Только опять же оба эти варианта работают на базе нессылочных данных, так что совершенно непонятно какое отношение их сравнение имеет к обсуждаемой нами теме неэффективности ссылочных данных? )
Структура массивов предпочтительна практически всегда.
Они только могут сравнится по производительности, когда в цикле используют все поля.
WH>>Тут у нас есть два крайних случая. WH>>1)Везде Some(что-то) WH>>2)Везде None WH>>В первом случае мы прочитаем в 33 раза больше памяти чем во втором. WH>>А если без разбиения на массивы, то в 64 раза больше. Причём всегда. _>Не верно, будет 64 и 32 раза, но это не суть, просто мелкое замечание. )
Не можешь память посчитать, а лезешь рассказывать про низкоуровневые оптимизации.
_>И какое отношение данный код (кстати вполне реальный и эффективный) имеет даже уже не к нашей дискуссии о неэффективности ссылочных данных,
Наипрямейшие. Если уж мы начинаем корёжить структуры данных для того чтобы процессор работал лучше нужно идти до конца и переходить к структуре массивов.
_>а хотя бы к двум твоим предыдущим примерам? )))
Это просто примеры одной оптимизации для разных случаев.
_>Если мы уходим от использование optional (причём не в сторону ссылочных данных, а наоборот на ещё более низкий уровень битовых полей и т.п.), то возникают уже совершенно другие расклады по производительности (причём это применимо и к варианту без разбиения на массивы — там же тоже можно сделать более интересный код без optional).
Не уходим. Этот код может сделать компилятор из такого:
soa<option<int32>> values[1024*1024];
foreach (Some(value) in values)
{
//делаем что-то с value
}
Совсем фантазии никакой. Всё разжёвывать нужно.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>>>Покрой шаблонами целое неограниченного размера. V>>Ну вот я со звуком много работал. Там идёт работа с пачками отсчетов вполне конечного размера. WH>Как эти предложения вообще связаны?
Очевидно так, что для некоторых сценариев трюка эмуляции ЗТ достаточно.
В упомянутых звуковой предметной области пачки отсчетов не просто конечного размера, а строго по некоей сетке:
40 байт, 80, 120, 160, 240, 320.
И как раз трюк с отсутствием необходимости на проверку диапазона работает, потому что эта проверка "схлопывается".
WH>Ты вообще можешь связно думать? Или у тебя в голове работает рандом, который скачет куда попало.
Не хами, парниша.
Ты на удивление несообразителен порой или невнимателен — на выбор. И это раздражает, ты даже не представляешь как.
Общаешься с коллегами забыв проснуться.