考虑一个例子:
import numpy as np
x = np.ones((100, 100))
y = np.ones((100, 100))
z = np.ones((100, 100))
f = x * y + y * z
f
时,numpy如何继续执行并管理内存以存储中间结果?f
转换为f = (x + z) * y
?f = (x + z) * y
,那么numpy会为f
在内存旁边分配任何临时内存吗?或者考虑另一个例子:
f = a + b + c # all of them of same dimension
[编辑]在@Daniel的回答之后,类似的基准测试显示numpy为此表达式分配了两次内存。
如果您能指出一些文档或相关的编译器技术,那将会很有帮助。谢谢!
答案 0 :(得分:1)
似乎numpy没有做任何优化,但也许更令人惊讶的是,Theano也没有。
这是一个比较每个实现的两个变体的脚本。输出如下。
import timeit
import numpy
import theano
import theano.tensor as tt
def main():
size = 1000
iterations = 1000
a = tt.matrix()
b = tt.matrix()
c = tt.matrix()
f1 = theano.function(inputs=[a, b, c], outputs=a * b + b * c)
f2 = theano.function(inputs=[a, b, c], outputs=(a + c) * b)
theano.printing.debugprint(f1)
theano.printing.debugprint(f2)
x = numpy.ones((size, size))
y = numpy.ones((size, size))
z = numpy.ones((size, size))
result = x * y + y * z
start = timeit.default_timer()
for _ in xrange(iterations):
result1 = x * y + y * z
assert numpy.all(result1 == result)
print timeit.default_timer() - start
start = timeit.default_timer()
for _ in xrange(iterations):
result2 = (x + z) * y
assert numpy.all(result2 == result)
print timeit.default_timer() - start
start = timeit.default_timer()
for _ in xrange(iterations):
result3 = f1(x, y, z)
assert numpy.all(result3 == result)
print timeit.default_timer() - start
start = timeit.default_timer()
for _ in xrange(iterations):
result4 = f2(x, y, z)
assert numpy.all(result4 == result)
print timeit.default_timer() - start
main()
我得到以下输出:
Elemwise{Composite{((i0 * i1) + (i1 * i2))}} [@A] '' 0
|<TensorType(float64, matrix)> [@B]
|<TensorType(float64, matrix)> [@C]
|<TensorType(float64, matrix)> [@D]
Elemwise{Composite{((i0 + i1) * i2)}} [@A] '' 0
|<TensorType(float64, matrix)> [@B]
|<TensorType(float64, matrix)> [@C]
|<TensorType(float64, matrix)> [@D]
9.19932313948
6.43367212255
4.15276831469
4.07725744595
因此,手动优化的版本对于numpy和theano都更快,但与Theano的差异更小。打印出的Theano计算图表显示Theano的优化编译器没有自动改进计算。 Theano的版本总体上更快。
请注意,这些结果可能会因操作的矩阵大小而有所不同。
答案 1 :(得分:1)
不,numpy
没有做任何此类优化。
应该怎么做? Numpy在c扩展中实现n维数组对象。剩下的就是纯粹的蟒蛇。 numpy
永远不会看到您要评估的实际表达式。它按evaluation order (see python documentation)订单的规定一次执行一个操作。因此,对于每个中间体,numpy
将必须分配临时内存。因此:
f = (x*y) + (y*z)
(为了清晰起见,括号)获得三个内存分配,最后一个用于f
。没有表达重写(这甚至是危险的,因为它可能会改变舍入效果)。f = (x+z)*f
:两次分配,最后一次分配到f
。f = (x + y) + z
:还有两个分配。 numpy
当然不是完全不可能知道你正在评估的是什么表达,但是如果没有解释器的帮助,所有这些都是肮脏的伎俩,必然会混淆用户。 numpy
没有做任何事情。