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:
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);
}
}