【问题标题】:How to speed up C# math code如何加速 C# 数学代码
【发布时间】:2012-06-01 00:48:20
【问题描述】:

我有一些 3d 插值代码占用了我项目运行时间的 90%,并且无法预先计算。

我可以使用哪些技术来加快速度?算法优化还是微优化?

这里是感兴趣的人的代码。

它基本上采用放置在 2 个 3d 数组中的数据并插入其余数据。

编辑:此外,我已经将其拆分为更高级别的线程以提高性能,但这对 windows phone 没有帮助,因为它们都是单核...

我可能会执行类似 (Single[] DensityMap = new Single[128 * 128 * 128];) 的操作来移除多维数组命中。我在 100 多个地方访问数组,并希望不必这样做(包装在函数中无济于事,因为 windows phone 不会内联函数调用,并且它对性能没有帮助......)

float[, ,] DensityMap = new float[128, 128, 128];
float[, ,] PressureMap = new float[128, 128, 128];

unchecked
{
    for (int x = 0; x < g_CraftWorldConstants.RegionSizeX; x++)
    {
        int offsetX = (x / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;
        int plusOffsetX = SAMPLE_RATE_3D_HOR + offsetX;
        int poxox = plusOffsetX - offsetX;
        double poxxpoxox = ((plusOffsetX - x) / (double)poxox);
        double xoxpoxox = ((x - offsetX) / (double)poxox);

        for (int y = 0; y < g_CraftWorldSettings.GET.RegionSizeY; y++)
        {
            int offsetY = (y / SAMPLE_RATE_3D_VERT) * SAMPLE_RATE_3D_VERT;
            int plusOffsetY = SAMPLE_RATE_3D_VERT + offsetY;
            int poyoy = plusOffsetY - offsetY;
            double poyypoyoy = ((plusOffsetY - y) / (double)poyoy);
            double yoypoyoy = ((y - offsetY) / (double)poyoy);

            for (int z = 0; z < g_CraftWorldConstants.RegionSizeZ; z++)
            {
                if (!(x % SAMPLE_RATE_3D_HOR == 0 && y % SAMPLE_RATE_3D_VERT == 0 && z % SAMPLE_RATE_3D_HOR == 0))
                {
                    int offsetZ = (z / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;
                    int plusOffsetZ = SAMPLE_RATE_3D_HOR + offsetZ;
                    int pozoz = plusOffsetZ - offsetZ;
                    double pozzpozoz = ((plusOffsetZ - z) / (double)pozoz);
                    double zozpozoz = ((z - offsetZ) / (double)pozoz);

                    double x00 = poxxpoxox * in_DensityMap[offsetX, offsetY, offsetZ] + xoxpoxox * in_DensityMap[plusOffsetX, offsetY, offsetZ];
                    double x10 = poxxpoxox * in_DensityMap[offsetX, offsetY, plusOffsetZ] + xoxpoxox * in_DensityMap[plusOffsetX, offsetY, plusOffsetZ];
                    double x01 = poxxpoxox * in_DensityMap[offsetX, plusOffsetY, offsetZ] + xoxpoxox * in_DensityMap[plusOffsetX, plusOffsetY, offsetZ];
                    double x11 = poxxpoxox * in_DensityMap[offsetX, plusOffsetY, plusOffsetZ] + xoxpoxox * in_DensityMap[plusOffsetX, plusOffsetY, plusOffsetZ];

                    double r0 = poyypoyoy * x00 + yoypoyoy * x01;
                    double r1 = poyypoyoy * x10 + yoypoyoy * x11;
                    in_DensityMap[x, y, z] = (float)(pozzpozoz * r0 + zozpozoz * r1);

                    double x02 = poxxpoxox * in_CaveDensity[offsetX, offsetY, offsetZ] + xoxpoxox * in_CaveDensity[plusOffsetX, offsetY, offsetZ];
                    double x12 = poxxpoxox * in_CaveDensity[offsetX, offsetY, plusOffsetZ] + xoxpoxox * in_CaveDensity[plusOffsetX, offsetY, plusOffsetZ];
                    double x03 = poxxpoxox * in_CaveDensity[offsetX, plusOffsetY, offsetZ] + xoxpoxox * in_CaveDensity[plusOffsetX, plusOffsetY, offsetZ];
                    double x13 = poxxpoxox * in_CaveDensity[offsetX, plusOffsetY, plusOffsetZ] + xoxpoxox * in_CaveDensity[plusOffsetX, plusOffsetY, plusOffsetZ];

                    double r2 = poyypoyoy * x02 + yoypoyoy * x03;
                    double r3 = poyypoyoy * x12 + yoypoyoy * x13;
                    in_CaveDensity[x, y, z] = (float)(pozzpozoz * r2 + zozpozoz * r3);
                }
            }
        }
    }
}

【问题讨论】:

  • 也许你可以近似 - 例如,只取每个轴上 1/10 的值,并插入缺失的值。
  • Parallel.For 可以大大加快速度
  • 如果您进行并行化,请确保仅在一个级别进行并行化——可能是外部 for 语句。因此,您将有两个连续的 for 循环,一个并行的 for 循环。请参阅msdn.microsoft.com/en-us/library/dd997392.aspx 了解原因。
  • 您有一些简化的计算(我确信编译器无论如何都会这样做,因此它不会帮助提高性能),例如(x / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR 可以简单地变成x,这样可以消除一些冗余变量。
  • @Lukazoid-这些是int计算,所以可能无法简化,即(1234 / 100) * 100 = 1200。

标签: c# performance windows-phone-7 optimization xna


【解决方案1】:

看来你有很多机会来优化你的代码。您的 x 循环执行 128 次,您的 y 循环执行 128*128=16,384 次,您的 z 循环执行 128^3=2,097,152 次。 z 循环中有许多项仅取决于 x 或 y 迭代,但在每次 z 迭代时都会重新计算它们。例如,

int poxox = plusOffsetX - offsetX;

double poxxpoxox = ((plusOffsetX - x) / (double)poxox);

这两个项被计算了超过 200 万次,但如果我对你的函数的粗略扫描是正确的,则只需要计算 128 次。将项移动到适当的循环级别,这样您就不会浪费循环多次重新计算相同的值。

这是您进行了基本优化的代码。我很想知道这如何影响您的运行时间。其中一些项仅取决于迭代值,并且对于 x、y 和 z 是相同的。所以我将它们完全提取出来并预先计算了一次。我还将外部 mod 操作移出内部循环,并修改了逻辑以确保评估短路,这应该会删除以前执行的大部分 mod 操作。

int[] offsets = new int[128];
int[] plusOffsets = new int[128];
double[] poii = new double[128];
double[] ioip = new double[128];
for (int i = 0; i < 128; i++) {
    offsets[i] = (i / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;
    plusOffsets[i] = SAMPLE_RATE_3D_HOR + offsets[i];
    double poioi = (double) (plusOffsets[i] - offsets[i]);
    poii[i] = ((plusOffsets[i] - i) / poioi);
    ioip[i] = ((i - offsets[i]) / poioi);
}

float[, ,] DensityMap = new float[128, 128, 128];
float[, ,] PressureMap = new float[128, 128, 128];

for (int x = 0; x < g_CraftWorldConstants.RegionSizeX; x++)
{
    int offsetX = offsets[x];
    int plusOffsetX = plusOffsets[x];
    double poxxpoxox = poii[x];
    double xoxpoxox = ioip[x];
    bool xModNot0 = !(x % SAMPLE_RATE_3D_HOR == 0);

    for (int y = 0; y < g_CraftWorldConstants.RegionSizeY; y++)
    {
        int offsetY = offsets[y];
        int plusOffsetY = plusOffsets[y];
        double poyypoyoy = poii[y];
        double yoypoyoy = ioip[y];
        bool yModNot0 = !(y % SAMPLE_RATE_3D_VERT == 0);

        for (int z = 0; z < g_CraftWorldConstants.RegionSizeZ; z++)
        {
            //if (!(x % SAMPLE_RATE_3D_HOR == 0 && y % SAMPLE_RATE_3D_VERT == 0 && z % SAMPLE_RATE_3D_HOR == 0))
            if (xModNot0 || yModNot0 || !(z % SAMPLE_RATE_3D_HOR == 0))
            {
                int offsetZ = offsets[z];
                int plusOffsetZ = plusOffsets[z];
                double pozzpozoz = poii[z];
                double zozpozoz = ioip[z];

                double x00 = poxxpoxox * DensityMap[offsetX, offsetY, offsetZ] + xoxpoxox * DensityMap[plusOffsetX, offsetY, offsetZ];
                double x10 = poxxpoxox * DensityMap[offsetX, offsetY, plusOffsetZ] + xoxpoxox * DensityMap[plusOffsetX, offsetY, plusOffsetZ];
                double x01 = poxxpoxox * DensityMap[offsetX, plusOffsetY, offsetZ] + xoxpoxox * DensityMap[plusOffsetX, plusOffsetY, offsetZ];
                double x11 = poxxpoxox * DensityMap[offsetX, plusOffsetY, plusOffsetZ] + xoxpoxox * DensityMap[plusOffsetX, plusOffsetY, plusOffsetZ];

                double r0 = poyypoyoy * x00 + yoypoyoy * x01;
                double r1 = poyypoyoy * x10 + yoypoyoy * x11;
                DensityMap[x, y, z] = (float)(pozzpozoz * r0 + zozpozoz * r1);

                double x02 = poxxpoxox * PressureMap[offsetX, offsetY, offsetZ] + xoxpoxox * PressureMap[plusOffsetX, offsetY, offsetZ];
                double x12 = poxxpoxox * PressureMap[offsetX, offsetY, plusOffsetZ] + xoxpoxox * PressureMap[plusOffsetX, offsetY, plusOffsetZ];
                double x03 = poxxpoxox * PressureMap[offsetX, plusOffsetY, offsetZ] + xoxpoxox * PressureMap[plusOffsetX, plusOffsetY, offsetZ];
                double x13 = poxxpoxox * PressureMap[offsetX, plusOffsetY, plusOffsetZ] + xoxpoxox * PressureMap[plusOffsetX, plusOffsetY, plusOffsetZ];

                double r2 = poyypoyoy * x02 + yoypoyoy * x03;
                double r3 = poyypoyoy * x12 + yoypoyoy * x13;
                PressureMap[x, y, z] = (float)(pozzpozoz * r2 + zozpozoz * r3);
            }
        }
    } 
}

【讨论】:

  • 它实际上让事情快了 19%!这是一个巨大的改进 :) 我之前已经将其中的许多变量移出内部函数,但我错过了你找到的所有变量,这表明另一双眼睛有多好。 SAMPLE_RATE_3D_HOR 也是一个 int (有人问),变量的所有愚蠢名称(如 poxxpoxox)都是因为 resharpers 内联函数,我曾经懒惰地删除了 8 个内部函数。谢谢,这真的很有帮助,你能看到我可以做些什么来改进它吗?
  • @DanielArmstrong - 我已经编辑了我的答案以包含我在发布之前版本后立即注意到的另一个优化,但直到现在才能发布,因为我必须开车去某个地方。这应该会进一步提高速度。您需要进行测试以确保我的更改不会影响结果。
  • @DanielArmstrong - 我已经编辑了答案以包括一个小的优化以删除大约 400 万个 mod (%) 操作,如果右手边不是电源,我认为这有点贵2 个。
  • 这看起来很有希望,我明天会实施,让你知道进展如何。
【解决方案2】:

你可以做一些事情来加速你的代码:

  • 避免使用 multidim.-arrays,因为它们很慢
  • 使用多个线程
  • 将要转换为双精度的变量存储在双精度变量中
  • 尽你所能预先计算(参见斧头的帖子)

数组

要模拟 3D 阵列,您可以这样做:

Single[] DensityMap = new Single[128 * 128 * 128];
DensityMap[z + (y * 128) + (x * 128 * 128)] = ...;

【讨论】:

  • 多线程如何在单核 CPU 上提供帮助?由于上下文切换和同步问题,这让事情变得更糟。
  • @dowhilefor 他还在谈论实际上有 3 个内核的 XBox360(每个内核可以处理 2 个线程)!您在谈论什么同步问题?
  • 抱歉,没有看到有关 xbox 的信息。如果您使用多个,请确保不会死锁您的线程。因此,如果他真的可以使用多个内核,那么处理同步问题可能值得付出努力,但我只考虑了一个内核,它在多线程时的性能会差得多。同步也是有代价的。
  • @dowhilefor 我认为您不需要在此处同步内容。您可以轻松地在此处拆分工作,并且无需处理此线程之间的数据。
  • 你是对的。但通常值得考虑的是,这可能会出现。无论如何好答案+1
【解决方案3】:

使用锯齿状数组而不是多维,即做

float[][][] DensityMap = new float[128][][];

然后使用 for 循环或 LINQ syntax(可能不是最佳的)创建内部数组。

这将提供比使用多维数组更好的性能,并且与使用单维数组并自己计算偏移量相同或更好的性能。也就是说,除非初始化锯齿状数组的成本很高;毕竟它会创建 128^2 个数组。我会对其进行基准测试,并且仅在成本确实很高时才恢复为一维数组。

【讨论】:

  • +1。我不久前做的一个简单的基准测试表明,锯齿状数组的初始化比多维数组的初始化需要更长的时间(我不记得多长时间),但对锯齿状数组元素的访问速度要快 15-20%。所以确实,在这种情况下(初始化一次,但访问元素很多),这可以提供帮助,除了其他回复中的提示。
  • 为什么锯齿状数组更快?它们应该更慢,因为需要更多的内存访问。
  • 这取决于多维数组是如何实现的。那时,它们并没有从与一维数组相同的优化中受益。不过,这可能已经改变了,这个答案已有 8 年历史了。
【解决方案4】:

你可以改变你的 for 循环,因为你没有为所有这些的中间值做任何事情

for (int x = 0; x < 128; x+= SAMPLE_RATE_3D_HOR) {
   for (int y = 0; y < 128; y+= SAMPLE_RATE_3D_VERT) {
      for (int z = 0; z < 128; z+= SAMPLE_RATE_3D_HOR) {

并行执行这些会更好。

有了这个,您可以消除 600 万个 mod % 计算和 60+000 个乘法。

--编辑-- 对不起,我错过了“!”与 3 个 mods 在线上。您仍然可以跳过其中一些计算。请参阅下面的 cmets。

【讨论】:

  • 其实我已经这样做了,这个函数旁边还有另一个W循环:),它是在所有6个线程中的360上线程化的。
  • “有了这个,你可以消除 600 万个 mod % 计算和 60+ 千次乘法”是什么意思。
  • 对不起,我误读了这一行 if (!(x % SAMPLE_RATE_3D_HOR == 0 && y % SAMPLE_RATE_3D_VERT == 0 && z % SAMPLE_RATE_3D_HOR == 0)) 我错过了“!”。如果您跳过 (X % SAMPLE_RATE_3D_HOR == 0) 的 Y 和 Z 循环并跳过 (Y % SAMPLE_RATE_3D_VERT == 0) 的 z 循环,您仍然可以消除最内层循环上的一些 mod 计算
【解决方案5】:

1) 你真的需要双打吗?尤其是你正在混合一些浮点数、双精度数和整数。

2) 您应该预先计算 k / SAMPLE_RATE_3D_HOR * SAMPLE_RATE_3D_HOR 模式。

int pre_calc[128];
for( int i = 0; i < 128; ++i )
    pre_calc[i] = (i / SAMPLE_RATE_3D_HOR) * SAMPLE_RATE_3D_HOR;

【讨论】:

  • 我的印象是,在 Windows 7 上,手机双打比浮点快 40-50%?这显然是因为 clr 无论如何都会将浮点数转换为双精度数。
  • 双打速度较慢。当参数之一是双精度时,clr 将运算符中的浮点数(和整数)提升为双精度值。
  • 我刚刚在手机硬件上对一个程序进行了基准测试,双精度数(加法、乘法、减法、除法)的数学运算似乎快了大约 35%。但是我可能做错了什么,这可能与缓存有关?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多