Kirjautuminen

Haku

Tehtävät

Keskustelu: Ohjelmointikysymykset: VB6: ReDim ongelma(1)

Sivun loppuun

Freeze [07.01.2011 00:52:40]

#

Hei!

Olen seuraavanlaisen ongelman äärellä.
Leikitään, että ohjelma alkaa näin:

Dim ongelma() as Custom
'-
Private Sub Form_Load()
  ReDim ongelma(2)
  ongelma(0).data = 1 'merkintä 1
  ongelma(1).data = 1 'merkintä 2
  ongelma(2).data = 1 'merkintä 3
  'vapaat paikat täynnä.. joten nyt haluaisin kernaasti lisää ongelmia!
  ReDim ongelma(5)
  '...
End Sub"

ReDim on ollut armelias, ja pyyhkinyt pois kolme äsken luomaani merkintää. Näin ei kuitenkaan saisi tapahtua, kun muuttujan "paikkoja lisätään". Onko mahdollista muuttaa ko. ongelman kokoa, ilman että sinne aiemmin tallennetut arvot pyyhkiytyy pois?

Antti Laaksonen [07.01.2011 00:57:20]

#

Avainsana Preserve auttaa asiaan:

ReDim Preserve ongelma(5)

Freeze [07.01.2011 01:06:14]

#

On se helppoa kun osaa.. kiitos! :)

Merri [07.01.2011 06:59:37]

#

Kannattaa muistaa, että jos ReDim Preserviä tekee jatkuvasti, niin lähes joka kerran kaikki vanhatkin tiedot kopioidaan uuteen paikkaan muistissa. Esimerkiksi jatkuva yhden elementin lisääminen aiheuttaa runsaasti tarpeetonta muistin ja prosessorin läträämistä.

Hyvä keino ulos tämmöisestä ongelmasta on käyttää apumuuttujaa, joka muistaa monesko taulukon elementti on seuraava käyttökelpoinen. Mikäli tämän apumuuttujan arvo menee ylitse UBoundin, niin sitten taulukon koon voi esimerkiksi kaksinkertaistaa. Näin vältetään jatkuva ReDim Preserve.

Freeze [07.01.2011 14:39:35]

#

Kiitos vinkistä, Merri!

Päätin käyttää myös mahdolliset vapautuneet ongelmat uudelleen, joka loi helpon ratkaisun myös muistiläträykseen..

Type Custom
  State as single
  Data as single
End type

Se onnistuu helposti ujuttamalla jokaisen ongelman sisään tilatieto 'State', ja samalla kun paikka täytetään, 'State' muutetaan arvoon "1".

Ja kun kaikki ongelmat käydään läpi, selviää onko vapaana olevia paikkoja. Jos ei ole, onkin syytä tuplata muuttuja... :)

Metabolix [07.01.2011 14:46:08]

#

Totuusarvolle oikea ja järkevä tyyppi on Boolean eikä suinkaan Single.

Jos taulukon järjestyksellä ei ole merkitystä, yksinkertaisempi (ja muistia säästävä) ratkaisu on poiston yhteydessä siirtää vain viimeinen vapautuneeseen kohtaan:

1 2 3 4 5 6 7 8
poistetaan 4:
1 2 3 8 5 6 7

Tällöin kirjanpitoon riittää tuo aiemmin mainittu laskuri ja lisääminenkin onnistuu helposti ilman turhia silmukoita.

neau33 [08.01.2011 15:03:58]

#

Moikka Freeze!

jutskan voi hoitaa myös esim. siten, että kasvattaa taulukkoa ainoastaan silloin, kun sinne pukataan dataa ja poistaa mahdollisen vanhan kaman muistista swappaamalla käyttäen aputaulukkoa...

'Module1
Public Type OlioType
    data As Integer
End Type

Public Olio() As OlioType
'Form1
Private Sub Command1_Click()

    Dim index As Integer
    On Error Resume Next
    index = UBound(Olio)
    If Err <> 0 Then
        Err.Clear
        ReDim Olio(0)
        On Error GoTo 0
    Else
        ReDim ApuOlio(UBound(Olio) + 1) As OlioType
        ReDim Preserve Olio(UBound(Olio) + 1)
        ApuOlio = Olio: Erase Olio
        Olio = ApuOlio: Erase ApuOlio
    End If

    'Testi:
    Static i As Integer, j As Integer
    i = i + 1
    Olio(UBound(Olio)).data = i

    For j = LBound(Olio) To UBound(Olio)
        MsgBox Olio(j).data
    Next j

End Sub

Merri [08.01.2011 15:32:36]

#

Tuo on turhaa, koska jo Preserve hoitaa homman kotiin. Swappaaminen tuolla tavoin ei tee muuta kuin kopioi ja poistaa samaa tietoa monta kertaa täysin turhaan:

' varaa ApuOliolle tilaa, varattu muistialue nollataan
ReDim ApuOlio(UBound(Olio) + 1) As OlioType
' varaa Oliolle tilaa, varattu muistialue nollataan, kopioi vanha tieto uuteen muistialueeseen, vapauta vanha muistialue
ReDim Preserve Olio(UBound(Olio) + 1)
' vapauta ApuOlion juuri varattu muistialue, varaa uusi muistialue ja kopioi Olion tiedot sinne, vapauta Olion muistialue
ApuOlio = Olio: Erase Olio
' varaa Oliolle jälleen uusi muistialue, kopioi ApuOlion tiedot sinne, vapauta ApuOlion muistialue
Olio = ApuOlio: Erase ApuOlio

Eli koodi, joka tekee paljon, mutta ei edistä asioita millään lailla.


Tässä sama idea siistittynä versiona:

Option Explicit

Private Type OlioType
    data As Long
End Type

Private Olio() As OlioType

Private Sub Command1_Click()

    Dim i As Long, initialized As Boolean, newindex As Long

    ' tarkista onko Olio määritelty, plus korjaa IDE:ssä oleva Not-bugi kutsumalla hInstancea (tämä jostain syystä korjaa bugin)
    ' (btw: Not Not Olio palauttaa 32-bit pointer-arvon taulukkomuuttujan safe array -rakennetietoihin, jos 0 niin taulukkoa ei ole määritelty ja mm. UBound heittäisi herjaa)
    initialized = Not Not Olio
    Debug.Assert App.hInstance

    ' newindex = 0 jos Olio ei määritelty tai > 0 jos määritelty
    If initialized Then newindex = UBound(Olio) + 1

    ' varaa uusi paikka (tiedon sijainti muistissa saattaa vaihtua)
    ReDim Preserve Olio(newindex)

    ' laita järjestysluku
    Olio(newindex).data = newindex + 1

    ' heivaa tiedot ulos
    Debug.Print "Olion sisältö:"
    For i = 0 To UBound(Olio)
        Debug.Print "Olio(" & i & ") = ", Olio(i).data
    Next i
End Sub

Tämä yllä oleva esimerkki siis lisää yhden elementin kerrallaan taulukon loppuun, mutta mitä suuremmaksi taulukko tulee, sitä raskaammaksi sen koon muuttaminen tulee.


Tässä vielä lisäyksenä neljännessä viestissä mainitsemani apumuuttujan käyttö:

Option Explicit

Private Type OlioType
    data As Long
End Type

Private Olio() As OlioType
Private OlioMaara As Long

Private Sub Command1_Click()

    Dim i As Long, initialized As Boolean

    initialized = Not Not Olio
    Debug.Assert App.hInstance

    ' tässä esimerkissä Preserve kutsutaan joka 16. elementin lisäämisen jälkeen
    ' vaihtoehtoisesti kaksinkertaistus: ReDim Preserve Olio(UBound(Olio) * 2 + 1)
    If initialized Then
        OlioMaara = OlioMaara + 1
        If OlioMaara > UBound(Olio) Then ReDim Preserve Olio(UBound(Olio) + 16)
    Else
        ReDim Olio(15)
    End If

    Olio(OlioMaara).data = OlioMaara + 1

    Debug.Print "Olion sisältö:"
    For i = 0 To UBound(Olio)
        Debug.Print "Olio(" & i & ") = ", Olio(i).data
    Next i
End Sub

Tässä on edelleen sama ongelma taulukon koon kanssa, mutta sitä sentään muutetaan vain joka 16:lla kutsulla.

neau33 [08.01.2011 15:46:44]

#

NO MORJENS!

Merri kirjoitti:

(Merrin koko ensimmäinen viesti)

Merri kirjoitti:

(Merrin koko toinen viesti)

-Nea-

(Mod. huom: vähän järkeä nyt peliin noiden lainausten kanssa!)

Merri [08.01.2011 15:48:50]

#

Koodisi ei poista "vanhaa kamaa muistista", toisin kuin väität.

neau33 [08.01.2011 16:04:30]

#

Moikka taas!

Merri@
Testaa: Kaiva jostain romulasta vanha 286, rakentele QBasic:llä (DRAW) graafinen viisarikello & kokeile loppuuko muisti vaiko eikö ilman edellä esittämääni swappaus systeemiä...

Merri [08.01.2011 16:13:17]

#

En minä mitään ole muistin loppumisesta mitään sanonut. Ehdottamasi swappaus-systeemi ei vaan tee yhtään mitään hyödyllistä.

http://merri.net/vb6/Taulukon_kasvatus.zip

Tämä linkitetty tiedosto sisältää projektin, jolla voit katsoa miten hidasta on täyttää 10000 elementtiä byte-taulukkoon ehdottamallasi menetelmällä suhteessa sittemmin esittelemääni menetelmään. Lopputulos on täsmälleen sama. Vauhti on aikalailla eri.

Liitetyssä paketissa on myös exe, sen ajaminen on omalla vastuulla.

jtha [08.01.2011 16:51:30]

#

Hei, Freeze.

Saanko udella miksi et tee kerralla riittävän suurta taulukkoa, jotta vältytään lisäämistarpeelta? Loppuuko muisti :-)

btw: jos haluat säästää muistia määrittele 'state':n tyypiksi byte (ei single tai boolean - boolean vie kokemukseni mukaan 16 bittiä!!!)

Antti Laaksonen [08.01.2011 17:00:45]

#

neau33 kirjoitti:

Testaa: Kaiva jostain romulasta vanha 286, rakentele QBasic:llä (DRAW) graafinen viisarikello & kokeile loppuuko muisti vaiko eikö ilman edellä esittämääni swappaus systeemiä...

Miten viisarikellon tekeminen liittyy taulukon kasvattamiseen?

neau33 [08.01.2011 18:32:13]

#

MOI!

viisarikellon teko ei välttämättä liity mitenkään taulukon kasvattamiseen mutta Microsoft ei ole luonut ainuttakaan Windows-käyttöjärjestelmän versiota, joka ei hyödyntäisi swappaamista (kykenisi toimimaan ilman swappaamista)...eli onko ao. lafka päätynyt viritelmissään jatkuvasti täysin hyödyttömiin rakennelmiin..?

Grez [08.01.2011 18:35:37]

#

Jos puhut käyttöjärjestelmän harjoittamasta "swappaamisesta", niin sillä tarkoitetaan tiedon siirtämistä tietyntyyppisestä muistista toisentyyppiseen muistiin, jotta sitä toista muistia olisi näennäisesti enemmän käytettävissä. Saman muistin sisällä swappaamisesta (kuten esimerkissäsi) ei saavuteta vastaavaa hyötyä.

Merri [08.01.2011 18:56:05]

#

Olet sekoittanut sivutuksen (paging, "swap space") ja muuttujien swappauksen.

http://en.wikipedia.org/wiki/Swap_space
http://en.wikipedia.org/wiki/Swap_(computer_science)

Molemmista kyllä saatetaan englanniksi puhua termillä swap, mutta eri asioita ne ovat.

neau33 [08.01.2011 19:05:30]

#

Ehei Merri!

Tarkoitin suoraa talukkojen osoittimien swappaamista...

Merri [08.01.2011 19:17:53]

#

Tämä on taulukoiden suora osoittimien swappaus:

Option Explicit

Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (Arr() As Any) As Long
Private Declare Sub PutMem4 Lib "msvbvm60" (ByVal Ptr As Long, ByVal Value As Long)

Private Sub Command1_Click()
    Dim X() As Byte, Y() As Byte
    Dim Xptr As Long, Yptr As Long

    ReDim X(0)
    ReDim Y(0)

    X(0) = 1
    Y(0) = 2

    Xptr = Not Not X
    Yptr = Not Not Y
    Debug.Assert App.hInstance
    PutMem4 ArrPtr(X), Yptr
    PutMem4 ArrPtr(Y), Xptr

    Debug.Print "X: ", X(0)
    Debug.Print "Y: ", Y(0)
End Sub

Vähän vaikeaa VB6:ssa verrattuna vaikka C:hen, mutta onnistuu noin.

neau33 [08.01.2011 20:13:00]

#

BTW!

käy se swappaaminen näinkin...eli muistityypistä toiseen ilman jatkuvan Redim Preserve käyttöä

Private Type OlioType
    value As Long
    state As Boolean
End Type

Private Olio() As OlioType
Private polku As String

Private Sub Command1_Click()

    polku = "C:\oliodata.dat"
    TsekkaaOlio

    'Testi:
    Olio(UBound(Olio)).value = UBound(Olio)
    Olio(UBound(Olio)).state = True

    Debug.Print "Olio(" & UBound(Olio) & _
    ").value = " & Olio(UBound(Olio)).value

End Sub

Sub TsekkaaOlio()

    If Not Not Olio Then

        If Olio(UBound(Olio)).state Then

            If Dir(polku) <> "" Then
                Kill polku
            End If

            Open polku For Binary Access Write As #1
            Put #1, , Olio: Close #1
            Dim newsize As Integer
            newsize = UBound(Olio) + 1
            Erase Olio: ReDim Olio(newsize)
            Open polku For Binary Access Read As #1
            Get #1, , Olio: Close #1

        End If

    Else
        ReDim Olio(0)
    End If

End Sub

Merri [08.01.2011 21:02:54]

#

Teet ReDim Preserven käyttämällä levyä välissä? Ei Preserven välttämisen tarvitse itsearvo olla. Aiemmassa keskustelussa Preserven välttämisellä tarkoitettiin sitä, että sitä kutsutaan harvemmin varaamalla valmiiksi enemmän muistia kerrallaan, joka parantaa suorituskykyä seurauksena siitä, että tavaraa tarvitsee liikutella muistissa harvemmin.

Esimerkissäsi suorituskyky heikkenee huomattavasti tiedon tallentuessa kovalevylle tai flashmuistille, vaikka se väistääkin Preserven käyttämisen.

Alla olevasta koodista puuttuu vielä kovalevylle tallentaminen, mutta olen lisännyt siihen pari Preserven kutsumista harventavaa tapaa. Vertailun vuoksi laitoin myös Collectionin kuvioihin mukaan.

http://merri.net/vb6/Taulukon_kasvatus2.zip

neau33 [10.01.2011 22:39:40]

#

Heippa taas!

tässä vielä taulukoidun olion swappaaminen osoittimesta...

Private Declare Function ArrayPtr Lib "msvbvm60" Alias _
"VarPtr" (Arr() As Any) As Long

Private Declare Sub GetByte Lib "msvbvm60" Alias _
"GetMem1" (ByRef inSrc As Any, ByRef inDst As Any)

Private Declare Sub PutMemberValue Lib "msvbvm60" Alias _
"PutMem4" (ByVal Ptr As Long, ByVal value As Any)

Private Declare Sub PutMemberState Lib "msvbvm60" Alias _
"PutMem4" (ByVal Ptr As Long, ByVal value As Boolean)

Private Type OlioType
   value As Long
   state As Boolean
End Type

Private Olio() As OlioType

Private Sub Command1_Click()

   TsekkaaOlio

   'Testi:

   Olio(UBound(Olio)).value = UBound(Olio)
   Olio(UBound(Olio)).state = True

   Dim i As Integer
   For i = LBound(Olio) To UBound(Olio)
      Debug.Print "Olio(" & i & _
      ").value = " & Olio(i).value
   Next

End Sub

Sub TsekkaaOlio()

   If Not Not Olio Then
      If Olio(UBound(Olio)).state Then
         ReDim Temppi(UBound(Olio) + 1) As OlioType
         GetByte ArrayPtr(Olio), ArrayPtr(Temppi)
         Dim i As Integer
         For i = LBound(Olio) To UBound(Olio)
            PutMemberValue VarPtr(Temppi(i).value), Olio(i).value
            PutMemberState VarPtr(Temppi(i).state), Olio(i).state
         Next i
         Olio = Temppi: Erase Temppi
      End If
   Else
      ReDim Olio(0)
   End If

End Sub

Grez [11.01.2011 12:54:26]

#

Tässä threadissa kyllä konkretisoituu harvinaisen hyvin vanha totuus, että se mitä on mahdollista tehdä ei välttämättä ole sama asia kuin mitä kannattaa tehdä.

Merri [11.01.2011 23:45:17]

#

Sanohan muuta.

Nean kaksi edellistä koodia myös aiheuttavat ongelmia jos projektissa on muuta koodia, koska Not Not Olion jälkeen ei ole kutsuttu Debug.Assert App.hInstance

Helppo tapa saada virhe näkyville on lisätä heti TsekkaaOlion kutsumisen jälkeen vaikka MsgBox CLng(0.1), jolloin ei ilmestykään messagebox vaan virheilmoitus "Expression too complex". Käännetyssä ohjelmassa tosin ongelmaa ei ole, virhe on vain IDE:ssä ajettaessa.


Sivun alkuun

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta