帮助通过C和/或汇编优化C#功能

Mus*_*sis 9 c c# optimization assembly

我有这个C#方法,我正在尝试优化:

// assume arrays are same dimensions
private void DoSomething(int[] bigArray1, int[] bigArray2)
{
    int data1;
    byte A1, B1, C1, D1;
    int data2;
    byte A2, B2, C2, D2;
    for (int i = 0; i < bigArray1.Length; i++)
    {
        data1 = bigArray1[i];
        data2 = bigArray2[i];

        A1 = (byte)(data1 >> 0);
        B1 = (byte)(data1 >> 8);
        C1 = (byte)(data1 >> 16);
        D1 = (byte)(data1 >> 24);

        A2 = (byte)(data2 >> 0);
        B2 = (byte)(data2 >> 8);
        C2 = (byte)(data2 >> 16);
        D2 = (byte)(data2 >> 24);

        A1 = A1 > A2 ? A1 : A2;
        B1 = B1 > B2 ? B1 : B2;
        C1 = C1 > C2 ? C1 : C2;
        D1 = D1 > D2 ? D1 : D2;

        bigArray1[i] = (A1 << 0) | (B1 << 8) | (C1 << 16) | (D1 << 24); 
    }
}
Run Code Online (Sandbox Code Playgroud)

该函数基本上比较了两个int数组.对于每对匹配元素,该方法比较每个单独的字节值并取两者中较大的一个.然后,为第一个数组中的元素分配一个int由4个最大字节值构成的新值(与源无关).

我已经在C#中尽可能地优化了这种方法(当然我可能还没有 - 也欢迎那些得分的建议).我的问题是,将此方法移动到非托管C DLL是否值得? 考虑到编组托管int数组的开销以便将它们传递给方法,生成的方法是否会执行得更快(以及更快)?

如果这样做会让我的速度提高10%,那么肯定不值得我花时间.如果它快2或3倍,那么我可能不得不这样做.

注意: 请不要"提前优化"评论,在此先感谢.这只是"优化".

更新: 我意识到我的代码示例没有捕获我正在尝试在此函数中执行的所有操作,因此这是更新版本:

private void DoSomethingElse(int[] dest, int[] src, double pos, 
    double srcMultiplier)
{
    int rdr;
    byte destA, destB, destC, destD;
    double rem = pos - Math.Floor(pos);
    double recipRem = 1.0 - rem;
    byte srcA1, srcA2, srcB1, srcB2, srcC1, srcC2, srcD1, srcD2;
    for (int i = 0; i < src.Length; i++)
    {
        // get destination values
        rdr = dest[(int)pos + i];
        destA = (byte)(rdr >> 0);
        destB = (byte)(rdr >> 8);
        destC = (byte)(rdr >> 16);
        destD = (byte)(rdr >> 24);
        // get bracketing source values
        rdr = src[i];
        srcA1 = (byte)(rdr >> 0);
        srcB1 = (byte)(rdr >> 8);
        srcC1 = (byte)(rdr >> 16);
        srcD1 = (byte)(rdr >> 24);
        rdr = src[i + 1];
        srcA2 = (byte)(rdr >> 0);
        srcB2 = (byte)(rdr >> 8);
        srcC2 = (byte)(rdr >> 16);
        srcD2 = (byte)(rdr >> 24);
        // interpolate (simple linear) and multiply
        srcA1 = (byte)(((double)srcA1 * recipRem) + 
            ((double)srcA2 * rem) * srcMultiplier);
        srcB1 = (byte)(((double)srcB1 * recipRem) +
            ((double)srcB2 * rem) * srcMultiplier);
        srcC1 = (byte)(((double)srcC1 * recipRem) +
            ((double)srcC2 * rem) * srcMultiplier);
        srcD1 = (byte)(((double)srcD1 * recipRem) +
            ((double)srcD2 * rem) * srcMultiplier);
        // bytewise best-of
        destA = srcA1 > destA ? srcA1 : destA;
        destB = srcB1 > destB ? srcB1 : destB;
        destC = srcC1 > destC ? srcC1 : destC;
        destD = srcD1 > destD ? srcD1 : destD;
        // convert bytes back to int
        dest[i] = (destA << 0) | (destB << 8) |
            (destC << 16) | (destD << 24);
    }
}
Run Code Online (Sandbox Code Playgroud)

本质上,这与第一种方法的作用相同,除了在这一方面,第二个数组(src)总是小于第一个(dest),第二个数组相对于第一个小数位于小数位置(意味着它不是位于,而是,10相对于dest,它可以定位在10.682791).

为了实现这一点,我必须在源中的两个包围值之间进行插值(例如,对于第一个元素,在上例中为10和11),然后将插值字节与目标字节进行比较.

我怀疑这个函数涉及的乘法比字节比较要贵得多,因此该部分可能是红鲱鱼(对不起).此外,即使比较相对于乘法仍然有些昂贵,我仍然存在这个系统实际上可能是多维的问题,这意味着不是比较一维数组,而是数组可以是2-,5-或无论如何,最终计算内插值所花费的时间将使4字节的最终字节比较所花费的时间相形见绌(我假设是这种情况).

相对于位移,这里的乘法有多昂贵,这是一种可以通过卸载到C DLL(甚至是汇编DLL)而加速的操作,尽管我不得不雇用某人来创建为了我)?

Han*_*ant 7

是的,_mm_max_epu8()内在做你想要的.一次咀嚼16个字节.痛点是阵列.SSE2指令要求其参数在16字节地址处对齐.你无法从垃圾收集堆中获得它,它只承诺4字节对齐.即使您通过计算16字节对齐的数组中的偏移来欺骗它,那么当垃圾收集器启动并移动数组时您将丢失.

您必须使用__declspec(align(#))声明符在C/C++代码中声明数组.现在,您需要将托管阵列复制到那些非托管阵列中.结果又回来了.您是否仍然领先取决于您的问题中不容易看到的细节.


Jim*_*hel 4

下面的函数使用不安全的代码将整数数组视为字节数组,这样就不需要进行位操作。

    private static void DoOtherThing(int[] bigArray1, int[] bigArray2)
    {
        unsafe
        {
            fixed (int* p1 = bigArray1, p2=bigArray2)
            {
                byte* b1 = (byte*)p1;
                byte* b2 = (byte*)p2;
                byte* bend = (byte*)(&p1[bigArray1.Length]);
                while (b1 < bend)
                {
                    if (*b1 < *b2)
                    {
                        *b1 = *b2;
                    }
                    ++b1;
                    ++b2;
                }
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

在我的机器上,在发布模式下针对 2500 万个整数的数组在调试器下运行,此代码比原始代码快约 29%。然而,独立运行时,运行时几乎没有区别。有时原始代码更快,有时新代码更快。

大约数字:

          Debugger  Standalone
Original  1,400 ms    700 ms
My code     975 ms    700 ms
Run Code Online (Sandbox Code Playgroud)

是的,我确实比较了结果以确保这些函数执行相同的操作。

我无法解释为什么我的代码没有更快,因为它所做的工作显着减少。

鉴于这些结果,我怀疑您是否可以通过使用本机代码来改进事情。正如您所说,编组数组的开销可能会耗尽您在处理过程中可能实现的任何节省。

不过,对原始代码进行以下修改后,速度会快 10% 到 20%。

    private static void DoSomething(int[] bigArray1, int[] bigArray2)
    {
        for (int i = 0; i < bigArray1.Length; i++)
        {
            var data1 = (uint)bigArray1[i];
            var data2 = (uint)bigArray2[i];

            var A1 = data1 & 0xff;
            var B1 = data1 & 0xff00;
            var C1 = data1 & 0xff0000;
            var D1 = data1 & 0xff000000;

            var A2 = data2 & 0xff;
            var B2 = data2 & 0xff00;
            var C2 = data2 & 0xff0000;
            var D2 = data2 & 0xff000000;

            if (A2 > A1) A1 = A2;
            if (B2 > B1) B1 = B2;
            if (C2 > C1) C1 = C2;
            if (D2 > D1) D1 = D2;

            bigArray1[i] = (int)(A1 | B1 | C1 | D1);
        }
    }
Run Code Online (Sandbox Code Playgroud)