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

  1. Az előfoglalás nem keveredik a hossz fogalmával, a teszt nem követel pontos, célgépfüggő kapacitást.
  2. A clear után a tartalom üres, a kapacitás megtartott, a következő jelentés helyes.
  3. 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.