来自: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. 简析一下SQL Server里面Fast_Forword 和 SRROLL 的区别

    这次简单说说游标的分类. 先看看通常游标的语法 DECLARE cursor_name CURSOR [ LOCAL :局部游标,仅在当前会话有效 | GLOBAL : 全局游标,全局有效,可以 ] ...

  2. 烂泥:yum的使用及配置

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 最近由于服务器需求,需要在公司内网搭建内网yum源. 搭建内网yum源需要分以下几个步骤,如下: 1. yum是什么 2. repo文件是什么 3. r ...

  3. HashMap的key可以是可变的对象吗???

    大家都知道,HashMap的是key-value(键值对)组成的,这个key既可以是基本数据类型对象,如Integer,Float,同时也可以是自己编写的对象,那么问题来了,这个作为key的对象是否能 ...

  4. hdu 3472 HS BDC(混合路的欧拉路径)

    这题是混合路的欧拉路径问题. 1.判断图的连通性,若不连通,无解. 2.给无向边任意定向,计算每个结点入度和出度之差deg[i].deg[i]为奇数的结点个数只能是0个或2个,否则肯定无解. 3.(若 ...

  5. 入门级的按键驱动——按键驱动笔记之poll机制-异步通知-同步互斥阻塞-定时器防抖

    文章对应视频的第12课,第5.6.7.8节. 在这之前还有查询方式的驱动编写,中断方式的驱动编写,这篇文章中暂时没有这些类容.但这篇文章是以这些为基础写的,前面的内容有空补上. 按键驱动——按下按键, ...

  6. 大话设计模式C++版——原则和引言

    转贴请注明转自:http://blog.csdn.net/gufeng99/article/details/45832711 读程杰的<大话设计模式>有一段时间了,将其C#版的设计模式代码 ...

  7. 第六章、Struts2数据校验

    一.三种实现方式 ① 用validate()方法实现数据校验 继承ActionSupport类,该类实现了Validateable接口,该接口中定义了一个validate()方法,在自定义的Actio ...

  8. ACCP 结业考试

    1) 在SQL Server 中,为数据库表建立索引能够(C ). 索引:是SQL SERVER编排数据的内部方法,是检索表中数据的直接通道 建立索引的作用:大大提高了数据库的检索速度,改善数据库性能 ...

  9. UI的重用性

    UI抽取思路 一款手机游戏中UI有几十个到上百个不等,如果一个一个做这些UI,无疑会花费很多时间. 近期我们的游戏UI已经是第N次改版了,经过这N多次的修改,我总结了UI其实有很多的共性(就是相同性) ...

  10. [No00005A]word多文档合一

    2个方法:法一,一个个插入,法二,一次性插入多个. 法一: 视图->大纲视图 点击 大纲 -> 显示文档 点击插入,逐个插入文档.. 最终 将视图调回页面视图..结束. 法二: 插入 - ...