【问题标题】:Is this popular StackOverflow answer incorrect?这个流行的 StackOverflow 答案不正确吗?
【发布时间】:2011-08-16 20:07:19
【问题描述】:

我指的帖子是this. 不仅第一个在将大小增加到 100000000 时对我来说运行得更快(563 MS 与 630 相比),而且在过去使用 for 循环中的属性造成了相当大的速度减慢。

在编写波形查看器时,每帧平均需要 23 毫秒来处理。循环遍历整个图片像素的 for 循环是导致速度下降的原因。我没有在 for 循环之前存储图片的宽度和高度,而是在每次迭代期间访问它。更改后,处理单个帧的时间从 23 毫秒变为仅 3 毫秒。

另外,我制作了这个示例类:

class LOL
{
    private int x;

    public LOL(int x)
    {
        this.x = x;
    }

    public int X
    {
        get
        {
            return x;
        }
    }
}

然后我创建了一个循环 500000000 次的 for 循环。一个测试在循环开始之前将 X 存储在一个整数变量中,一个测试在每次迭代期间访问 X 属性。前者大约需要 1,500 毫秒,而后者大约需要 8,000 毫秒。

三个测试,每个测试的结果都是预先存储限制是性能提升的最佳解决方案。我错过了什么吗?目前在我的程序中需要优化,因为要处理大图片,并且在所有性能关键区域中,我总是预先存储循环的边界以获得性能提升,这些测试似乎证实了这一点。

【问题讨论】:

  • “过去在 for 循环中使用属性会导致相当大的速度减慢” - 这完全取决于循环的确切性质。当使用数组执行此操作时,JIT 获取提示并执行一些边界检查消除循环内的数组访问。这与您的测试不同,听起来它实际上并没有在循环中做任何事情......
  • 我假设您运行的是发行版,没有附加调试器?
  • 你在比较苹果和橘子。图像的 Width 和 Height 属性与数组大小无关。是的,它们很昂贵,它需要通过非托管 GDI+ 代码来回往返。您总是希望在循环之前将它们检索到局部变量中。

标签: c# performance optimization


【解决方案1】:

不,我觉得它是正确的;如果我运行这个:

int[] values = new int[100000000];

var watch = Stopwatch.StartNew();
int length = values.Length;
for (int i = 0; i < length; i++)
    values[i] = i;
watch.Stop();
var hoisted = watch.ElapsedMilliseconds;

watch = Stopwatch.StartNew();
for (int i = 0; i < values.Length; i++)
    values[i] = i;
watch.Stop();
var direct = watch.ElapsedMilliseconds;

并通过优化构建,从控制台运行;我得到 direct 为 71 和 hoisted 为 163,这与我期望 JIT 消除对向量的越界检查(但仅在直接访问时)有关。

【讨论】:

  • 对我来说它们的速度差不多,但第二个速度稍快一些。我认为第一个对我来说更快的原因是因为我在存储长度后启动了秒表。我想这没有多大意义。不管怎样,谢谢回复。我现在明白了=)
  • 你知道,如果我颠倒你的测试发生的顺序(直接第一个和第二个提升),在 Release 中编译并优化,我会得到相反的结果。提升是~84ms,直接是~150ms。当我把你粘贴的代码拿走时,我得到了相反的结果(即直接更快)。
  • @David hmm.... 我认为指出了 JIT 进行/不进行优化的不同场景。我无法复制您的数字,但我可以让它们保持相同 - 意思是:它没有应用优化
  • @David 我也得到了相反的结果。
  • @BrunoLM - 我想知道这些方法的初始化(他们不是 JIT 的吗?)是否会增加一些开销并可能会扭曲结果。不过我只是猜测。
【解决方案2】:

发布模式编译,缓存长度更快。调试时则相反。

测试方法

public static class Performance
{
    public static void Test(string name, decimal times, bool precompile, Action fn)
    {
        if (precompile)
        {
            fn();
        }

        GC.Collect();
        Thread.Sleep(2000);

        var sw = new Stopwatch();

        sw.Start();

        for (decimal i = 0; i < times; ++i)
        {
            fn();
        }

        sw.Stop();

        Console.WriteLine("[{0,15}: {1,-15}]", name, new DateTime(sw.Elapsed.Ticks).ToString("HH:mm:ss.fff"));
        Debug.WriteLine("[{0,15}: {1,-15}]", name, new DateTime(sw.Elapsed.Ticks).ToString("HH:mm:ss.fff"));
    }
}

测试代码:

var testAmount = 100;

int[] a1 = new int[10000000];
int[] a2 = new int[10000000];

Performance.Test
(
    "Direct", testAmount, true,
    () =>
    {
        for (int i = 0; i < a1.Length; ++i)
        {
            a1[i] = i;
        }
    }
);

Performance.Test
(
    "Cache", testAmount, true,
    () =>
    {
        var l = a2.Length;
        for (int i = 0; i < l; ++i)
        {
            a2[i] = i;
        }
    }
);

调试结果

[   Direct: 00:00:06.474   ]
[   Cache:  00:00:06.907   ]

发布结果

[   Direct: 00:00:05.382   ]
[   Cache:  00:00:04.714   ]

【讨论】:

  • 我的号码很不一样;在这种情况下,我基本上得到了相同的时间 - 与您引用的“发布结果”完全不同。在 Debug 中,缓存版本的速度明显较慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-24
  • 2011-02-09
  • 2018-11-30
  • 1970-01-01
  • 2018-06-28
  • 2016-08-17
  • 1970-01-01
相关资源
最近更新 更多