我有一个基于表现的问题。 有没有办法删除嵌套的foreach循环替换它们更高效的东西?这是一个例子:
List<foo> foos = SelectAllfoos();
foreach(foo f in foos){
//dosomething
foreach(foo2 f2 in foo.GetFoos2()){
//dosomething
}
foreach(foo3 f3 in foo.GetFoos3()){
//dosomething
}
foreach(foo4 f4 in foo.GetFoos4()){
//dosomething
foreach(foo4_1 f4_1 in f4.GetFoos4_1()){
//dosomething
}
}
}
显然,这是我刚为这个例子发明的假代码。但想象你有类似的东西。你应该如何改进这种方法的表现?
PS:我已经尝试使用System.Threading.Task.Parallel.ForEach
并且它提高了性能,但我的意思是更好的方法来编写此代码。
PPS:这是用C#编写的,但我的问题涉及更广泛的范围,在所有语言中都有用。
答案 0 :(得分:2)
由于这个问题相当普遍,而且只关注那些没有提供有关实际工作的信息的循环,我只能提供一般答案。
您通常想要关注的最后一件事是循环机制本身。如果有的话,这些影响往往很小。
通常情况下,如果您遇到这种情况,那么算法改进就会出来(例如:顺序循环不能比线性时间复杂度更好,因为它们需要遍历并对每个元素执行某些操作,无论如何),那么最大的两个改进通常来自并行化和内存优化。
遗憾的是,后者很少被讨论,特别是在高级语言中,但往往带来同样多的影响。它可以按数量级的顺序改善执行时间,并且无论语言如何都适用。像缓存效率这样的概念不是依赖于语言的概念,因为无论我们使用什么编程语言,硬件都保持不变(尽管我们实现它的方式因语言而异)。
内存访问模式
例如,采用图像处理算法。在这种情况下,给定两个相同的机器指令(除了它们被交换的事实除外),在外部循环中一次访问一个水平扫描线的像素的存储器访问模式可以明显优于访问一个垂直列的像素的存储器访问模式每次像素数。即使具有相同的总指令级成本的机器指令(尽管指令成本可变),这也是正确的,但仅仅是以交换顺序访问内存。
这是因为,粗略地说,计算机将较慢形式的内存中的数据提取到连续块(页面,缓存行)中的更快形式的内存中。当您水平访问图像的像素时,相邻的水平像素块可能会从较慢的内存形式中提取到更快的形式,并且最终会从更快的内存形式访问所有相邻像素,然后再继续下一系列像素。当您以垂直方式访问图像的像素时,最终将水平相邻像素加载到更快的内存形式中,仅使用该列中的一个像素。由于高速缓存未命中,结果会显着减慢所得到的图像算法,因为我们在被驱逐之前将其加载到更小但更快的内存形式时无法使用所有可用数据(我们基本上会浪费那些更小但更快的内存带来的好处)。
所以通常如果你想让循环变得更快,并且算法改进已经完成,你想要分析访问内存的方式,甚至可能改变所涉及的数据结构的内存布局。当你在内存中一起访问连续的数据时,计算机会喜欢它,当你以混乱的方式访问内存时,不要那么喜欢它。他们喜欢将内存内容紧密组合在一起的数组,而不是链接结构,这些结构将内存分散到各处(除非链接结构或其内存分配器经过精心设计,不要这样做)。快速循环不会像循环所做的那样改变循环的机制,但比算法改进更深入,甚至可能是并行化是来自面向数据的设计思维方式的那些与内存相关的优化。在像C#这样的语言中,从数据结构中获得更好的引用局部性的技术之一就是对象池。
循环平铺/阻止
有时,您可以通过简单地更改循环数据的方式来改进内存访问模式,而无需实际更改数据的表示方式。一个这样的例子是循环平铺(aka loop blocking):https://software.intel.com/en-us/articles/how-to-use-loop-blocking-to-optimize-memory-use-on-32-bit-intel-architecture。但同样,这里的加速并不是来自优化编写循环的方式本身,而是以利用参考局部性的方式优化遍历数据的方式。它完全依赖于内存访问。
<强>仿形强>
所有这些微观优化技术都倾向于使您的代码更难以维护,因此他们几乎总是最好地应用于您的手中的大量分析测量。首先要了解优化的一个方面是如何衡量,基于硬数据而不是预测来做。初学者往往希望优化更多,而不是更少,因为他们根据可能效率低下而不是硬数据和正确测量的猜测来进行优化。为了明显算法瓶颈,这很容易做到这一点,但其他任何东西通常都需要你手中的分析器。一个好的优化者是一个狙击手调度热点,而不是一个手榴弹,盲目地向任何可能减慢速度的东西投掷手榴弹。实际上,了解如何正确确定优化的优先级并进行正确的测量可能比理解机器的内部工作更为重要。所以可能超出所有这些内容,如果你想让你的循环更快,首先抓住一个分析器并学习如何正确地测量效率低下。要问的第一件事不是如何让事情变得更快,而是实际上需要更快的事情(同样重要的是,如果不是更多,那不是什么)。