En Recycling Factory Pattern i Delphi

Problemene med å bruke og gjenbruke mange varierende størrelse objekter i et program kan føre til fragmentering av haugen som kan bremse ned prosesseringshastighet. Denne artikkelen bruker en fabrikk objekt, for å gjøre og resirkulere objekter med minimal fragmentering effekter. Ett av mønstrene i den klassiske boken "Design Patterns, Elements of Gjenbrukobjektorientert programvare" er fabrikken metode, hvor objekter er opprettet og jeg nylig måtte bruke det. Grunnen? Et minne fragmentering problem i et simuleringssystem ved hjelp av et stort antall gjenstander for å holde tallrekker. Det var flere numeriske array-typer, i faste størrelser som varierer fra 1500 x 1 til 5000 x 10 tall enten heltall eller dobles. Disse ble brukt i en simulering av en økonomisk beregning over en to års periode, skaper data på hver av 472 simulerte dager fra historiske data. På hver dag, ble data som leses inn fra en database, behandles deretter lagret out.Despite omfattende lekkasjekontroll, vil programvaren bare løpe i en rekke simulerte dager før spising opp Windows vekslefilen. Minnelekkasjer ble nøye fulgt ned og elimineres, men håret mitt var fortsatt i fare. Applikasjonen vil ikke kjøre lenger enn ca 30 simulerte dager før det hadde begått over halvparten av NT Øker fil- dette på en 512 Mb ram system! Lukk Undersøkelsen viste at for hver dag det gikk, vokste allokert minne fra 5M til ca 80MB deretter krympe tilbake til ca 5M igjen. På forsiden av det, ikke et problem med 512MB spille med, men ser på Win NT Task Manager viste en økende mengde minne committed.FRAGMENTATIONThe problemet var enkelt, det var heap fragmentering skylden. Dette skjer når mange forskjellige objekter er opprettet og deretter ødelagt gjentatte ganger. Som hvert objekt er opprettet, bruker den minne fra haugen. Hvis gjenstander ble opprettet og deretter ødelagt i motsatt rekkefølge det sannsynligvis ikke ville skje som alle frigjort minne kan bli slått sammen til én stor blokk. Men i alle system med et stort antall gjenstander rekkefølgen av skapelse og ødeleggelse vil aldri bli symmetrical- min app kan lett ha opptil 50.000 objekter i minnet samtidig. Så når et objekt er ødelagt, er en peker til frigjort minne blokk lagt til en gratis-blokk list.When andre forespørsler om minnet er gjort, prøver Windows å fordele disse forespørslene ut av frigjort listen først. Fragmentering skjer der en stor blokk som 1Mb har blitt forespurt og senere frigjort, etterfulgt av en anmodning om en mindre blokk. Dette er tatt fra den første blokken på gratis-listen som kan godt være det 1Mb, som etterlater bare 900Kb gratis. Så en annen forespørsel om et stort 1Mb blokk kommer sammen, det kan ikke være fornøyd fra den frie listen, og så det er tatt fra haugen. Hvis oppretting /ødeleggelse syklusen skjer nok ganger de store blokkene på haugen er hakket i mindre biter; den fysiske ram er oppbrukt og erstattet med virtuell ram. Windows heap minnehåndtering (og Delphi) er ganske flink det tar mye for å få det til fragment. Men under hardt press av et stort antall objekter blir skapt og ødelagt, minnet lederen vil gradvis hule in.When Windows går tom for ledig ram, begynner det bytte sider med ram til disk og ytelse tar et stup. Din app kan galoppere lykkelig sammen med 100% CPU før bytte starter. Det blir da en sørgemarsj krypende sammen på kanskje 7-10% av CPU, tar evigheter å kjøre. Katastrofe! Microsoft har lagt mye arbeid å lage minne lederen så effektiv som mulig. For eksempel i henhold til NT, er det en to-trinns prosess for å reservere og å binde hukommelse. Hvis applikasjonen krever 100Mb, er det reservert når programmet er lastet. Men bare når minnet er tilgjengelig er reserve sider begått. Hvis du ønsker å vite mer enn du noensinne vil trenge å vite om dette og andre emner, anbefaler jeg boken Inside NT, utgitt av Microsoft Press men få David Solomon-versjon som er den senere utgaven, ikke den Helen Custer første utgaven. FABRIKK PATTERNSo Jeg trengte en ikke fragmentering måte å skape mange objekter, bruker dem, kaste dem bort og deretter gjøre alt på nytt uten vinduer kjører ut av virtuelt minne. Har nylig lest Pattern boken jeg tenkte hvorfor ikke bruke en fabrikk, dvs. en fabrikk objekt som skaper objekter av en bestemt klasse. Jeg deretter gikk en bedre og gjorde det miljøvennlig, så det blir å resirkulere alle sine produserte gjenstander i stedet for å ødelegge dem og uten tilhørende fragmentering problemer. Glasur på kaken var å gjøre fabrikken i stand til å utvide sin kapasitet uten tap av tilgang speed.Rather enn har en fabrikk klasse for alle typer klasse, tok jeg den enklere tilnærming av passerer objektet klassen inn i fabrikken som en fabrikk skapelse parameter . Når fabrikken er opprettet du oppgi både klasse av gjenstander det kan gjøre og den første lagringskapasitet på fabrikken. Denne størrelsen kan endres oppover ved å ringe GrowFactory metoden. Jeg foreslår at du bare kalle dette i eksepsjonell (!) Circumstances.The link tilbake til fabrikken fra hvert objekt er nødvendig, slik at alle "fabrikken gjort objekter" (FMOs) må stige ned fra en TFactoryObject klasse i stedet for TObject. Dette legger en fabrikk referanse som er "stemplet" på alle FMOs slik at objektet vet hvilke fabrikk for å bruke til å resirkulere itself.Instead med å lage et objekt appen ber om en fra egnet fabrikken ved å ringe sin RequestObj metode som returnerer en tclass objekt tilbake og du konvertere den til riktig klasse ved hjelp av 'som'. Til slutt når du er ferdig med å bruke objektet du bare ringe sin RecycleSelf metoden. Ingen etablering eller ødeleggelse unntatt av fabrikkene themselves.HOW Det fungerer når fabrikken er opprettet, er alle objektene fysisk opprettet i én sammenhengende blokk med ram. En tListe (fblocklist) objekt holder adressen for hver av disse blokkene. Hver gang du vokser fabrikken, er en ny blokk opprettes og legges til denne listen. Metoden AddObjects skaper det angitte antallet objekter ved hjelp av ram fra blokken. Hvis du skriver kode som dette være klar over at bare gjør en TObject (adresse) er ikke nok til å opprette objektet. Du må alltid ringe ObjectClass.InitInstance (adresse) for å forvandle det til en "ordentlig" objekt. InitInstance sletter alt til null, null osv, men enda viktigere det setter opp fabrikken VMT.The inneholder også en annen tListe (ffreelist) som holder adressen til hver ubrukt object.All av esel arbeidet med å fylle fabrikken er gjort i privat metode AddObjects. For hvert objekt laget i blokken, omdanner denne pekeren ptr til et objekt, ved bruk av FactoryObject som klassen av objektet opprettet. Dette må alltid være en etterkommer av TfactoryObject. Obj holder objekt referanse og lenker fabrikken til produsert objekt. ptr blir så inkrementert for å peke på det neste objektet i blokken ved å tilsette fsize.To får man en gjenstand r kodeanrop Request_Obj som spretter referansen utenfor enden av ffreelist og returnerer det som den første ønskede objektet. Gjenvinning er det motsatte; den presser den resirkulerte objekt referanse på enden av ffreelist. En ting å huske på. Når en fabrikk laget objekt er i bruk, vil ikke fabrikken har ikke noen referanse til det, selv om minnet okkupert av objektet befinner seg inne i fabrikken! BYTTE SKAPE OG DESTROYUsing fabrikken krever at du bruker produserte gjenstander litt annerledes enn normalt. Du trenger ikke lenger opprette eller frigjøre dem eksplisitt, i stedet du bare be den aktuelle fabrikken for objektet. Med mindre fabrikken er tom dette vil alltid fungere. Du må endre initialisering kode i konstruktør og oppsigelse kode i destructor rutiner. Det er to approaches.1) Hvis objektet har et enkelt lage konstruktør uten parametere, kan du endre navnet til prosedyren init, overstyring og fjern eventuell arvet skape samtaler. Fabrikken kaller alltid en init-metoden når et objekt blir forespurt. Som standard er dette ikke gjør noe, men du kan overstyre dette, slik at Init vil bli kalt automatisk på hvert objekt spurt fra factory.2) Hvis originalen opprette har parametre, endre navnet til noe som Initialise og fjerne arve etc samtaler. Etter at objektet er bedt om samtale Initialise rutine, f.eks MyRoutine.Initialise (...) Hvis objektet har destructor kode, endre navnet til inngrepet gjort, overstyring, slik at det heter automatisk når objektet blir resirkulert. Init og Done er lik opprette /ødelegge, men uten bagasje av konstruksjonen eller ødeleggelse mechanism.As en liten digresjon, jeg forstår at det er argumenter i Delphi verden til fordel for den ene delen opprettelse eller todelt ene delen er der konstruktøren har parametre og fullstendig setter opp gjenstanden. I det todelte tilnærming, konstruktør skaper bare en blank gjenstand som deretter blir initialisert ved en senere fremgangsmåte. Fabrikken tilnærmingen jeg tror er fast i den todelte camp..When fabrikken er ødelagt, alle de tildelte minneblokker blir frigjort. Før dette, sjekker fabrikken at antall objekter i freelist matcher kapasitet. Hvis du har glemt å resirkulere alle gjenværende objekter, vil det heve en exception.COMPARISON PROGRAMThis viser fordelene ved fabrikkene .. På jobben min app hatt mange forskjellige størrelser av objekter i ulike mengder, men en 15.000 linje programmet ville ikke akkurat være publiserbare . Etter litt prøving og feiling kom jeg opp med et kort program som kan vise fragmentering. Men dette avhenger av størrelsen på de ulike stedene, hvor mange det er, tilgjengelig ram og hvor lenge det er kjørt. Også synes fragmentering å skje raskere på NT4.0 enn 98 som antyder at kanskje 98 har en bedre hukommelse manager. Når du kjører det skaper og ødelegger et stort antall gjenstander gjentatte ganger for det angitte antall dager. Det gjør også akkurat det samme ved hjelp av en fabrikk. Hver dag er tidsbestemt, og begge sett av ganger plottet ved hjelp Tchart.I brukt tre typer objekter, alt stammer fra tTestobj som stammer fra Tfactoryobject. Små gjenstander er 2K i størrelse, medium er 40K og store er 800K, men disse størrelsene er definert av konstanter og kan enkelt endres. Demoen programmet tildeler 100Mb for de vanlige gjenstander og annen 100Mb for fabrikken. Begge inneholder det samme antall objects- delt likt av størrelse mellom de tre objekttyper, slik det er 17406 små gjenstander, 873 medium og 43 store. I objektet skapelsen prosedyre TimeOneDay disse er tilfeldig opprettet og lagt til listen. På slutten av dagen blir de frigjort og prosessen gjentas neste day.The samme er gjort ved hjelp av tre fabrikker med alle produserte gjenstander som er lagt til en factorydata liste. På en P2 400 med 256 MB kjører i 100 dager, var det en beskjeden økning i tid for både fabrikken og normal objekt skapelse. Kommenterer ut normal etableringen bekreftet dette suspicion- økningen var mye mindre over et tusen dag løp bruker fabrikker mot 100 dager med både normal og fabrikk testing. Jeg gjettet at dette var forårsaket av økt siden bytte på grunn av fragmentering påvirker begge prosessene. Noen andre kombinasjoner av størrelser og antall objekter under Windows 98 viste ingen fragmentationI implementert dette opprinnelig å bruke en tListe for både testdata og factorydata og endret det senere når jeg innså at gjentatte ganger å legge til og frigjøre 18000 pekere ble også legge til heap fragmentering. Jeg er ikke taler for nødlanding tstringlists helt i Kode de er svært nyttig (som er tlists), men hvis du har til å manipulere et stort antall elementer, kan det være bedre å bruke dine egne listestrukturer. Hvis du ønsker å bruke tListe eller Tstringlist for dette formålet, er det trolig bedre å fylle strukturen med null pekere, slik at count = kapasitet og bruke et heltall å holde indeksen for den siste pointer.This app også bekreftet at fabrikken kode er veldig rask på tildeling og deletion- typisk 30 ms for 18000 gjenstander i stedet for 870 ms som normal oppretting /sletting tok .------------------------ ------------------------ tgis artikkelen opprinnelig dukket opp i Delphi Developer magazine. Sources.zip



Previous:
Next Page: