想知道一次迭代与多次迭代对性能的影响。我在Python工作 - 我不确定这是否会影响答案。
考虑尝试对列表中的每个项目执行一系列数据转换。
def one_pass(my_list):
for i in xrange(0, len(my_list)):
my_list[i] = first_transformation(my_list[i])
my_list[i] = second_transformation(my_list[i])
my_list[i] = third_transformation(my_list[i])
return my_list
def multi_pass(my_list):
range_end = len(my_list)
for i in xrange(0, range_end):
my_list[i] = first_transformation(my_list[i])
for i in xrange(0, range_end):
my_list[i] = second_transformation(my_list[i])
for i in xrange(0, range_end):
my_list[i] = third_transformation(my_list[i])
return my_list
现在,除了具有可读性的问题,严格来说,在性能方面,one_pass优于multi_pass是否真的有优势?假设大部分工作都发生在转换函数本身中,那么multi_pass中的每次迭代不会只占用大约1/3的时间吗?
答案 0 :(得分:5)
区别在于您正在阅读的值和代码在CPU's cache中的频率。
如果my_list
的元素很大,但适合CPU缓存,则第一个版本可能是有益的。另一方面,如果转换的(字节)代码很大,则缓存操作可能比缓存数据更好。
这两个版本可能比更易读的方式更慢:
def simple(my_list):
return [third_transformation(second_transformation(first_transformation(e)))
for e in my_list]
Timing it收益:
one_pass: 0.839533090591
multi_pass: 0.840938806534
simple: 0.569097995758
答案 1 :(得分:2)
假设您正在考虑一个程序,它可以很容易地成为一个具有多个操作的循环,或者多个循环各执行一个操作,那么它永远不会改变计算复杂性(例如,O(n)算法仍然是O(n)方式)。
单遍方法的一个优点是可以节省循环的“簿记”。无论迭代机制是递增还是比较计数器,还是检索“下一个”指针并检查null,或者其他什么,当你在一次传递中完成所有操作时,你就会减少它。假设您的操作完成了大量工作(并且您的循环机制简单明了,不会绕过昂贵的生成器或其他东西),那么这种“簿记”工作将因您的实际工作而相形见绌。操作,这使得这绝对是一个你不应该做的微优化,除非你知道你的程序太慢并且你已经耗尽了所有更重要的可用优化。
另一个优点是,在转移到下一个元素之前将所有操作应用于迭代的每个元素往往会从CPU缓存中获益更多,因为每个项目在后续操作中仍然可以在缓存中item,而使用多次传递使得几乎不可能(除非您的整个集合适合缓存)。 Python通过字典进行了很多间接,但是通过读取散布在整个内存空间的散列桶,每个单独的操作可能并不难以溢出缓存。所以这仍然是一个微观优化,但这种分析给了它更多的机会(虽然仍然不确定)产生重大差异。
多次传递的一个优点是,如果需要在循环迭代之间保持状态,则单次传递方法会强制您保持所有操作的状态。这可能会损害CPU缓存(可能每个操作的状态单独适合整个传递的缓存,但不是所有操作的状态放在一起)。在极端情况下,这种效果理论上可以使程序在内存中拟合而不是(我在一个正在咀嚼非常大量数据的程序中遇到过这种情况)。但在极端情况下,你知道你需要分解,非极端情况再次是微观优化,不值得提前做。
因此,表现通常倾向于单次通过一个微不足道的数量,但在某些情况下可能有利于单次通过或多次通过。您可以从中得出的结论与适用于所有编程的一般建议相同:首先以最清晰和可维护的方式编写代码,并且仍然充分地解决您的程序问题。只有当你有一个大部分完成的程序而如果结果是“不够快”,那么测量你的代码的各个部分的性能影响找出值得花时间的地方。
由于性能原因而担心是否编写单通道或多通道算法的时间几乎总是被浪费掉。因此,除非您拥有无限制的开发时间,否则您将从总体开发工作中获得“最佳”结果(最佳包括性能),不必担心此问题,并根据需要进行解决。< / p>
答案 2 :(得分:1)
与其他版本相比,您可能会减少任一版本中的缓存未命中数。这取决于那些转换函数实际上做了什么。
如果这些函数有很多代码并且在不同的数据集上运行(除了输入和输出),多通道可能会更好。否则单次传递可能会更好,因为当前列表元素可能会保持缓存,循环操作只执行一次而不是三次。
这是一个比较实际运行时间非常有用的案例。
答案 3 :(得分:1)
就个人而言,我无疑会更喜欢one_pass选项。它肯定表现更好。你可能是对的,差异不会很大。 Python已经非常好地优化了xrange迭代器,但是你的迭代次数仍然超过了所需的3倍。