Cél és előfeltétel
A memóriahasználat javítása gyakran az adatok életének megértésével kezdődik. Ebben a leckében nem allokátort írunk: a meglévő Vec és String felületein keresztül vizsgáljuk a hossz, a kapacitás és az újrahasználat kapcsolatát. Előfeltétel a költségmodell, valamint a String gyakorlati használata. A példák külön edition 2024 programok, külső függőség nélkül.
A hossz az érvényes elemek száma. A kapacitás azt jelzi, mennyi elem számára van hely újabb foglalás nélkül; Stringnél bájtban értjük. A kettőt nem szabad összekeverni. Egy előre lefoglalt, de üres vektorban még nincsenek olvasható elemek. A lefoglalt hely nem jogosít fel indexelésre, és a biztonságos megoldás továbbra is push vagy más szabályos elemhozzáadás.
A fölösleges foglalás és a fölösleges tartalékolás
Ha előre ismered a várható elemszámot, egy kapacitásmegadás csökkentheti a növekedés közbeni újrafoglalások szükségességét. Ez azonban munkahipotézis, nem általános gyorsulási bizonyíték. A foglalásnak is költsége van, a túl nagy becslés pedig sokáig megtarthat kihasználatlan memóriát. Gondold végig azt is, hogy a puffer egy kérésig, egy feldolgozási adagig vagy a teljes folyamat életéig él.
A clear eldobja az érvényes tartalmat, de a Vec és String használható kapacitását megtartja. Ez jó lehet egy ismételt feldolgozási ciklusban. Ettől még a korábbi elemek erőforrásai felszabadulnak, és egy titkos adat bájtjainak biztonságos törlését sem ígérheted pusztán ezzel. A logikai üresség, a visszatartott foglalás és a memória felülírása három külön kérdés.
1. feladat: előfoglalás helyes elvárásokkal
Készíts négy elemre előkészített vektort. Ellenőrizd, hogy kezdetben üres, és négy push után pontosan négy érvényes eleme van. A kapacitásra csak az API által megengedett alsó korlátot állítsd, ne egy konkrét allokátoron tapasztalt pontos értéket követelj meg.
Megoldás
fn main() {
let mut values: Vec<u32> = Vec::with_capacity(4);
assert!(values.is_empty());
assert!(values.capacity() >= 4);
assert_eq!(values.get(0), None);
for n in 0..4 { values.push(n); }
assert_eq!(values, vec![0, 1, 2, 3]);
assert_eq!(values.len(), 4);
assert!(values.capacity() >= values.len());
}
A kapacitás négy vagy nagyobb lehet. A teszt a garantált kapcsolatot vizsgálja, így nem kötődik egy célgép növekedési stratégiájához. A kezdeti get nulla indexre is hiányt ad: az üres gyűjteménynek a foglalástól nem lesz tartalma. Érdemes ezt saját szavaiddal megmagyaráznod, mielőtt további optimalizálást keresel; sok hibás következtetés a hossz és a rendelkezésre álló hely összekeveréséből származik.
2. feladat: használd újra a formázó puffert
Készíts két rövid jelentést ugyanazzal a Stringgel. Az első eredményt ellenőrizd, majd clear után készíts másodikat. Közvetlenül a pufferbe formázz, és ne tarts meg az első tartalomra mutató referenciát a módosítás alatt. A teszt a tartalomra és a clear kapacitásmegőrzésére vonatkozzon.
Megoldás
use std::fmt::Write;
fn main() {
let mut buffer = String::with_capacity(64);
write!(&mut buffer, "sorok={}", 12).unwrap();
assert_eq!(buffer, "sorok=12");
let capacity = buffer.capacity();
buffer.clear();
assert!(buffer.is_empty());
assert_eq!(buffer.capacity(), capacity);
write!(&mut buffer, "hibak={}", 3).unwrap();
assert_eq!(buffer, "hibak=3");
}
A write itt a fmt::Write traitet használja String célra, nem fájlt ír. A formázás emiatt nem készít külön format! eredményt, amelyet utána másolnánk a pufferbe. Ez egy konkrét köztes szöveg elkerülését mutatja, nem az összes lehetséges foglalás számát bizonyítja. A kapacitás vizsgálata önmagában nem allokátorprofil: egy művelet más belső struktúrája is foglalhat memóriát.
3. feladat: kezeld az érvénytelen tartalékolást
Próbálj olyan kapacitást kérni, amely u64 elemekből nem ábrázolható. A try_reserve hibáját kezeld eredményként, és ellenőrizd, hogy az eredeti elem megmarad. Szándékosan kapacitástúlcsordulást használunk, nem a gép szabad memóriáját próbáljuk elfogyasztani. Ne írj valós memóriahiány előidézésére támaszkodó tesztet.
Megoldás
fn main() {
let mut values = vec![42_u64];
let outcome = values.try_reserve(usize::MAX);
assert!(outcome.is_err());
assert_eq!(values, vec![42]);
assert_eq!(values.len(), 1);
}
A try_reserve további elemszámot kér a jelenlegi hosszhoz képest, nem végső bájtméretet. A hatalmas kérés itt aritmetikai vagy kapacitáskorlát miatt elutasítható. Nem tekintjük a sikertelen tartalékolást részleges adatfeldolgozásnak, és nem folytatjuk az írást úgy, mintha a kért hely rendelkezésre állna. Az üzleti kód ilyenkor választhat kisebb adagot, hibajelzést vagy másik feldolgozási tervet.
Milyen adatot vigyél tovább a döntéshez?
Egy valódi rendszerben jegyezd fel az átlagos és legnagyobb pufferhosszt, a megtartás időtartamát és az újrahasználat gyakoriságát. Egy ritka óriási kérés után hosszú ideig élő buffer sok memóriát tarthat meg. Ezért a „soha ne engedd el” ugyanúgy rossz általános szabály, mint a „minden sor után hozz létre újat”. Előbb nevezd meg a korlátot, és csak aztán válassz újrahasználási politikát.
A shrink_to_fit kérés a kapacitás csökkentésére, nem hordozható ígéret a folyamat rezidens memóriájának azonnali visszaesésére. A következő mérésben a teljes működési mintát figyeld: egyszeri indítás és folyamatos terhelés más kérdést válaszol meg. Az érvényes adatok megőrzése minden esetben erősebb követelmény marad a kisebb kijelzett memóriaértéknél.
Elfogadási feltételek
- Az előfoglalás nem keveredik a hossz fogalmával, a teszt nem követel pontos, célgépfüggő kapacitást.
- A clear után a tartalom üres, a kapacitás megtartott, a következő jelentés helyes.
- A kapacitástúlcsordulás hibát ad és változatlan adatot hagy maga után, tényleges memóriaelfogyasztás nélkül.
Összefoglaló és továbblépés
A legfontosabb kérdés a foglalás tulajdonosa és élettartama. A felesleges másolat elkerülése néha többet ér az előfoglalás finomhangolásánál. A Cow és kölcsönzött adatok leckében olyan felületet készítünk, amely csak módosításkor hoz létre saját szöveget.