LINUX.ORG.RU

Rust и создание больших массивов

 ,


3

6

Вот этот очень простой код потенциально легко вызовет stack overflow (если нет, то надо просто увеличить 16777216), хотя не должен (мы ведь на самом деле выделяем место в куче в итоге).

#[derive(Copy, Clone)]
pub struct Item {
    a: i32,
    b: i32
}

pub struct Items {
    items: [Item; 16777216]
}

impl Items {
    pub fn new() -> Items {
        Items {
            items: [Item { a: 0, b: 1 }; 16777216]
        }
    }
}

fn foo() -> Box<Items> {
    Box::new(Items::new())
}

Пруф: https://rust.godbolt.org/z/8sWsoKojx

Для Ъ: Массив сначала создаётся на стеке, а только потом выделяется память и происходит memcpy в кучу. Максимальные оптимизации не спасут.

К тому же не забываем про оверхед копирования. Я готов поверить разработчикам Rust, что копирование при создании структур из нескольких сотен байт не создаст заметной просадки, но я верю, что пример выше будет статистически значимо работать медленнее уже на нескольких тысячах элементов, а не обязательно 16 миллионов.

Как принято создавать в Rust такие массивы? unsafe или есть решения получше?

Мне нравится Rust последнее время, сколько я к нему присматриваюсь, но вот такая очевидная мелочь как copy elision не предусмотрена для типа системного языка... Или я просто всё делаю не так и Items::new надо писать как-то иначе?

★★★★★

Последнее исправление: KivApple (всего исправлений: 2)

Ответ на: комментарий от technic93

в деструкторе если ты обнули rhs.begin но не обнулил rhs.current…

Там два поста моих было, в первом код неверный - в присвоении что-то подобное воспроизвести не получится скорее всего. Код вот этот смотри, там где push_back падает на moved-out объекте. current должен зануляться anyway, иначе деструктор не сработает по нормальному, да.

Я тебе и говорю, что это странная аргументация. По умолчанию много чего нельзя дёргать, безотносительно мува.

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

Стандарт требует деструктор, потому что он будет его дёргать. А какой-то метод какого-то класса - ты сам решаешь можно его дергать или нет.

Ну да. Только сам решаешь - в смысле при написании класса решаешь, а не при использовании. При использовании уже не решаешь. Не обеспечили поддержку use-after-move - всё, ничего не поделаешь, только сам класс доделывать.

Типа обычно предполагается что нету смысла дергать что либо после мува, отсюда и соответствующие линты в clang/студии.

Да, в идеальном случае там вообще prvalue будет и ты просто синтаксически его дёрнуть не сможешь. Типа

f(vec<T>{});
// здесь vec уже нет
//f(vec<T>{}.push_back(T{}));  // так ты не вектор в f() отдашь, а decltype(push_back(T{})) - очевидно, это не то
anonymous
()
Ответ на: комментарий от anonymous

Ну да. Только сам решаешь - в смысле при написании класса решаешь, а не при использовании. При использовании уже не решаешь. Не обеспечили поддержку use-after-move - всё, ничего не поделаешь, только сам класс доделывать.

Нелепая херня. Никакого use-after-move на уровне языка не существует, как и не существует никакого move. Это первое - ты уже обгадился.

Далее, если там у тебя появилось какое-то неправильно состояние, о котором ты слышал где-то в школе, то это ничего не меняет. Такое состояние это свойство семантики метода. И такие же «проблемы» могут быть что в copy, что в push и прочей херне.

Чего у тебя нет проблемы с use-after-push, если push можно реализовать как угодно и он может ломать что угодно?

anonymous
()
Ответ на: комментарий от anonymous

Хм, позор сцарька снесли. Наводит на размышления.

Ну так что, сцарёк, ты мне скажешь ещё что-нибудь про use-after-push, или там про общее/частное? Я тебе и так фору дал, а ты всё халтуришь.

anonymous
()
Ответ на: комментарий от technic93

Очнись! в расте с момента 1.0 добавили дофига всего очень важного, даже не говоря об асинках.

Но не настолько важного, и что касалось бы базовой функциональности. Критиков послушать, так без возможности наколхозить структурку в пару мегабайт вообще кушать не могут. Я вижу РФЦ в смысле «да, наверное надо бы что-то придумать, хотя можно и потом, лет через 5-10».

Каким сишникам, алё? Если я знаю размер заранее то записать его в константу компиляции очевидно лучше.

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

Вообще, давай для разнообразия мозг включим. Когда не следует выносить массив за пределы структуры? Когда он мал. Накладные расходы на выделение в хипе могут быть слишком велики по сравнению с самим массивом. И тем более не следует выносить в случае, когда структура с массивом вместе помещается в строку-две кэша CPU, тогда использование вектора вообще было бы вредительством. Но при таких условиях описанной топикстартером проблемы не существует как класса. Проблема начинается, когда размер становится очень большим. Если массив занимает уже несколько килобайт или тем более десятки килобайт, то смысл его внедрять теряется, пофиг на дополнительный уровень косвенности, один хрен при обращении будет промах по кэшу и поток будет ждать. Но задач, при которых размер велик и при этом фиксирован на стадии компиляции не так много, обычно это большой блок неограниченного размера или блок размером от 0 до N. В таких случаях огульно выделять N не хочется. Поэтому если уж делать внедрение больших массивов в структуры реально рабочими, то делать надо в сишном стиле в виде (X, [T]), чтоб при создании в рантайме размер [T] задавался. Кстати, сделать это не сложнее чем просто [T;N]. Нужен аналог конструктора, а механизмы работы с [T] в Расте уже есть, всё что нужно - это вычислить размер выделяемой памяти, сохранить N в скрытом поле и сделать операцию заимствования поля типа [T] из структуры (N,[T;N]) в &[T], который вроде как 2мя указателями реализован.

khrundel ★★★★
()
Ответ на: комментарий от anonymous

Можно разъяснить причину веселья?

В опасном С++ в Debug режиме будут проверяться выходы за границы у массивов, векторов, слайсов и всех контейнеров в методе, который не проверяет выходы за границы.

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

Вот для сравнения:

https://godbolt.org/z/c3d3vbn58

https://godbolt.org/z/dj3de8fv6

https://godbolt.org/z/jqzbvnaq5

https://godbolt.org/z/rdTevh1d3

Безопасный Rust не захотел добавить проверок в Debug, а лишь написал комментарий:

// SAFETY: the caller guarantees that `slice` is not dangling, so it
// cannot be longer than `isize::MAX`. They also guarantee that
// `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
// so the call to `add` is safe.
fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 4)
Ответ на: комментарий от fsb4000

Как по мне, все подобные комментарии нужно проверить и добавить Debug Assert, так как почти везде в безопасном Rust лишь комментарии в отличии от С++, который в стандартной библиотеке в Debug старается поймать все нарушения инвариантов, а не надеется на пользователя

https://github.com/rust-lang/rust/search?p=4&q=%22%2F%2F+SAFETY%3A+the+caller%22

Исправить примерно так:

https://github.com/rust-lang/rust/blob/673d0db5e393e9c64897005b470bfeb6d5aec61b/library/core/src/char/convert.rs#L92-L95

fsb4000 ★★★★★
()
Последнее исправление: fsb4000 (всего исправлений: 2)
Ответ на: комментарий от fsb4000

Не слишком убедительно, хотя возможно и такие ассерты в раст и не помешали бы, а неубедительно потому, что в c++ это основной метод который используется для доступа к вектору, тот же метод at я в реальном коде вообще практически не видел. В расте же эти методы которые ты показал unsafe и должны использоваться только в исключительных случаях и при использовании программист должен сам представить гарантии что это безопасно. А самые часто используемые методы доступа безопасны и даже в релизе в них остаются все проверки.

anonymous
()
Ответ на: комментарий от khrundel

Если бы это было важно, добавили бы до 1.0.

Безотносительно всего остального, это так себе аргумент. 1.0 старались сделать побыстрее и многое, что можно было отложить, выкидывали. Взять хотя бы дерайв-атрибуты, без которых значительная часть библиотек жить не может.

DarkEld3r ★★★★★
()
Ответ на: комментарий от fsb4000

Как по мне, все подобные комментарии нужно проверить и добавить Debug Assert, так как почти везде в безопасном Rust лишь комментарии в отличии от С++, который в стандартной библиотеке в Debug старается поймать все нарушения инвариантов, а не надеется на пользователя

В Расте не комментарии, а слово ансейф, которое заставляет прочитать комментарий и намекает где искать проблемы. Отладочные ассерты как бы решение не лучше, программист запустит, протыкается, вроде работает, затем соберёт релизную версию и в продакшен.

khrundel ★★★★
()
Ответ на: комментарий от DarkEld3r

Безотносительно всего остального, это так себе аргумент. 1.0 старались сделать побыстрее и многое, что можно было отложить,

Ну как бы всё стараются сделать побыстрее. Насколько ж важна была эта фича, если «побыстрее» победило?

Ну и с 1.0 уже сколько лет прошло? Столько всякого надобавляли, уже и новый профиль Раста замутили, а эта фича всё в разделе «может сделаем когда-нибудь».

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Хотел сказать только, что «не попало в 1.0» - не аргумент.

Столько всякого надобавляли, уже и новый профиль Раста замутили, а эта фича всё в разделе «может сделаем когда-нибудь».

С другой стороны, и не закрыли как «ненужное». Фича ведь действительно бывает полезна.

К разговору о «сделаем когда-нибудь» - есть баги с тегом «unsound», которые аж 2015 годом датируются. Очевидно, что жить с ними можно, но хорошего тут мало.

DarkEld3r ★★★★★
()
Ответ на: комментарий от DarkEld3r

С другой стороны, и не закрыли как «ненужное». Фича ведь действительно бывает полезна.

Теоретически конечно может. Какая-нибудь POD-структура извне, забитая подзавязку полями, которую через libc’шный read заполняют. Хотя такое скорее через отображение в память и transmute на месте.

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

khrundel ★★★★
()
Ответ на: комментарий от khrundel

Жертва пропаганды, насколько я понял - если в 1.0 фейкстатик не починили, то это фича? Как там с методичкой, всё нормально?

anonymous
()
Ответ на: комментарий от anonymous

Не слишком убедительно, хотя возможно и такие ассерты в раст и не помешали бы, а неубедительно потому, что в c++ это основной метод который используется для доступа к вектору,

Чини методичку.

тот же метод at я в реальном коде вообще практически не видел.

Твоя проблема, и да, жертва пропаганды, вектор это массив, а массив - это RA. Если твоя скриптуха не имеет в RA, то это не означает, что каким-то образом что-то там стало другим.

В расте же эти методы которые ты показал unsafe и должны использоваться только в исключительных случаях

Правда что-ли? А чего же везде и всюду оно используется, поэтому как это основное назначение массива?

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

Эти маня-фантазии. Какие там гарантии предоставил весь тот ворованный рантайм, компиляторы и 99% кода(который ансейф) в любой растолибе чуть посложнее хеловорлда?

А самые часто используемые методы доступа безопасны и даже в релизе в них остаются все проверки.

Ога, массив который не массив и зерокост, который не зерокост.

anonymous
()
Ответ на: комментарий от anonymous

чуть посложнее хеловорлда?

А почему ты позволяешь себе рассуждать о чём-то сложнее хеловорлда, в то время как сам ни к чему такому отношения не имеешь? Или ты что-нибудь кроме хеловорлдов писал?

anonymous
()

В любом C# можно это сделать во первых написав куда меньше кода, во вторых, без всяких там дополнительных indirection.

Но щас конечно, набегут и скажут что C# НЕБЕЗОПАСНЫЙ язык.

«Ненастоящий шотландец», да.

Секта как есть.

lovesan ★★
()
Ответ на: комментарий от lovesan

Не настораживает, что для someArray не указан размер? Хинт: лишняя индирекция.

Аналог в расте:

struct SomeStruct {
  some_boxed_slice: Box<[i32]>,
}
red75prim ★★★
()
Последнее исправление: red75prim (всего исправлений: 2)
Ответ на: комментарий от red75prim

Почитай как устроены массивы в C#. Они там фиксированного размера. Соответственно там прямо указатель на данные фактически. Напомню что мы хотим выделить место именно в куче.

Если бы был List<T> - там другое дело.

Если хочется прям инлайна массива в структуре, и это прям на стеке - вон:

    unsafe struct MyStruct
    {
        public fixed int someArray[123];
    }

Но тут сам себе буратино, т.к. как раз может случиться stack overflow. (если мы структуру на стеке инициализируем, конечно, а если в другом объекте который в куче - конечно нет)

lovesan ★★
()
Последнее исправление: lovesan (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.