c# - 用整數索引執行數組表查找的最快方法是什麼?




memory lookup (2)

RAM已經是最快的事情之一。 唯一更快的內存是CPU緩存。 因此它將是“內存綁定”,但這仍然足夠快。

當然,在給定的大小下,此數組的大小為 600萬 個條目。 那可能不適合任何緩存。 並將永遠花光。 速度沒有關係,這只是太多數據。

通常,當今,視頻處理是在GPU上完成的。 實際上, GPU旨在在巨型陣列上運行。 因為這就是您現在看到的圖像-巨大的陣列。

如果您必須將其保留在GPU端,也許緩存或惰性初始化會有所幫助? 您可能並不需要真正的每一個價值。 您只需要 通用 值。 以骰子為例:如果擲出2個6面骰子,則2-12的每個結果都是可能的。 但結果36例中有6例發生7。 2和12個案例中只有36個案例中有1個案例。 因此,將7存儲起來比2和12更為有用。

我有一個移動大量數據的視頻處理應用程序。

為了加快處理速度,我製作了一個查詢表,因為實際上許多計算只需要計算一次即可重用。

但是,我現在所有查找都需要30%的處理時間。 我想知道它是否可能是慢速RAM。但是,我仍然想嘗試對其進行更多優化。

目前,我有以下內容:

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

然後,我使用指針 p (相當於 width * y + x )執行查找以獲取結果。

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]];
   }
}

請注意,我無法進行整個陣列複製以進行優化。 而且,該應用程序是高度多線程的。

在縮短函數堆棧方面取得了一些進展,因此沒有吸氣劑,而是直接從只讀數組中檢索。

我也嘗試過轉換為ushort,但是它似乎要慢一些(據我了解,這是由於字長引起的)。

IntPtr會更快嗎? 我將如何處理?

下面的附件是時間分配的屏幕截圖:


看來您在這裡所做的實際上是一個“聚會”。 現代CPU為此有專門的指令,尤其是 VPGATHER** 。 這是在.NET Core 3中公開的,並且 應該 像下面這樣工作,這是單循環方案(您可能可以從此處獲得雙循環版本);

結果優先:

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

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

碼:

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




lookup