Les membres ayant 30 points peuvent parler sur les canaux annonces, projets et hs du chat.

Forum Casio - Projets de programmation


Index du Forum » Projets de programmation » Simulation de fluide monochrome
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Simulation de fluide monochrome

Posté le 29/09/2022 15:02

Salut à tous,

Je me suis dernièrement amusé à créer une simulation de fluide qui tourne sur navigateur et sur GPU, lorsqu'une idée invraisemblable m'a traversé l'esprit : celle de la porter sur nos bonnes vieilles 35+ tweakées.

Sachant que l'écran est petit et monochrome, j'ai pensé que ça vaudrait le coup d'essayer.
L'algorithme consiste en gros à effectuer une dizaine de doubles boucles qui parcourent tous les pixels de l'écran pour mettre à jour des valeurs dans plusieurs tableaux 2D avant de dessiner le résultat à l'écran, et ce pour chaque frame.
Ainsi pour un écran de 128*64, on aurait environ 128*64*10 = 81920 opérations élémentaires par frame.

Du coup j'ai essayé de concocter un petit addin pour me rendre compte des performances, mais ce fut assez décevant.



En utilisant le code suivant, qui comporte seulement deux double-boucles dont la boucle d'affichage, et qui est restreint en 64x64, je tourne aux alentours de 2-3fps sur émulateur (sachant qu'il n'y a aucune simulation de fluide encore)
Connaissant la suite de l'algorithme, je pense que même en overclockant il sera difficile d'atteindre 1fps...

main.c
Cliquer pour enrouler
#include <stdlib.h>
#include <math.h>
#include <gint/display.h>

const int SIM_W = 64;
const int SIM_H = 64;
const float fSIM_W = (float)SIM_W;
const float fSIM_H = (float)SIM_H;

int ID(int x, int y) {
    return x + y * SIM_W;
}

int main(void) {
    float *v = (float *)malloc(sizeof(float) * SIM_W * SIM_H);

    int radius = 8;

    float a = 0;
    while (1) {
        int addX = (int)(fSIM_W * (cos(a)*.5+.5));
        int addY = (int)(fSIM_H * (sin(a)*.5+.5));

        for (int j = 0; j < SIM_H; j++) {
            for (int i = 0; i < SIM_W; i++) {
                int intensity = fmax(0, radius - sqrt((addX - i) * (addX - i) + (addY - j) * (addY - j)));
                v[ID(i, j)] += (float)intensity / (float)radius;
                v[ID(i, j)] *= 0.95;
            }
        }

        dclear(C_WHITE);
        for (int j = 0; j < SIM_H; j++) {
            for (int i = 0; i < SIM_W; i++) {
                dpixel(i, j, v[ID(i, j)] > 0.5 ? C_BLACK : C_WHITE);
            }
        }
        dupdate();

        a += 0.2;
    }

    return 1;
}


Comme je n'ai pas codé d'addin depuis longtemps je voulais savoir s'il existait des astuces low-level ou non qui permettraient de rendre réalisable un tel projet à plus de 1fps ?

Merci pour vos réponses !


1, 2 Suivante
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 30/09/2022 00:14 | #


Update :

J'ai tout passé en fixed et c'est déjà un peu plus rapide, je dirais aux alentours de 6-7fps.
J'ai aussi enlevé la racine carrée car je n'ai pas besoin d'avoir un cercle super précis.



Je me demande s'il y aurait des optimisations à faire du côté de l'affichage, peut être ne pas passer par dpixel ?

main.c - using fixed point
Cliquer pour enrouler
#include <stdlib.h>
#include <math.h>
#include <gint/display.h>

const int SIM_W = 64;
const int SIM_H = 64;
const float fSIM_W = (float)SIM_W;
const float fSIM_H = (float)SIM_H;

#define DB 15
#define FIX(x) ((x)<<DB) // int to fixed
#define FFIX(x) ((x)*(1<<DB)) // float to fixed
#define UNFIX(x) ((x)>>DB) // fixed to int
typedef int fix;

fix fmul(fix x, fix y) {
    int d1, d2, e1, e2;
    e1 = x >> DB;
    e2 = y >> DB;
    d1 = x & (0xFFFFFFFF>>(32-DB));
    d2 = y & (0xFFFFFFFF>>(32-DB));
    return ((e1*e2)<<DB) + e1*d2 + e2*d1 + ((d1*d2)>>DB);
}

fix fdiv(fix x, fix y) {
    if (y == 0) y += 1;
    if (y>(1<<(2*DB-2))) return x/(y>>DB);
    return fmul(x, ((1<<(2*DB))/y));
}

float fixtof(fix f) {
    return ((float)f)/(1<<DB);
}

int ID(int x, int y) {
    return x + y * SIM_W;
}

int main(void) {
    fix *v = malloc(sizeof(fix) * SIM_W * SIM_H);

    int radius = 8;
    fix dissipation = FFIX(0.92);
    fix fradius = FIX(radius/4);

    float a = 0;
    while (1) {
        int addX = (int)(fSIM_W * (cos(a)*.5+.5));
        int addY = (int)(fSIM_H * (sin(a)*.5+.5));

        for (int j = 0; j < SIM_H; j++) {
            for (int i = 0; i < SIM_W; i++) {
                int intensity = fmax(0, radius - ((addX - i) * (addX - i) + (addY - j) * (addY - j))/2);
                v[ID(i, j)] = fmul( v[ID(i, j)] + fdiv(FIX(intensity), fradius), dissipation );
            }
        }

        dclear(C_WHITE);
        for (int j = 0; j < SIM_H; j++) {
            for (int i = 0; i < SIM_W; i++) {
                dpixel(i, j, fixtof(v[ID(i, j)]) > 0.5 ? C_BLACK : C_WHITE);
            }
        }
        dupdate();

        a += 0.1;
    }

    return 1;
}

Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 30/09/2022 00:37 | #


Tu dois pouvoir éviter le sinus/cosinus en flottant. Voilà une version qui les calcule en point fixe (avec ma lib, mais ça se transpose) :

static num num_cos_dl(num a)
{
    num u = 1.0;
    int p = 7;
    for(p = 2 * p - 1; p >= 1; p -= 2)
        u = num(1) - a * a / (p * p + p) * u;
    return u;
}

num num_cos(num a)
{
    if(a < 0) a = -a;
    a = a % num(6.28319);
    if(a > num(3.14159)) a -= num(6.28319);
    return num_cos_dl(a);
}

num num_sin(num a)
{
    return num_cos(a - num(1.57080));
}

Ton fmax() force une conversion en double qui est inutile puisqu'à la fin du stockes dans un int ; calcule en int et écris le test à la main.

Pour tes multiplication/division flottantes je suggère de vérifier le code assembleur ; la multiplication au moins je suis certain que la version 64 bits que j'utilise dans Rogue Life sera plus rapide (dmuls.l + xtrct).

Ton fradius est constant, je soupçonne que la division en point fixe soit précalculée par le compilateur, mais c'est pas dit.

Pas besoin d'effacer l'écran à chaque itération puisque tu redessines tous les pixels.

Et oui, tu peux générer les long de la VRAM direct bit-à-bit pour éviter le surcoût d'appeler dpixel() pour faire les modifications. De tête (suppose une largeur multiple de 32) :

uint32_t *vram = gint_vram;
fix half = FIX(1) / 2;
for(int j = 0; j < SIM_H; j++) {
    for(int i = 0; i < SIM_W / 32; i++) {
        uint32_t data = 0;
        for(int k = 0; k < 32; k++) {
            data = (data << 1) | (v[ID(32*i+k, j)] > half);
        }
        vram[i] = data;
    }
    vram += 4;
}

Et comme tu l'as vu ne convertis pas en float pour comparer en float avec 0.5, convertis 0.5 en fix et compare les fix.

Note que la macro FIX() que tu as choisie est un peu casse-pieds, on peut pas écrire FIX(0.5) avec.
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 30/09/2022 01:29 | #


Tu es encore une fois un puit de connaissances, merci pour ces précieux conseils !

J'ai modifié le code en suivant tes corrections (à l'exception de sin/cos et de la vram pour l'instant), et les résultats m'ont grandement plu



Maintenant c'est le gif qui ne peut pas contenir toutes les frames! C'est très encourageant et ça me permet de commencer à implémenter la simulation de fluide.

Je vous tient au courant de l'avancée !
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 30/09/2022 21:15 | #


Petite update !

J'ai commencé à implémenter la simulation de fluides, cependant je me suis heurté à un autre problème :
Pour cette simulation il faut allouer de la mémoire pour au strict minimum 5 tableaux de 128*64.
Si je dis pas de bêtises, la RAM des 35+/75 fait 64ko et les int font 4 octets donc un tableau fait déjà 128*64*4=32768 octets soit près de la moitié de la RAM disponible !

Pour les 5 tableaux on arrive à 163ko, soit 100ko de plus que ce qui est disponible.
D'après mes calculs, si je veux rester en dessous des 64ko pour les 5 tableaux il va falloir contraindre la simulation en 56*56 grand maximum.

J'ai lu sur d'autres forums qu'il était possible de libérer plus ou moins 50ko en plus des 64ko de RAM, pourrait-on m'éclairer sur ce sujet ?

Finalement, est-il envisageable d'avoir une simulation plein écran (donc trouver + de 163ko pour stocker les 5 tableaux de 128*64) ?

Merci !
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Slyvtt Hors ligne Community Manager Points: 892 Défis: 0 Message

Citer : Posté le 30/09/2022 22:01 | #


La gestion des ressources est toujours un problème en soit, il faut donc être malin.

Je te conseille de te poser quelques questions :

- as tu absolument besoin de int32_t (ou de uint32_t) ou peux tu travailler avec des int16_t (ou des uint16_t) auquel cas, si tel est le cas tu peux avoir des int sur 2 octets au lieu de 4 octets (et du coup tu divises tes besoins mémoire par deux)

- as tu besoin de travailler sur du 128*64, ne peux tu pas envisager des clusters de 2x2 pixels comme unité de base (downscaling) ce qui divise tes besoins de mémoire par 4 (64x32) et accélèrera aussi les calculs par la même occasion.

- tes 5 tableaux sont ils tous "utiles" ? ou as tu moyen de simplifier, voir de changer le type de 1 voir plusieurs de ces tableaux pour gagner en espace mémoire nécessaire ?

Bref, prends ton algo point par point et regarde ce qui peut être allégé et l'impact sur le résultat de cette possible modification (positive ou négative) afin d'en déduire si c'est opportun ou non comme optimisation.

------------------------------------------------------------------
Le monde est dangereux à vivre ! Non pas tant à cause de ceux qui font le mal, mais à cause de ceux qui regardent et laissent faire.
-------------------------------------------------------------------
Albert Einstein
Mathématicien, Physicien, Scientifique (1879 - 1955)
Mb88 Hors ligne Membre Points: 334 Défis: 0 Message

Citer : Posté le 30/09/2022 22:10 | #


Tu peux aussi utiliser malloc. Pour les grands tableaux c'est mieux.
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 30/09/2022 22:14 | #


Alors, en mémoire sur SH4 tu as :
- RAM statique + pile : 32 ko partagés
- Tas : 48 ko (90 ko sur la Graph 35+E II)
- Extra : 256 ko (0 sur la Graph 35+E II !)

Pour accéder à l'extra, c'est à l'adresse 0x88040000. Mais sois sûr de refuser de te lancer sur Graph 35+E II ou ça fera du dégât !

#include <gint/hardware.h>

if(gint[HWCALC] == HWCALC_G35PE2) {
    /* Show error */
    return;
}
fix (*buffers)[5][128*64] = (void *)0x88040000;
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 30/09/2022 22:35 | #


@Slyvtt : Pour l'instant j'utilise des int en tant que fixed point, je perds déjà en précision en faisant cela et je ne sais pas si des fixed point sur 2 octets sont envisageables pour cette simulation, mais j'essayerai une fois l'algorithme terminé pour voir si ça peut marcher !
L'idée de dowscale est intéressante pour avoir tout de même du plein écran mais vu la résolution très basse de la calculatrice j'ai besoin d'utiliser le maximum de pixels afin d'apercevoir des détails de la simulation.
Finalement pour les tableaux, l'algorithme de base en requiert 8 mais j'ai réussi à restreindre à 5 et ils sont vraiment tous nécessaires. Ceci dit il y a un tableau qui n'a pas forcément besoin d'avoir autant de précision que les autres, donc c'est déjà quelque chose que je peux optimiser !

@Mb88 : Oui ils sont déjà tous déclarés avec malloc !

@Lephenixnoir : Tas c'est à combiner avec la ram statique ou il y a un moyen particulier pour y accéder ? Sur émulateur j'arrivais à dépasser 32ko en tout cas. Et donc la simulation semble largement possible en plein écran si j'utilise la mémoire extra sur toutes les calculatrices sauf la 35+E II c'est ça ?

Merci beaucoup pour tous vos conseils !
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 30/09/2022 22:40 | #


Le tas c'est malloc(). Oui hors Graph 35+E II tu peux juste prendre le code ci-dessus avec la RAM "extra" et t'as les 5 buffers gratuitement. Sur la Graph 35+E II je crains que tu n'aies pas assez de mémoire...

... sauf si tu utilises la RAM du SPU, qui est complètement starbée (et un peu plus lente que la RAM normale) mais pourrait marcher pour ça. Tu as 160 ko de PRAM qui peut recevoir des tableaux tant que les éléments individuels font 4 octets.
Redcmd Hors ligne Membre Points: 339 Défis: 7 Message

Citer : Posté le 30/09/2022 23:53 | #


How possible is it to reduce the size of each pixel?
from 32bits to 16bits?

You could write your hot loop in assembly
tho I think gcc does a pretty good job already (much better than casio's sdk)
RedCMD#4299 - Discord
Mandelbrot SNKEmini Minesweeper
Shadow15510 Hors ligne Administrateur Points: 5410 Défis: 16 Message

Citer : Posté le 01/10/2022 17:41 | #


Coucou par ici !
L'idée m'intéresse ! Comment simules-tu les fluides ? Est-ce que tu calcules les déformations et les rotations de ton fluide, ou est-ce plus simplifié ?
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 01/10/2022 20:41 | #


Hello !

J'implémente le paper initulé "Real Time Fluid Dynamics for Games" de Jos Stam, il se base sur les équations de Navier-Stokes!
En gros le fluide est représenté par un champ de vecteur, donc un tableau 2d pour la vélocité X et un autre pour la vélocité Y.
Ensuite il y a plusieurs étapes :

Les forces : l'étape la plus simple, normalement on les ajoutent en fonction de la position de la souris mais là je vais devoir penser autrement
L'advection : le fluide se transporte lui-même, donc on calcule un nouveau champ de vecteur à partir du champ t-1,
La diffusion : les forces se dissipent au cours du temps
La divergence : Pour simplifier les calculs, le fluide a comme contrainte d'être incompressible, ce qui implique que la divergence du fluide doit être nulle en tout point, ce qui n'est pas le cas actuellement donc on calcule la divergence actuelle du fluide.
La pression : On résout une équation du type Ax=B avec A la pression, x le fluide et B la divergence pour trouver la pression qui satisfait une divergence de 0 et on soustrait le résultat au champ de vecteur calculé après diffusion. Pour résoudre l'équation j'utilise l'algorithme de jacobi qui est un algo itératif qui va approximer le résultat et s'en rapprocher à chaque itération (il converge lentement donc il faut au moins 20 itérations)

Et on se retrouve avec le champ vectoriel t+1 du fluide qui satisfait les équations de Navier-Stokes pour un fluide incompressible !
Maintenant pour afficher quelque chose il faut un autre tableau représentant le colorant et il va passer par l'étape d'ajout des forces, puis par l'étape d'advection (qui va transporter le colorant à travers le fluide)

Voilà c'est assez grossièrement résumé mais dans les grandes lignes l'algo ressemble à ça ! Je mets un lien vers la page Github de mon implémentation GPU, tu pourras trouver toutes les références qui m'ont aidé dont le paper à la fin du readme !

https://github.com/indiana-dev/WebGPU-Fluid-Simulation#references
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Shadow15510 Hors ligne Administrateur Points: 5410 Défis: 16 Message

Citer : Posté le 01/10/2022 20:53 | #


Ow ok merci beaucoup ! C'est propre
"Ce n'est pas parce que les chose sont dures que nous ne les faisons pas, c'est parce que nous ne les faisons pas qu'elles sont dures." Sénèque

Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 01/10/2022 20:56 | #


Quand ça commence par "J'implémente un papier" tu sais que ça va être classe
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 01/10/2022 22:41 | #


Explication sur le type du gros buffer.

Appelons "champ scalaire" un tableau de W*H fix, pour clarifier l'explication.

fix (*buffers)[5][W*H] = (void *)0x88040000;

buffers est un pointeur (puisque c'est *buffers dans la définition). L'objet pointé, *buffers, est de type fix[5][W*H], c'est un tableau de 5 champs scalaires. (*buffers)[0] est donc le premier champ scalaire, de type fix[W*H].

fix *field_0 = (*buffers)[0];

Cet affectation fait un cast implicite de fix[W*H] vers fix *.

On pouvait faire un peu plus simple que ce type compliqué pour buffers. C'est rare qu'on utilise un pointeur vers un tableau de 5 éléments ; le C nous permet d'abuser et d'utiliser un pointeur vers le premier élément, laissant implicite le fait qu'il y en a 4 autres qui le suivent en mémoire.

fix (*buffers)[W*H] = (void *)0x88040000;

Ici *buffers est de type fix[W*H]. buffers est donc un pointeur vers un seul champ scalaire.

fix *field_0 = buffers[0];
fix *field_1 = buffers[1];

Mais on peut abuser et écrire buffers[1] ce qui va chercher le champ scalaire situé après *buffers. Quand bien même le type de buffers ne garantit l'existence que d'un champ.

(C'est classique ; si une fonction prend un char * en paramètre c'est pas clair du tout si c'est une chaîne de caractères -un tableau- ou juste un pointeur vers un seul caractère qu'elle va remplir, à part dans le contexte.)
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 02/10/2022 00:25 | #


Top, merci pour ces informations. J'ai réussi à accéder à la mémoire extra et donc augmenter la taille de la simulation !

En 36x36 jusqu'à présent je tournais aux alentours de 10-15fps, et en 64x64 je suis à 3-5fps. Cependant je n'ai pas encore optimisé tant que ça l'algorithme, donc je pense que c'est possible de le rendre plus rapide, et en dernier recours je pourrais utiliser l'overclock.

Voilà un petit aperçu du rendu actuel (à noter que j'utilise un logiciel pour accélérer l'émulateur d'environ 6x, donc ce n'est pas représentatif des performances réelles!):


(le chiffre représente le nombre de frames dessinées / 10)
Je ne suis pas sûr que ce soit 100% fonctionnel encore. Les résultats que j'ai diffèrent légèrement de mon ancienne simulation et au bout d'un moment la simulation explose et devient du noise. Je vais m'y pencher et voir si c'est un problème de précision ou de code !

Update : Voici le code actuel, je n'ai plus de noise et j'ai essayé d'optimiser ce que je pouvais, ça tourne aux alentours de 7fps mais je commence à manquer d'idées. Si vous souhaitez y jeter un oeil peut-être que vous y verrez des optimisations évidentes ! J'aimerai faire le plus possible avant de passer à l'overclock

https://haste.breizh.pm/yuranewome.m
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 02/10/2022 09:48 | #


Première chose : commence à mesurer attentivement la performance du code. Tu peux le faire avec libprof. C'est le moment où tu as vraiment besoin de chiffres précis pour savoir où taper

Marque toutes les petites fonctions static GINLINE (avec <gint/defs/attributes.h>) pour être sûr qu'elles soient inlinées. C'est probablement déjà le cas mais autant être sûr.

Essaie de compiler avec -O2 ou -O3 au lieu de -Os dans ton CMakeLists.txt.

Dans advect() :

      fix x1 = ffloor(x);
      fix y1 = ffloor(y);
      fix x2 = x1 + ONE;
      fix y2 = y1 + ONE;

      fix s1 = x - x1;
      fix s0 = ONE - s1;
      fix t1 = y - y1;
      fix t0 = ONE - t1;

La décomposition partie entière/décimale peut se faire un peu plus efficacement :

static GINLINE fix ffrac(fix x) {
    return x & ((1 << DB) - 1);
}
int x1_as_int = UNFIX(x);
fix s1 = ffrac(x);

Sachant que comme DB=16 le compilateur optimisera ffrac() en une seule instruction extu.w. (Tu peux aussi écrire return (unsigned short)x si tu veux cette garantie, mais ça marchera que pour DB=16.) De façon générale DB=16 c'est le mieux pour les perfs.

Le faire d'avoir x1 en int te simplifie aussi la vie puisqu'à la fin du faisais un fID() dessus donc te le convertissais quand même.

Les const fix ne sont pas optimisés comme tu le penses. const ne garantit pas que c'est une constante, juste que c'est en lecture seule. Ici ton code accède vraiment à la mémoire chaque fois que tu mentionnes ONE, ONE_HALF, etc. Tes lignes 28-39 seraient mieux en macros. T'inquiète pas le compilateur va tout précalculer et ces calculs ne se retrouveront pas à l'exécution.

  const halfRdx = fmul(ONE_HALF, rdx);

Attention quand tu ne donnes pas le type d'une variable c'est automatiquement un int. Ici par chance ça marche. Mais ça devrait pop dans les warnings avec -Wall -Wextra, je te conseille vivement de ne pas laisser passer ce genre de trucs.

    // Calculate divergence & reset pressure
    for (int j = 0; j < H; j++) {
      for (int i = 0; i < W; i++) {
        int id = ID(i, j);
        div[id] = i*j==0||i==W-1||j==H-1 ? 0 : fmul(halfRdx, vx[ID(i+1,j)] - vx[ID(i-1, j)] + vy[ID(i, j+1)] - vy[ID(i, j-1)]);
        p[id] = 0;
      }
    }

Pour l'histoire de localité du cache, tu devrais séparer l'usage de p et de div. D'ailleurs tu peux faire memset(p, 0, W*H*sizeof(fix)) et ça ira sans doute plus vite.

Pour ta boucle tu devrais définitivement sortir les itérations j=0 et j=H-1 pour ne pas avoir à les tester sans cesse, et les faire séparément. Et peut-être même que faire la boucle interne de i=1 à i=W-1, éliminant la condition, serait également un boost (à tester).

          p[id] = fmul(fmul(alpha, div[id]) + p[ID(i-1, j)] + p[ID(i+1, j)] + p[ID(i, j-1)] + p[ID(i, j+1)], ONE_QUARTER);

Ne multiplie pas par ONE_QUARTER, divise directement par 4. La multiplication ou la division d'un fix par un entier ne nécessite pas passer par fmul() ou fdiv(). (Le compilateur optimisera en un test et un bit shift).

Si tu sais que la valeur est positive tu peux même directement faire le bit shift de 2 unités vers la droite.

L'opti avec dpixel() est toujours une option.

Bon finalement rien de bien violent, mais quand tu auras trouvé quelles boucles comptent le plus on pourra voir bien en détail
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 02/10/2022 14:35 | #


Merci beaucoup pour ta réponse !

Concernant le timer, j'ai implémenté gint/timer avant de lire ta réponse mais comme on en a discuté libprof semble plus adapté pour mesurer les performances donc je vais switcher.

Pour DB, au final mettre plus de précision sur la partie décimale semblait tout casser. Je dois pas me rendre compte de la range de la partie entière dans les calculs intermédiaires. Mais si ça marche en 16 et que c'est + opti, tant mieux.

Pour halfRdx sans type c'est un oubli (les types et les point-virgules c'est dur après javascript ). J'avais effectivement un warning qui m'était passé sous le nez !

Ahh concernant la localité du cache je comprend peut-etre mieux ce qui m'est arrivé hier :
Figure toi que le code actuel (dans ton commentaire) est plus rapide que celui-ci :

        
// Calculate divergence & reset pressure
for (int j = 1; j < H-1; j++) {
    for (int i = 1; i < W-1; i++) {
        int id = ID(i, j);
        div[id] = fmul(halfRdx, vx[ID(i+1,j)] - vx[ID(i-1, j)] + vy[ID(i, j+1)] - vy[ID(i, j-1)]);
        p[id] = 0;
    }
}
for (int i = 0; i < W; i++) {
    p[ID(i, 0)] = 0;
    p[ID(i, H-1)] = 0;
}
for (int j = 0; j < H; j++) {
    p[ID(0, j)] = 0;
    p[ID(W-1, j)] = 0;
}


Dans le premier code il y a un if effectué sur tous les pixel de l'écran
Dans le deuxième, plus de if car je ne check plus les bordures (que je gère juste après).
Je m'attendais à ce que ce soit plus rapide en raison de l'absence de la condition mais au final ça ralentit le programme de 0.5fps.
De toute façon avec ton astuce de memset je n'ai plus besoin d'avoir la condition, et je peux virer les deux boucles de bordure derrière, mais ça m'avait étonné hier.

J'ai une question à propos des optimisations de fixed : rdx et dt sont des fix et je ne pouvais pas déclarer fix dtRdx = fmul(dt, rdx) hors du main (non constant expression error je crois). Est-ce opti de faire #define dtRdx fmul(dt, rdx) ? Comment optimiser une constante qui est la multiplication de deux autres ?
Et comme tu m'indique qu'avec les fixed la division/multiplication par entier se fait directement, je n'ai pas besoin de mes define ONE_HALF, ONE_QUARTER, je peux juste faire /2 et /4 partout ?
"Si tu sais que la valeur est positive tu peux même directement faire le bit shift de 2 unités vers la droite." -> la valeur positive, tu parles du fixed, du int ou du résultat ?

Pour dpixel, j'ai essayé hier de supprimer carrément la ligne (donc de ne rien dessiner) et ça n'a pas changé du tout les performances. Ceci dit j'appelais toujours dupdate pour afficher le nombre de frames donc c'est peut-être cet appel qui est lent ?

Je vais maintenant me pencher du côté des fonction static, de ton optimisation partie entière/fractionnelle et ajouter libprof pour avoir une analyse plus précise des performances.

(Et à vrai dire, pour chaque frame toutes les doubles boucles sont exécutées une seule fois sauf celle de l'équation de pression qui est exécutée 10 fois ici (mais normalement je devrais mettre 20). C'est le fait d'avoir 20 fois cette boucle qui semble représenter le plus gros poids pour l'algo, ça fait vite beaucoup d'opérations, même si l'opération n'est pas très gourmande).
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Drakalex007 Hors ligne Membre Points: 662 Défis: 0 Message

Citer : Posté le 02/10/2022 14:52 | #


Update :

Concernant le code de pression : effectuer moins d'opérations, supprimer la condition et utiliser memset semble plus lent.
Avec le code actuel, une frame prend 109ms, tandis qu'avec cette modification, une frame prend 123ms :

    
// 109ms    
for (int j = 0; j < H; j++) {
    for (int i = 0; i < W; i++) {
        int id = ID(i, j);
        div[id] = i*j==0||i==W-1||j==H-1 ? 0 : fmul(halfRdx, vx[ID(i+1,j)] - vx[ID(i-1, j)] + vy[ID(i, j+1)] - vy[ID(i, j-1)]);
        p[id] = 0;
    }
}

// 123ms
for (int j = 1; j < H-1; j++) {
    for (int i = 1; i < W-1; i++) {
        div[ID(i, j)] = fmul(halfRdx, vx[ID(i+1,j)] - vx[ID(i-1, j)] + vy[ID(i, j+1)] - vy[ID(i, j-1)]);
    }
}

memset(p, 0, W*H*sizeof(fix));


C'est pas vraiment attendu si ?
Jouez dès maintenant à Jetpack Joyride sur votre calculatrice !
Pour les curieux
Cliquer pour enrouler
Un très bon Monopoly : Monopoly 2.0 !
Vous aimez Minecraft ? Venez voir ma chaîne ! (même si je fais plus de vidéos dessus)
Lephenixnoir En ligne Administrateur Points: 22768 Défis: 149 Message

Citer : Posté le 02/10/2022 16:13 | #


C'est pas ce que j'attendais non, mais les chiffres ont toujours raison. Si tu gardes la condition mais que tu actives le memset() ça donne quoi ? memset() est plus rapide en principe, mais il est possible que le débit RAM soit déjà maxed out.
1, 2 Suivante

LienAjouter une imageAjouter une vidéoAjouter un lien vers un profilAjouter du codeCiterAjouter un spoiler(texte affichable/masquable par un clic)Ajouter une barre de progressionItaliqueGrasSoulignéAfficher du texte barréCentréJustifiéPlus petitPlus grandPlus de smileys !
Cliquez pour épingler Cliquez pour détacher Cliquez pour fermer
Alignement de l'image: Redimensionnement de l'image (en pixel):
Afficher la liste des membres
:bow: :cool: :good: :love: ^^
:omg: :fusil: :aie: :argh: :mdr:
:boulet2: :thx: :champ: :whistle: :bounce:
valider
 :)  ;)  :D  :p
 :lol:  8)  :(  :@
 0_0  :oops:  :grr:  :E
 :O  :sry:  :mmm:  :waza:
 :'(  :here:  ^^  >:)

Σ π θ ± α β γ δ Δ σ λ
Veuillez donner la réponse en chiffre
Vous devez activer le Javascript dans votre navigateur pour pouvoir valider ce formulaire.

Si vous n'avez pas volontairement désactivé cette fonctionnalité de votre navigateur, il s'agit probablement d'un bug : contactez l'équipe de Planète Casio.

Planète Casio v42 © créé par Neuronix et Muelsaco 2004 - 2022 | Il y a 47 connectés | Nous contacter | Qui sommes-nous ? | Licences et remerciements

Planète Casio est un site communautaire non affilié à Casio. Toute reproduction de Planète Casio, même partielle, est interdite.
Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou copyrights.
CASIO est une marque déposée par CASIO Computer Co., Ltd