Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

VB 6 et antérieur Discussion :

Mon problème vient-il des optimisations ?


Sujet :

VB 6 et antérieur

  1. #1
    Membre régulier
    Mon problème vient-il des optimisations ?
    Bonjour-bonsoir heureux confinés (!...)

    Une fois n'est pas coutume : je vais vous soumettre du code. Mais je vais d'abord expliquer ce que je veux faire (attention, ça va décoiffer !).

    J'utilise la DLL WINMM pour récupérer le son d'un micro, et je veux afficher le "sonogramme" obtenu. Le but du programme étant de comparer, cycle après cycle, des formes d'ondes censées être semblables, pour évaluer les différences. Et en plus, comme elles sont censées se répéter à intervalle régulier et prédéfini, je désire les représenter sur un cercle, et non sur une droite, comme on le fait d'habitude, afin de mettre en évidence un éventuel décalage. Et ça n'est pas encore tout à fait fini : pour pouvoir comparer deux formes d'onde successives, je représente la forme "courante" sur un cercle externe, et la forme précédente sur un cercle intérieur.

    Pour pouvoir faire cela, il faut effacer le tracé précédent. Mais au lieu de faire un "CLS", je tente d'effacer "point par point" le tracé précédent.

    Après divers tâtonnements, j'en suis venu à la conclusion qu'il me fallait 4 buffers de son : celui qui est en train de se remplir est tracé "en temps réel" sur le cercle externe, alors que celui que j'ai tracé au cycle précédent doit être effacé du cercle externe et tracé sur le cercle interne, après que celui du cercle interne ait été effacé.

    Pour avoir cet effet visuel "temps réel", ces différentes opérations sont faites sous le contrôle d'un "timer" au rythme de un appel toutes les millisecondes : voici le code :
    Code vb6 :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    Private Sub Timer1_Timer()
    Dim t As Currency, alpha As Single, i As Integer, s As Single, c As Single
    Const Eps As Single = 0.75
    QueryPerformanceCounter t
    alpha = (t - t0) / CPeriode
    If alpha > alpha0 + 1 Then 'changement de période
        buf = (buf + 1) Mod 4
        Coul = 1 - Coul
        alpha0 = alpha ' alpha0 : compte les tours; DEUXPI * (alpha-alpha0) : angle
        waveInPrepareHeader DevHandle, VarPtr(Wave(buf)), LInData
        waveInAddBuffer DevHandle, VarPtr(Wave(buf)), LInData
        t1 = t
        t2 = t
     Else
        For i = (t2 - t1) / CPas To (t - t1) / CPas
            s = Sin(DEUXPI * i / nbpas)
            c = Cos(DEUXPI * i / nbpas)
            Picture1.PSet (InData((buf + 3) Mod 4, i) * s, InData((buf + 3) Mod 4, i) * c), vbWhite
            Picture1.PSet (InData(buf, i) * s, InData(buf, i) * c), couleur(Coul)
            Picture1.PSet (InData((buf + 2) Mod 4, i) * Eps * s, InData((buf + 2) Mod 4, i) * Eps * c), vbWhite
            Picture1.PSet (InData((buf + 3) Mod 4, i) * Eps * s, InData((buf + 3) Mod 4, i) * Eps * c), couleur(1 - Coul)
        Next
        t2 = t
    End If
    End Sub

    J'ai dit que le cycle était prédéfini. Je le contrôle avec le "PerformanceCounter" de Windows, qui est prétendument plus précis que la microseconde...

    CPeriode est la durée théorique d'un cycle, et Cpas est la période à laquelle le sont est échantillonné, ces deux valeurs étant mesurées dans l'unité du "PerformanceCouneter", qui utilise le type "Currency" de VB6 (équivalent des entiers 64 bits).

    Je remercie ceux qui sont venus jusqu'ici. Je ne suis pas sûr qu'il soit intéressant d'entrer dans le détail des choix que j'ai fait pour réaliser mon projet, mais venons-en à mon problème, qui se situe dans la boucle avec les 4 Picture1.Pset : il me semble bien que, pour chaque indice "i" des 4 buffers qui sont remplis successivement par le micro, je fais les opérations successives suivantes : écriture sur le cercle externe, soit en rouge, soit en noir, puis écriture au même endroit en blanc, puis écriture sur le cercle interne, soit en noir, soit en rouge, puis écriture au même endroit en blanc. et que ces opérations sont "entrelacées" avec celles des autres indices et des autres buffers de telle façon que chaque sonogramme se développe progressivement, en remplaçant le précédent.

    Eh bien ça ne marche pas : je vois parfois rien, parfois un quart de cercle avec du rouge et du noir mélangé. Évidemment, j'ai pu faire une erreur de raisonnement (mais j'ai quand-même pas mal réfléchi...) mais je me demande si la réponse ne serait pas dans telle ou telle "optimisation", soit de VB6, soit de Windows, soit du BIOS, qui changerait l'ordre d'exécution des écritures de pixels.

    Je ne doute pas que ceux qui sont venus jusqu'ici sauront répondre à cette question. Et si c'est "oui", est-il possible de l'empêcher ?

    Merci d'avance.

    AMIcalement.

  2. #2
    Rédacteur/Modérateur

    Salut

    Pour améliorer la rapidité de traitement il te faudrait plutôt passer par ces 2 APIs qui sont beaucoup plus rapide SetPixel à la place de .Pset et si tu as un jour besoin de lire un pixel --> GetPixel
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    'Assigne une couleur au contrôle image en x,y
    Public Declare Function SetPixel Lib "gdi32" ( _
        ByVal hdc As Long, _
        ByVal x As Long, _
        ByVal y As Long, _
        ByVal crColor As Long) As Long
    'Obtient la couleur en x,y d'un contrôle image
    Public Declare Function GetPixel Lib "gdi32" ( _
        ByVal hdc As Long, _
        ByVal x As Long, _
        ByVal y As Long) As Long

    Il y a encore beaucoup plus rapide, GDI+
    Tu peux faire une recherche dans le forum VB6 et Vos Source VB6, j'ai plusieurs discussions et sources postées qui t'en dirons plus.
    Sur Google fais une recherche avec GDI+ VB6 developpez.net tu y retrouveras pleins de sources (dont plusieurs des miennes).

    For i = (t2 - t1) / CPas To (t - t1) / CPas pour améliorer aussi ici, créer une variable Debi = (t2 - t1) / CPas et une autre Fini = (t - t1) / CPas puis le début de boucle For devient For i = Debi To Fini cela évite, a chaque incrémentation de i de refaire faire le calcul par le processeur.

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
            Picture1.PSet (InData((buf + 3) Mod 4, i) * s, InData((buf + 3) Mod 4, i) * c), vbWhite
            Picture1.PSet (InData(buf, i) * s, InData(buf, i) * c), couleur(Coul)
            Picture1.PSet (InData((buf + 2) Mod 4, i) * Eps * s, InData((buf + 2) Mod 4, i) * Eps * c), vbWhite
            Picture1.PSet (InData((buf + 3) Mod 4, i) * Eps * s, InData((buf + 3) Mod 4, i) * Eps * c), couleur(1 - Coul)
    Là aussi il y a des calculs qui se répète.
    Tout temps pris pour refaire un même calcul n'est pas utilisé pour rafraichir/actualiser l'affichage, se qui explique en partie le dysfonctionnement.
    Soyez sympa, pensez -y
    Balises[CODE]...[/CODE]
    Balises[CODE=NomDuLangage]...[/CODE] quand vous mettez du code d'un autre langage que celui du forum ou vous postez.
    Balises[C]...[/C] code intégré dans une phrase.
    Balises[C=NomDuLangage]...[/C] code intégré dans une phrase quand vous mettez du code d'un autre langage que celui du forum ou vous postez.
    Le bouton en fin de discussion, quand vous avez obtenu l'aide attendue.
    ......... et pourquoi pas, pour remercier, un pour celui/ceux qui vous ont dépannés.

  3. #3
    Membre régulier
    Merci de ta réponse, merci de montrer que tu sais de quoi tu parles.

    Je vais regarder les api s pour SetPixel, je n'y avais pas pensé.

    J'ai cependant deux remarques à faire. J'étais inquiet de la consommation de temps CPU par ces longues boucles, mais le gestionnaire des tâches ne dépasse pas quelques pourcents de CPU, et encore moins de GDI. Je suis bien certain que ça ne répond pas à tout, mais quand-même. Et j'ai fait des recherches cet après-midi sur l'optimisation sous VB6, et je suis tombé sur un site américain qui disait explicitement que c'était une illusion de croire qu'on y gagnerait en évitant les calculs dans les bornes de boucles : il prétend que l'interpréteur est capable de faire l'optimisation qui va bien. As-tu un avis ?

    Il y a un truc que je n'ai pas regardé, je vais le faire dès que j'aurai fini ce message : générer l'exe et regarder si c'est pareil. Ensuite, je regarderai tes optimisations, il ne faut négliger aucune piste.

    Encore merci, et à +...

    AMIcalement.

  4. #4
    Membre régulier
    Grand merci à ProgElecT ! J'ai trouvé une réponse.

    Et, curieusement, ça n'est que très indirectement que ses conseils m'ont aidé. Le m'explique : il m'a fait remarquer que mes boucles auraient beaucoup à gagner à ce qu'un certain nombre de calculs en soient sortis. Dans l'absolu, ça n'est pas faux, bien que je ne sois pas persuadé que le problème original vienne de là. Mais j'ai sorti des boucles les calculs d'incrémentation des index de buffer, en utilisant des variables locales pour porter les numéros calculés en dehors des boucles. Mais, comme je l'avais expliqué, le "dévidage" des buffers est fait par paquets, dans le programme déclenché par un timer : au premier temps d'un cycle, le timer calcules tous les paramètres du cycle - dont les numéros des différents buffers - et dans les temps suivants, il traite les éléments des buffers correspondant à ce temps du cycle.

    C'est compliqué, je vous en fait mes excuses, certains diront que c'est bien de moi... Mais ça occupe bien le confinement.

    Quoi qu'il en soit, j'ai mis deux jours à comprendre pourquoi tout se passait comme si mes incrémentations de numéro de buffer ne se faisaient pas (soit à l'échantillonnage, soit à l'affichage) : les numéros de buffers calculés lors du premier temps du cycle étaient dans des variables locales au programme timer !!!

    Maintenant, ça marche.

    Merci de votre attention.

    AMIcalement.

  5. #5
    Membre régulier
    Arrrhhhh ! Je reviens sur ce problème. Voici le nouveau code :
    Code vb6 :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    Private Sub Timer1_Timer()
    Dim t As Currency, alpha As Single, i As Integer, s As Single, c As Single
    Dim D As Long, F As Long
    Const R2 As Single = 1.35, R1 As Single = 1.45
    QueryPerformanceCounter t
    alpha = (t - t0) / CPeriode
    If alpha > alpha0 + 1 Then 'changement de période
        alpha0 = alpha ' alpha0 : compte les tours; DEUXPI * (alpha-alpha0) : angle
        buf = (buf + 1) Mod 4
        b0 = buf
        b1 = (buf + 1) Mod 4
        b2 = (buf + 2) Mod 4
        b3 = (buf + 3) Mod 4
        coul = 1 - coul
        waveInPrepareHeader DevHandle, VarPtr(Wave(buf)), 32
        waveInAddBuffer DevHandle, VarPtr(Wave(buf)), 32
        t1 = t
        t2 = t
        Ctdp(0) = t
    Else
        D = (t2 - t1) / CPas
        F = (t - t1) / CPas
        For i = D To F
            s = Sin(DEUXPI * i / nbpas) * M / 200
            c = Cos(DEUXPI * i / nbpas) * M / 200
            SetPixel Picture1.hdc, M + R1 * InData(b3, i) * s, M + R1 * InData(b3, i) * c, vbWhite
            SetPixel Picture1.hdc, M + R1 * InData(b0, i) * s, M + R1 * InData(b0, i) * c, couleur(coul)
            SetPixel Picture1.hdc, M + R2 * InData(b2, i) * s, M + R2 * InData(b2, i) * c, vbWhite
            SetPixel Picture1.hdc, M + R2 * InData(b3, i) * s, M + R2 * InData(b3, i) * c, couleur(1 - coul)
        Next
        t2 = t
    End If
    End Sub

    Pour la mise au point de tout ce bazar, je n'ai pas utilisé le micro, mais j'ai créé quatre buffers que mon programme remplissait à l'initialisation. Cela m'a permis de me faire une idée de ce que ça pourrait donner en vrai". Et c'est quand j'ai vu ce que j'espérais voir que j'ai crié victoire.

    Trop tôt, bien entendu. Maintenant que j'ai mis le micro en service, je ne vois plus qu'un quart de cercle, et les pixels rouges et noirs sont tout mélangés : j'en déduis que c'est la garouille dans les buffers.

    Ma question porte aujourd'hui sur l'utilisation des deux fonctions WaveInPrepareHeader et WWaveInAddBuffer (lignes 15 et 16 ci-dessus) La doc de Microsoft ne dit pas clairement ce qu'elles font, et je ne sais pas si c'est bien comme ça qu'il faudrait faire ce que je veux faire : que l'API remplisse les buffers l'un après l'autre, avec le plus petit "trou" possible au changement de buffer, et traiter les données le plus tôt possible après leur écriture.

    L'expertise exigée par cette question n'est plus tout à fait la même, et je ne la possède pas...

    Merci d'avance;

    AMIcalement.

  6. #6
    Membre régulier
    Bonjour à tous.

    Je m'arrache les cheveux depuis une semaine sur cette question, et, cette nuit, j'ai compris où était le problème, mais j'ai besoin de vous pour le régler, car je n'arrive pas à trouver les informations clé dans mes recherches.

    Dans le code que j'ai publié, il manque une information que je croyais triviale, mais qui est cruciale : les tables "indata" sont déclarées en "byte" en VB6, et sont passées (par adresse) aux routines de l'API : c'est là le nœud : l'API les traite bel et bien octet par octet, mais le programme du timer ne le sait pas, et le traite donc comme VB6 traite les "byte", et moi, je ne sais plus comment il les traite : sur deux octets, sur quatre octets ? et en poids fort ou en poids faible ?

    Merci a celui qui me donnera un lien vers cette information.

    AMIcalement.