web-dev-qa-db-fra.com

Quel est le moyen le plus rapide pour effectuer une recherche de tableau de tableau avec un index entier?

J'ai une application de traitement vidéo qui déplace beaucoup de données.

Pour accélérer les choses, j'ai créé une table de recherche, car de nombreux calculs ne doivent être calculés qu'une seule fois et peuvent être réutilisés.

Cependant, je suis au point où toutes les recherches prennent maintenant 30% du temps de traitement. Je me demande si ça pourrait être de la RAM lente .. Cependant, je voudrais quand même essayer de l'optimiser un peu plus.

Actuellement, j'ai les éléments suivants:

public readonly int[] largeArray = new int[3000*2000];
public readonly int[] lookUp = new int[width*height];

J'effectue ensuite une recherche avec un pointeur p (ce qui équivaut à width * y + x) pour récupérer le résultat.

int[] newResults = new int[width*height];
int p = 0;
for (int y = 0; y < height; y++) {
   for (int x = 0; x < width; x++, p++) {
      newResults[p] = largeArray[lookUp[p]];
   }
}

Notez que je ne peux pas faire une copie complète du tableau pour optimiser. En outre, l'application est fortement multithread.

Certains progrès ont été réalisés dans le raccourcissement de la pile de fonctions, donc pas de getters mais une récupération directe à partir d'un tableau en lecture seule.

J'ai également essayé de me convertir en version courte, mais cela semblait plus lent (si je comprends bien, cela est dû à la taille de Word).

Un IntPtr serait-il plus rapide? Comment pourrais-je m'y prendre?

Ci-dessous, une capture d'écran de la distribution du temps:

enter image description here

37
RobotRock

Il semble que ce que vous faites ici soit effectivement un "rassemblement". Les processeurs modernes disposent d'instructions dédiées à cet effet, en particulier VPGATHER**. Ceci est exposé dans .NET Core 3, et devrait fonctionner quelque chose comme ci-dessous, qui est le scénario à boucle unique (vous pouvez probablement travailler à partir d'ici pour obtenir la version à double boucle);

résultats d'abord:

AVX enabled: False; slow loop from 0
e7ad04457529f201558c8a53f639fed30d3a880f75e613afe203e80a7317d0cb
for 524288 loops: 1524ms

AVX enabled: True; slow loop from 1024
e7ad04457529f201558c8a53f639fed30d3a880f75e613afe203e80a7317d0cb
for 524288 loops: 667ms

code:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

static class P
{
    static int Gather(int[] source, int[] index, int[] results, bool avx)
    {   // normally you wouldn't have avx as a parameter; that is just so
        // I can turn it off and on for the test; likewise the "int" return
        // here is so I can monitor (in the test) how much we did in the "old"
        // loop, vs AVX2; in real code this would be void return

        int y = 0;
        if (Avx2.IsSupported && avx)
        {
            var iv = MemoryMarshal.Cast<int, Vector256<int>>(index);
            var rv = MemoryMarshal.Cast<int, Vector256<int>>(results);

            unsafe
            {
                fixed (int* sPtr = source)
                {
                    // note: here I'm assuming we are trying to fill "results" in
                    // a single outer loop; for a double-loop, you'll probably need
                    // to slice the spans
                    for (int i = 0; i < rv.Length; i++)
                    {
                        rv[i] = Avx2.GatherVector256(sPtr, iv[i], 4);
                    }
                }
            }
            // move past everything we've processed via SIMD
            y += rv.Length * Vector256<int>.Count;
        }
        // now do anything left, which includes anything not aligned to 256 bits,
        // plus the "no AVX2" scenario
        int result = y;
        int end = results.Length; // hoist, since this is not the JIT recognized pattern
        for (; y < end; y++)
        {
            results[y] = source[index[y]];
        }
        return result;
    }

    static void Main()
    {
        // invent some random data
        var Rand = new Random(12345);
        int size = 1024 * 512;
        int[] data = new int[size];
        for (int i = 0; i < data.Length; i++)
            data[i] = Rand.Next(255);

        // build a fake index
        int[] index = new int[1024];
        for (int i = 0; i < index.Length; i++)
            index[i] = Rand.Next(size);

        int[] results = new int[1024];

        void GatherLocal(bool avx)
        {
            // prove that we're getting the same data
            Array.Clear(results, 0, results.Length);
            int from = Gather(data, index, results, avx);
            Console.WriteLine($"AVX enabled: {avx}; slow loop from {from}");
            for (int i = 0; i < 32; i++)
            {
                Console.Write(results[i].ToString("x2"));
            }
            Console.WriteLine();

            const int TimeLoop = 1024 * 512;
            var watch = Stopwatch.StartNew();
            for (int i = 0; i < TimeLoop; i++)
                Gather(data, index, results, avx);
            watch.Stop();
            Console.WriteLine($"for {TimeLoop} loops: {watch.ElapsedMilliseconds}ms");
            Console.WriteLine();
        }
        GatherLocal(false);
        if (Avx2.IsSupported) GatherLocal(true);
    }
}
55
Marc Gravell