typescript - Rust WebAssembly Prilagođeni elementi Pogreška oslobađanja memorije
Moj prvi WASM proizveden u Rust-u proizvodi sljedeću pogrešku, nemam pojma kako pristupiti njegovom otklanjanju.
wasm-000650c2-23:340 Uncaught RuntimeError: memory access out of bounds
at dlmalloc::dlmalloc::Dlmalloc::free::h36961b6fbcc40c05 (wasm-function[23]:670)
at __rdl_dealloc (wasm-function[367]:8)
at __rust_dealloc (wasm-function[360]:7)
at alloc::alloc::dealloc::h90df92e1f727e726 (wasm-function[146]:100)
at <alloc::alloc::Global as core::alloc::Alloc>::dealloc::h7f22ab187c7f5835 (wasm-function[194]:84)
at <alloc::raw_vec::RawVec<T, A>>::dealloc_buffer::hdce29184552be976 (wasm-function[82]:231)
at <alloc::raw_vec::RawVec<T, A> as core::ops::drop::Drop>::drop::h3910dccc175e44e6 (wasm-function[269]:38)
at core::ptr::real_drop_in_place::hd26be2408c00ce9d (wasm-function[267]:38)
at core::ptr::real_drop_in_place::h6acb013dbd13c114 (wasm-function[241]:50)
at core::ptr::real_drop_in_place::hb270ba635548ab74 (wasm-function[69]:192)
Kontekst: najnoviji Chrome, Rust wasm-bindgen kod pozvan iz TypeScript prilagođenog elementa, koji radi na canvasu u shadow DOM-u. Podaci renderirani na canvas dolaze iz HTML5 AudioBuffer-a. Sve Rust varijable imaju lokalni opseg.
Web komponenta radi savršeno ako se u dokumentu pojavljuje samo jedna instanca, ali ako se pojave daljnje instance, ispisuje se greška kao gore. Kod radi bez ikakvih drugih problema.
Znam da u Chromeu postoje nerešeni problemi s memorijom -- izgleda li ovo tako, ili mi iskusni Rust/Wasm programer može reći je li ovo neobično?
js-sys = "0.3.19"
wasm-bindgen = "0.2.42"
wee_alloc = { version = "0.4.2", optional = true }
[dependencies.web-sys]
version = "0.3.4"
Rust kod je mali i samo prikazuje dva kanala AudioBuffer-a na dodani HTMLCanvasElement:
#[wasm_bindgen]
pub fn render(
canvas: web_sys::HtmlCanvasElement,
audio_buffer: &web_sys::AudioBuffer,
stroke_style: &JsValue,
line_width: f64,
step_size: usize,
) {
// ...
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() }; // !
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
// ...
Pokušao sam komentirati funkcionalnost, i ako kod ne dira platno, ali radi gore navedeno, dobijem pogrešku. Izmjena u nastavku rezultira jednostavnom pogreškom 'out of wam memory'. Audio datoteka je 1.200 k.
let channel_data: [Vec<f32>; 2] = [
audio_buffer.get_channel_data(0).unwrap(),
audio_buffer.get_channel_data(1).unwrap()
];
UREDI: Potonja out of memory pogreška, za gornji ispravni kod, stvarno me zbunila, ali zapravo je to bug u Chromeu.
Odgovor #1
Tvoj problem je što stvaraš blok neinicijalizirane memorije i ne inicijaliziraš ga pravilno:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in 0..1 {
channel_data[channel_number] = audio_buffer
.get_channel_data(channel_number as u32) // no need for `as u32` here btw
.unwrap();
}
Rangeovi (također poznati kao a..b) su ekskluzivni u Rustu. To znači da vaš petlja ne iterira dvaput kao što biste pretpostavili, već samo jednom i imate jedan neinicializirani Vec<f32> koji će tada izazvati panic prilikom njegovog brisanja. (Molimo pogledajte odgovor Matthieu M.-a za pravilno objašnjenje)
Ovdje postoji nekoliko mogućnosti.
- Koristite odgovarajući raspon, npr.
0..2 - Upotrijebite uključivi raspon
0..=1 Nemojte koristiti nesiguran konstrukciju, nego umjesto toga
let mut channel_data: [Vec<f32>; 2] = Default::default()
Ovo će pravilno inicijalizirati dva Vec-a.
Za potpuniji pregled načina inicijalizacije polja, pogledajte Što je pravi način za inicijalizaciju polja fiksne duljine?
Usput: izbjegavajte korištenje unsafe, posebno ako ste novi u Rustu.
Odgovor #2
Ovdje su dva problema:
- Stvarate neinicializirani blok memorije i tretirate ga kao da je inicijaliziran.
- Vaša iteracija je pogrešna,
0..1iterira preko[0](to je ekskluzivno).
Pogledajmo ih jedan po jedan.
Nemojte koristiti unsafe.
Općenito, trebali biste nastojati izbjeći unsafe. Postoji vrlo malo razloga za njegovo korištenje, a postoji mnogo načina na koje ga se može pogrešno upotrijebiti (kao ovdje).
Problem.
U ovom posebnom slučaju:
let mut channel_data: [Vec<f32>; 2] = unsafe { std::mem::uninitialized() };
for channel_number in /*...*/ {
channel_data[channel_number] = /*...*/;
}
Postoje dva problema:
- Korištenje
std::mem::uninitializedje zastarjelo zbog sigurnosnih razloga; jako je loša ideja koristiti ga. Njegova zamjena jeMaybeUninitialized. - Dodjeljivanje neinicijaliziranoj memoriji je Nedefinirano ponašanje.
U Rustu ne postoji operator dodjele, kako bi se izvršila dodjela jezik će:
- Odustani od prethodne instance.
- Prepišite sada neiskorištenu memoriju.
Odbacivanje sićeg memorije koja misli da je Vec je Nedefinirano Ponašanje; u ovom slučaju vjerojatan učinak je da se pročita i oslobodi neka nasumična vrijednost pokazivača. To može izazvati pad programa, to može osloboditi nepovezani pokazivač, što može dovesti do naknadnog pada programa ili oštećenja memorije, to je LOŠE.
Rješenje.
Ovdje postoji malo razloga za korištenje unsafe:
- Savršeno je moguće sigurno izvršiti inicijalizaciju niza.
- Potpuno je moguće izravno inicijalizirati niz.
- Postoji malo koristi u izvedbi ako se ne izvrši zadana inicijalizacija ako inzistirate na dvofaznoj inicijalizaciji, jer
DefaultimplementacijaVecne dodjeljuje memoriju.
Ukratko:
auto create_channel = |channel_number: u32| {
audio_buffer
.get_channel_data(channel_number)
.unwrap()
};
let mut channel_data = [create_channel(0), create_channel(1)];
je jednostavno, sigurno i najučinkovitije.
Dajte prednost iteratorima pred indeksiranjem.
Ako inzistirate na dvoslojnoj inicijalizaciji, koristite iteratori umjesto indeksiranja kako biste izbjegli pogreške za jedan korak.
U vašem slučaju:
let mut channel_data = [vec!(), vec!()];
for (channel_number, channel) = channel_data.iter_mut().enumerate() {
*channel = audio_buffer
.get_channel_data(channel_number as u32)
.unwrap();
}
Na Iterator postoji mnogo pomoćnih funkcija, u ovom posebnom slučaju, enumerate će omotati element koji vraća iter_mut() ( &mut Vec<f32>) u tuple (usize, &mut Vec<32>):
- Imaš izravan pristup elementu, nije potrebno nikakvo izračunavanje.
- Također imate indeks elementa, bez pogrešaka pomaka za jedan.
