来自:http://deeplearning.net/software/theano/tutorial/aliasing.html

Understanding Memory Aliasing for Speed and Correctness

内存的重用是theano提升代码运行速度的一种方法,而且理解theano如何别名(alias)缓冲区对编写的程序速度的提升和正确性的保证很重要。

这部分是基于theano处理内存的基础上来说明原则的,而且解释了为了获得更快的性能,在什么时候该改变某些函数默认的行为和方法。

一、内存模型:两个空间

可以通过一些简单的原则来指导theano的函数处理。首先,theano会管理一个内存池,并且在这个池中theano会追踪值的变化。

  • Theano 会管理它自己的内存空间,并且通常不会与非theano代码的python变量的内存相重叠。
  • Theano 函数只修改在theano内存空间中的缓冲区。
  • Theano的内存空间包括分配给存储shared变量和临时用来执行函数的缓冲区。
  • 物理意义上来说,theano的内存空间会跨越主机,GPU设备,而且在未来可能会涉及到远程机器上的对象。
  • 分配给shared变量的内存缓冲区是唯一的:它不会被另一个shared变量所别名(aliased)。
  • Theano管理的内存在theano函数未运行和theano的库代码未运行的时候是保持不变的。
  • 一个函数的默认行为是返回用户空间的变量作为输出,然后期待用户空间的变量作为输入。

介于theano管理的内存和用户管理的内存的区别可以通过一些theano函数(例如,sharedget_value 和针对于In和Out的构造函数)和 borrow=True flag来细分。这可以让一些方法在冒着微妙的bug的风险下在整体的程序 (通过别名内存)上更快 (避免了复制操作)

该章节剩下的部分意在帮助你理解什么时候使用 borrow=True 参数是安全的,并且可以让代码更快的运行。

二、Borrowing when 创建共享变量

borrow 可以作为共享变量构造函数的参数:

import numpy, theano
np_array = numpy.ones(2, dtype='float32') s_default = theano.shared(np_array)
s_false = theano.shared(np_array, borrow=False)
s_true = theano.shared(np_array, borrow=True)

默认情况(s_default)
和显式的设置borrow=False 这两种情况下,构造的共享变量是对np_array进行深度复制的。所以我们后续对np_array的改变不会影响到这两个共享变量。

np_array += 1 # now it is an array of 2.0 s改变的操作

s_default.get_value()  # -> array([1.0, 1.0])
s_false.get_value() # -> array([1.0, 1.0])
s_true.get_value() # -> array([2.0, 2.0])

如果我们在cpu上运行这段代码,那么我们对np_array的改变可以通过 s_true.get_value()看到,因为Numpy
arrays是可变的,而且s_ture使用了np_array对象作为它的内部缓冲。

然而,对np_array的别名和 s_true 没法保证这种情况一定发生,也许会临时性的发生,也许根本不发生。 没法保证是因为当theano使用的是gpu的时候,那么borrow 这个flag是没有任何影响的。只能临时性的发生是因为如果我们调用一个theano函数来更新s_ture的值,那么这个别名关系也许会,也许不会被打破 (该函数允许通过修改它的缓冲区来更新这个shared变量,这会保留这个别名,或者改变这个变量指向的缓冲区,而这会终止这个别名)。

要点(Take home message:):

当shared变量代表一个大的对象的时候,在shared变量的构造函数中使用borrow=True是安全的操作(也是好主意) (在内存占用方面) ,而且你不需要在内存中对它进行复制。

想要利用副作用,从而通过使用 borrow=True 来修改 shared 变量的方法不是一个别名技术,因为在一些设备上
(例如GPU 设备)该技术不会生效的。

三、Borrowing when 访问共享变量的值

检索

borrow 参数可以同样用来控制如何检索 shared 变量的值。

s = theano.shared(np_array)

v_false = s.get_value(borrow=False) # N.B. borrow default is False
v_true = s.get_value(borrow=True)

当 borrow=False 传递给 get_value, 的时候,意味着返回的值不会是对theano的内部的内存进行别名。当borrow=True 传递给 get_value的时候,意味着返回的值可能是对theano的内部的内存的别名。不过这两种调用都会对内部的内存进行复制,产生副本。

使用 borrow=True 还仍然会进行复制的原因是因为shared变量的内部的表达并不是和你想的一样。当你通过传递一个NumPy
数组来创建一个shared变量的时候,例如,然后 get_value() 也必须返回一个NumPy
数组。这就是为什么theano可以让gpu的使用看起来透明的原因。不过当你使用gpu的时候(或在未来可能是一个远程机器),那么 numpy.ndarray 就不是你数据的中间表示了。如果你真的想要theano返回它的中间表示且不想对它进行复制,那么你应该对get_value函数使用return_internal_type=True 参数。它将不会 cast内部对象
(总是在常量时间内返回的),不过也许会由环境因素(例如:计算设备,Numpy数组的dtype)而返回各种不同的数据类型。

v_internal = s.get_value(borrow=True, return_internal_type=True)

可以将 borrow=False 和 return_internal_type=True结合起来使用 ,这会返回内部对象的一个深度复制。这对内部的调试来说是很重要的,而不是对通常的使用而言的。

我们可以透明使用theano对不同类型的优化,有个原则就是当shared变量创建之后, get_value() 在默认情况下总是会返回和它接受的同一个对象类型。所以如果你在gpu上手动创建了数据,然后用这个数据在gpu上创建一个共享变量。 当return_internal_type=False的时候get_value总是会返回gpu上的数据。

要点(Take home message:):

当你的代码没有修改返回的值的时候使用get_value(borrow=True) 是安全的 (而且有时候也更快)。不要使用副作用(side-effect)来修改一个“shared”变量,因为它会让你的代码具有设备依赖。通过副作用的方法来修改gpu变量是不可能的。

分配

 
  Shared
 变量同样有一个 set_value 方法可以接受一个可选的 borrow=True 参数。该语义相似于那些创建新shared变量的语义, borrow=False 是默认的,而且 borrow=True 意思是theano可能会重用作为变量内部存储的缓冲区。

手动更新shared变量的值的一个标准的模式是:

s.set_value(
some_inplace_fn(s.get_value(borrow=True)),
borrow=True)

该模式工作的时候是不管计算设备是什么的,当后者(应该说的是设备)可能会在没有复制的情况下暴露theano的内部变量,那么它就会和in-place一样快的速度进行更新。

当在gpu上分配 shared 变量的时候,gpu与主机之间的内存迁移是很耗时的。这里是一些提示,用于却跑如何快速和高效的使用gpu的内存和带宽:

  • 在Theano 0.3.1之前, set_value 在gpu上不是以in-place方式工作的。也就是说,有时候gpu对于旧的内存没有释放之前就对新变量进行内存的分配了。如果你在gpu内存上运行的已经接近于上限了,这可能会导致你得到gpu内存已经耗尽的提示。

    解决方法:更新到最新的theano。

  • 如果你打算反复的对一个共享变量的进出数据块进行交换,你可能会想要重用第一次分配的内存I,这可以更快而且更好的使用内存

         解决方法: 更新到最新版本的theano
(>0.3.0)并考虑 padding源数据来确保每个块都有着相同的size。

  • 同样值得提到的就是,当前gpu的复制只支持连续的内存。所以theano必须先将你提供的值转换成c-连续形式,然后在对它进行复制。这需要额外的在主机上对数据进行复制。

    解决方法:确保你想要赋值给 CudaNdarraySharedVariable 的数据已经是C-连续了。

可以在sandbox.cuda.var
– The Variables for Cuda-allocated arrays
上面找到当前gpu版本的set_value()的实现过程。

四、Borrowing when 构造函数对象

borrow 参数同样可以提供给 In 和 Out 对象来控制theano.function 是如何处理它的参数
argument[s] 和返回值value[s]的:

import theano, theano.tensor

x = theano.tensor.matrix()
y = 2 * x
f = theano.function([theano.In(x, borrow=True)], theano.Out(y, borrow=True))

Borrowing 一个输入意味着theano会将你提供的参数暂时的视为是theano池的一部分。因此,在执行函数(例如,f)的时候,在对其他变量进行计算的过程中,你的输入可能会作为一个缓冲区而重用(和重写)。

Borrowing 一个输出意味着theano不会在每次调用函数的时候坚持分配一个新的输出缓冲区。它可能会在之前的调用的基础上重用同一个,并重写旧的内容。因而,它会通过副作用来重写旧的返回值。这些返回值也会在执行另一个编译好的函数上被重写 (例如,输出会被别名成一个shared变量)。所以在调用更多的theano函数之前,小心使用一个 borrowed 返回值。默认情况下当然是不borrow 内部结果的。

同样可以传递一个 return_internal_type=True flag
给 Out 变量,该变量有着和对shared变量的get_value函数设置return_internal_type flag时一样的解释。不同于 get_value(),

return_internal_type=True
 和 borrow=True 参数的结合,然后传给 Out() 不能保证说避开了对一个输出值的复制。他们只是隐式的对graph的编译和优化提供了更多的灵活性。

对于 GPU的graphs来说,borrowing是影响速度的主要因素:

from theano import function, config, shared, sandbox, tensor, Out
import numpy
import time vlen = 10 * 30 * 768 # 10 x # cores x # threads per core
iters = 1000 rng = numpy.random.RandomState(22)
x = shared(numpy.asarray(rng.rand(vlen), config.floatX))
f1 = function([], sandbox.cuda.basic_ops.gpu_from_host(tensor.exp(x)))
f2 = function([],
Out(sandbox.cuda.basic_ops.gpu_from_host(tensor.exp(x)),
borrow=True))
t0 = time.time()
for i in xrange(iters):
r = f1()
t1 = time.time()
no_borrow = t1 - t0
t0 = time.time()
for i in xrange(iters):
r = f2()
t1 = time.time()
print 'Looping', iters, 'times took', no_borrow, 'seconds without borrow',
print 'and', t1 - t0, 'seconds with borrow.'
if numpy.any([isinstance(x.op, tensor.Elemwise) and
('Gpu' not in type(x.op).__name__)
for x in f1.maker.fgraph.toposort()]):
print 'Used the cpu'
else:
print 'Used the gpu'

结果:

$ THEANO_FLAGS=device=gpu0,floatX=float32 python test1.py
Using gpu device 0: GeForce GTX 275
Looping 1000 times took 0.368273973465 seconds without borrow and 0.0240728855133 seconds with borrow.
Used the gpu

要点(Take home message:):

当在函数返回值后,输入x 对于函数来说是不需要的,你可以将它做为额外的工作空间,然后考虑使用 In(x, borrow=True)来对它进行标记 。它可以让函数变得更快,而且减少对内存的需求。当一个返回值y
很大(内存占用方面),你只需要当它返回的时候,读取它一次然后考虑对它进行标记 Out(y, borrow=True)。

参考资料:

[1] 官网:http://deeplearning.net/software/theano/tutorial/aliasing.html

Theano2.1.14-基础知识之理解为了速度和正确性的内存别名的更多相关文章

  1. C#基础知识之理解HTTP协议

    在互联网时代HTTP协议的重要性无需多言,对于技术岗位的同学们来说理解掌握HTTP协议是必须的.本篇博客就从HTTP协议的演进.特性.重要知识点和工作中常见问题的总结等方面进行简单的介绍.理解掌握了这 ...

  2. C#基础知识之理解Cookie和Session机制

    会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话.常用的会话跟踪技术是Cookie与Session.Cookie通过在客户端记录信息确定用户身份,Session通过在服务器端 ...

  3. Android基础知识—Context理解及使用

    Context是Android中一个非常重要的概念,用于访问全局信息,几乎所有的基础组件都继承自 Context,理解 Context 对于学习 Android 四大基本组件非常有帮助. 1. Con ...

  4. C# 篇基础知识1——编译、进制转换、内存单位、变量

    编译:C#语言要经过两次编译,程序员编写好源代码后进行第一次编译,将源代码编译为微软中间语言(MSIL),生成可以发布的应用软件:当用户使用软件时,MSIL代码会在首次载入内存后进行第二次编译,中间语 ...

  5. Java基础知识强化之IO流笔记58:内存操作流

    1. 内存操作流: 用来操作处理临时存储的信息的. (1)操作字节数组: ByteArrayInputStream ByteArrayOutputStream 代码示例: package cn.itc ...

  6. 浅析C++基础知识

    近期想对C++的面试题目进行一下更加详细的整理.事实上认真思考一下C++程序猿的面试,我们能够发现对程序猿的能力的考察总是万变不离当中,这些基础知识主要分为五部分:一. C/C++基础知识 二. C/ ...

  7. python基础知识的学习和理解

    参考链接:https://github.com/yanhualei/about_python/tree/master/python_learning/python_base   python基础知识笔 ...

  8. 理解RxJava:(一)基础知识

    理解RxJava:(一)基础知识 本文翻译自Grokking RxJava, Part 1: The Basics,著作权归原作者danlew所有.译文由JohnTsai翻译.转载请注明出处,并保留此 ...

  9. [C# 基础知识系列]专题九: 深入理解泛型可变性

    引言: 在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类 ...

随机推荐

  1. sqlserver删除所有表(表结构和数据)

    要删除某个数据库,或者删除数据库中的所有表(删除表结构和数据),需要先删除表间的外键约束,才能删除表.如删除数据库db_wy中的所有表: --/第1步**********删除所有表的外键约束***** ...

  2. Android中的单例模式

    定义: 单例模式:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 使用场景: 确保某一个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一 ...

  3. 每日Scrum(5)

    进入冲刺第五天,软件的界面设计成为主打,收集学校的很多美图是我们组的任务: 问题在于软件已很难有很大的改进,大方向也都是变不了的

  4. [Config]Zabbix的Mongodb插件安装,centos

    1.yum install php-devel php-pear httpd-devel 2.安装mongo php驱动,pecl install mongo 3.pecl install mongo ...

  5. Linux 引导修复

    前些天,我的Ubuntu老提示"Filesystem root"空间不足,于是,我煞笔的用win pe去扩展空间,结果,空间扩展不成,反倒丢失了引导..... 于是就上网查资料,看 ...

  6. POI读写Word docx文件

    使用POI读写word docx文件 目录 1     读docx文件 1.1     通过XWPFWordExtractor读 1.2     通过XWPFDocument读 2     写docx ...

  7. 如何生成可变表头的excel

    1.实现功能: 传入一个表头和数据,将数据导入到excel中. 为了便于项目的扩展,数据传入通过泛型集合传入,获取数据时,通过反射的方式获取,这样无论你的表头是多少项,我都能很方便的生成.另外为了便于 ...

  8. C# .NET 动态调用webservice的三种方式

    转载自 百度文库 http://wenku.baidu.com/link?url=Q2q50wohf5W6UX44zqotXFEe_XOMaib4UtI3BigaNwipOHKNETloMF4ax4W ...

  9. linux运行级别[转自网络]

    运行级别就是操作系统当前正在运行的功能级别.级别是从0到6,具有不同的功能.这些级别定义在/ect/inittab文件中.这个文件是init 程序寻找的主要文件,最先运行的服务是那些放在/etc/rc ...

  10. [转]ASP.NET MVC 3 Razor + jqGrid 示例

    本文转自:http://www.cnblogs.com/think8848/archive/2011/07/15/2107828.html 前些天写了一篇有关jqGrid的文章,因为是公司项目的原因一 ...