Python管理内存,python基于值的自动内存管理方式?
堆上内存默认情况下,Spark只使用堆上内存。执行器端的堆内内存区域可以大致分为以下四个块:
Execution 内存:主要用于存储Shuffle、Join、Sort、Aggregation等计算时的临时数据。Storage 内存:主要用于存储spark的缓存数据,如RDD的缓存和展开数据;用户内存(User Memory):主要用于存储RDD转换操作所需的数据,如RDD依赖等信息。预留内存(Reserved Memory):系统预留内存,将用于存储Spark内部对象。如果整个执行器堆中的内存用图形表示,可以总结如下:
我们对上图做如下解释:
memory=runtime . get runtime . maxmemory实际上是由参数spark.executor.memory或- executor-memory配置的。ReservedMemory在Spark 2.2.1中写死,其值等于300MB。这个值不能修改(如果在测试环境下,我们可以通过参数spark.testing.reservedMemory修改);可用内存=系统内存-预留内存,也就是Spark的可用内存;
离堆内存)Spark 1.6开始引入离堆内存(详见SPARK-11389)。这种模式不在JVM中申请内存,而是调用Java的不安全API直接从操作系统申请内存,比如C语言的malloc()。因为这种模式不经过JVM内存管理,所以可以避免频繁的GC。这种内存应用的缺点是,你必须为内存应用和释放编写自己的逻辑。
默认情况下,堆外内存是关闭的。我们可以通过spark.memory.offHeap.enabled参数来启用它,通过spark.memory.offHeap.size来设置堆外内存的大小(以字节为单位)如果启用了堆外内存,那么在执行器中就会同时存在堆内内存和堆外内存,两者的使用是相辅相成的。此时,执行器中的执行内存是堆内执行内存和堆外执行内存之和,同理,存储内存也是如此。与堆内内存相比,堆外内存只区分执行内存和存储内存,其内存分布如下图所示:
图中的maxOffHeapMemory等于spark.memory.offHeap.size参数的配置。
执行记忆和储存记忆的动态调整细心的同学一定看到上面两张图中执行记忆和储存记忆之间有一条虚线。为什么?
用过Spark的同学应该知道,在Spark 1.5之前,执行内存和存储内存的分配是静态的。换句话说,如果执行内存不足,即使存储内存有大量空闲程序,也无法利用。或者反过来,达拉斯到礼堂这让我们很难调优内存,我们必须非常清楚地知道执行和存储的内存分配。目前,执行内存和存储内存可以相互共享。也就是说,如果执行内存不足,而存储内存空闲,则执行可以从存储中申请空间;反之亦然,达拉斯到礼堂因此,上图中的虚线表示执行内存和存储内存可以随操作动态调整,从而有效利用内存资源。执行存储器和存储存储器之间的动态调整可以总结如下:
具体实现逻辑如下:
程序提交时,我们会设置基本执行内存和存储内存区域(由spark.memory.storageFraction参数设置);程序运行时,如果双方空间不足,会存储到硬盘;将内存中的块存储到磁盘的策略是根据LRU规则执行的。如果自己空间不足,对方有空,可以借用对方的空间;(存储空间不足是指不够放下一个完整的块)执行内存的空间被对方占用后,对方可以将被占用的部分转移到硬盘上,然后‘归还’借用的空间。存储内存的空间被对方占用后,目前的实现是无法让对方‘回归’的,因为洗牌过程中需要考虑很多因素,实现复杂;此外,混洗过程生成的文件将在以后使用,而存储在高速缓冲存储器中的数据可能在以后不使用。
实际上Spark UI中显示的存储内存的可用内存等于堆内内存和堆外内存之和,计算公式如下:
对乃
内存=17179869184字节
reserved memory=300 MB=300 * 1024 * 1024=314572800
usable memory=system memory-reserved memory=17179869184-314572800=16865296384
totalOnHeapStorageMemory=usable memory * spark . memory . fraction
=16865296384 * 0.6=10119177830
对味
totalOffHeapStorageMemory=spark . memory . off heap . size=10737418240
storage memory=totalOnHeapStorageMemory
=(10119177830 10737418240)字节
=(20856596070/(1000 * 1000 * 1000))GB
=20.9 GB
注意,上面提到的借用对方内存,要求借用者和被借用者的内存类型相同,都是堆内内存或者堆外内存,堆内内存没有借用堆外内存的空间。
任务间的内存分配为了更好地利用已用内存,执行内存在执行器中运行的任务间共享。具体来说,Spark在内部维护一个HashMap来记录每个任务占用的内存。当一个任务需要在执行内存区申请numBytes内存时,首先判断HashMap中是否维护了该任务的内存使用量,如果没有,则将该任务的内存使用量设置为0,并添加到以TaskId为键,以memory usage为值的HashMap中。之后,为这个任务申请numBytes内存。如果执行内存区中的空闲内存刚好比numBytes多,那么在HashMap中将numBytes加到当前任务使用的内存中,然后返回;如果当前执行内存区不能申请每个任务的最小可用内存,则当前任务被阻塞,直到其他任务释放足够的执行内存后,该任务才能被唤醒。每个任务的可用执行内存大小范围从1/2n到1/n,其中n是当前执行器中运行的任务数。任务运行所需的最小内存是(1/2N *执行内存);当N=1时,任务可以使用所有的执行内存。
例如,如果执行内存大小为10GB,执行器中当前运行的任务数为5,则该任务可以申请的内存范围为10/(2 * 5) ~ 10/5,即1GB ~ 2GB的范围。
一个例子为了更好地理解上述堆内内存和堆外内存的用法,这里举一个简单的例子。
只使用了堆内内存。现在,我们提交的Spark作业具有以下内存配置:
-执行器-内存18g
由于spark.memory.fraction和spark.memory.storageFraction的参数没有设置,我们可以看到关于存储内存的Spark UI显示如下:
图中明确显示存储内存可用内存为10.1GB,这个数字是怎么来的?根据前面的规则,我们可以得到以下计算结果:
system memory=spark . executor . memory
保留内存=300MB
usableMemory=系统内存-保留的内存
storage memory=usable memory * spark . memory . fraction * spark . memory . storage fraction
如果我们代入数据,我们会得到以下结果:
内存=18GB=19327352832字节
reserved memory=300 MB=300 * 1024 * 1024=314572800
usable memory=system memory-reserved memory=19327352832-314572800=19012780032
storage memory=usable memory * spark . memory . fraction * spark . memory . storage fraction
=19012780032 * 0.6 * 0.5=5703834009.6=5.312109375 GB
没有,和上面的10.1GB不匹配,为什么?这是因为Spark UI中显示的存储内存的可用内存实际上等于执行内存和存储内存之和,即可用内存* spark.memory.fraction:
storage memory=usable memory * spark . memory . fraction
=19012780032 * 0.6=11407668019.2=10.62421 GB
还是错了,这是因为虽然我们设置了- Executor-memory 18g,但是Spark的Executor通过Runtime.getRuntime.maxMemory得到的内存其实并没有那么大,只有17179869184字节,所以systemMemory=17179869184,然后计算出来的数据如下:
内存=17179869184字节
reserved memory=300 MB=300 * 1024 * 1024=314572800
usable memory=system memory-reserved memory=17179869184-314572800=16865296384
storage memory=usable memory * spark . memory . fraction
=16865296384 * 0.6=9.42421875 GB
我们把上面的16865296384 * 0.6字节除以1024 * 1024 * 1024,换算成9.42421875 GB,还是和UI上显示的不匹配。这是因为Spark UI通过除以1000 * 1000 * 1000将字节转换为GB,如下所示:
内存=17179869184字节
reserved memory=300 MB=300 * 1024 * 1024=314572800
usable memory=system memory-reserved memory=17179869184-314572800=16865296384
storage memory=usable memory * spark . memory . fraction
=16865296384 * 0.6字节=16865296384 * 0.6/(1000 * 1000 * 1000)=10.1 GB
现在终于吻合了。
具体字节换算成GB的计算逻辑如下(核心模块下的/core/src/main/resources/org/Apache/spark/ui/static/utils . js):
函数格式字节(字节,类型){
如果(类型!==display )返回字节;
if (bytes==0)返回‘0.0b’;
var k=1000
var DM=1;
var size=[ B , KB , MB , GB , TB , PB , EB , ZB , YB ];
var i=Math.floor(Math.log(字节)/math . log(k));
返回parseFloat((bytes/Math.pow(k,I))。toFixed(dm))“尺寸[I];
}
我们设置- Executor-memory 18g,但是Spark的Executor通过Runtime.getRuntime.maxMemory得到的内存其实没那么大,只有17179869184字节。这个数据是怎么算出来的?
Runtime.getRuntime.maxMemory是程序可以使用的最大内存,它的值将小于实际配置的执行器内存的值。这是因为内存分配池的堆部分分为三个空间:机智舞、幸存者、终身保有,里面有两个幸存者区域,我们在任何时候都只能使用这两个幸存者区域中的一个,所以可以用下面的公式来描述:
执行者记忆=机智之舞2 *幸存者持续
Runtime.getRuntime.maxMemory=机智之舞,幸存者终身制
以上17179869184字节可能由于GC配置不同,数据有所不同,但以上计算公式是相同的。
使用堆内和堆外内存。现在,如果我们启用堆外内存,会发生什么?我们的内存相关配置如下:
火花.执行者.内存18g
spark . memory . off heap . enabled true
spark . memory . offheap . size 10747 . 48868868687
从上面可以看到,堆外内存是10GB,现在Spark UI上显示的存储内存可用内存是20.9GB,如下:
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。