一.引言

org.apache.spark.util.SizeEstimator 类提供了 estimate 方法,该方法估计给定对象在JVM堆上占用的字节数。估计包括给定对象引用的对象占用的空间、它们的引用等。使用场景主要用于 spark 计算 broadCast 的内存容量,因为是 estimate ,所以对于指定对象在 JVM 上占用的字节数只是估算,而非实际。

二.使用

    val yourClass = Class.forName("className")val size = SizeEstimator.estimate(yourClass)println(s"预估大小: ${size}")

yourClass 为要预估的类,可以是 Scala / Java 原生的数据类,也可以是自己定义的任意 Class。

esitimte 会对 yourClass 的 ClassInfo 进行大小估算,ClassInfo 包含 shellSize 和 pointerFilelds 两个属性代表该类的缓存大小:

  private class ClassInfo(val shellSize: Long,val pointerFields: List[Field]) {}

A.shellSize:

所有非静态字段的大小加上 java.lang.Object大小,可以理解为 class 内自己定义的变量

B.pointerFileds:

指向对象的任何字段,例如 class 内的 HashMap 存在 key 指向对应的 value

3.注意事项

1.字节估算

SizeEstimator.estimate 主要依赖  estimate 方法和 visitSingleObject 方法估算一个 class 的大小,其内部采用队列实现,通过 enqueue 将对象放入队列,随后 while 循环 dequeue 累加大小,直到队列为空。

A.主方法 estimate

用于初始化记录状态的 SearchState,随后调用 visitSingleObject 判断每个对象的大小

  private def estimate(obj: AnyRef, visited: IdentityHashMap[AnyRef, AnyRef]): Long = {val state = new SearchState(visited)state.enqueue(obj)while (!state.isFinished) {visitSingleObject(state.dequeue(), state)}state.size}

B.辅助方法 visitSingleObject

该方法会对队列中的 Object 进行估算,同时忽略 scala.reflect 和 classLoader 相关的对象,因为前者引用全局反射对象,而全局反射对象又引用许多其他大型全局对象,后者引用了整个REPL,这些引用都会混淆估算器评估对应类的大小。

case _ 可以看到具体的累加过程,首先添加类的全部 shellSize,即内部变量大小,随后对于所有带有引用的对象,也会压入队列进行递归的计算,直到对列清空。

  private def visitSingleObject(obj: AnyRef, state: SearchState) {val cls = obj.getClassif (cls.isArray) {visitArray(obj, cls, state)} else if (cls.getName.startsWith("scala.reflect")) {// Many objects in the scala.reflect package reference global reflection objects which, in// turn, reference many other large global objects. Do nothing in this case.} else if (obj.isInstanceOf[ClassLoader] || obj.isInstanceOf[Class[_]]) {// Hadoop JobConfs created in the interpreter have a ClassLoader, which greatly confuses// the size estimator since it references the whole REPL. Do nothing in this case. In// general all ClassLoaders and Classes will be shared between objects anyway.} else {obj match {case s: KnownSizeEstimation =>state.size += s.estimatedSizecase _ =>val classInfo = getClassInfo(cls)state.size += alignSize(classInfo.shellSize)for (field <- classInfo.pointerFields) {state.enqueue(field.get(obj))}}}}

Tips:

由于指向性对象会递归压入队列,当 class 内有同一类的多个指向索引时,该 class 的估算值会增大,因为多个索引指向同一对象,其实际占用字节在内存中只有一份,而递归累加会累加多次。

    class testEstimate {val mapA = new mutable.HashMap[String, Object]()val mapB = new mutable.HashMap[Int, Object]()val mapC = new mutable.HashMap[Double, Object]()val testObject = new Object()mapA("string") = testObjectmapB(1) = testObjectmapC(1D) = testObject}

例如预估 testEstimate 类的大小时,mapA,mapB,mapC 均指向了同一对象 testObject,实际占用大小为 Size(testObject) x1,但 visitSingleObject 方法会针遍历 class 的 classInfo.pointerFields 从而导致估算的值中累加了 Size(testObject) x 3 的数量,所以当类内指向性结构很多时,估算的准确性会下降。

2.效率问题

esitmate 内部采用队列 + 递归的形式遍历 class 内全部变量与全部引用对象及其变量和引用,所以当类特别复杂时,estimate 会比较耗时,即该方法存在性能问题,有性能需求的小伙伴要注意该方法在代码中的使用。

A.估算一个数组

  def estimator(k: Int): Unit = {val testArray = (0 to k).toArrayval st = System.currentTimeMillis()val size = SizeEstimator.estimate(testArray)println(s"预估大小: ${size} 耗时: ${System.currentTimeMillis() - st}")estimator(10000)
预估大小: 40024 耗时: 324

B.估算一个DataBase

    val start = System.currentTimeMillis()val dataBaseSize = SizeEstimator.estimate(dataBase)val end = System.currentTimeMillis()println(s"DB size: $dataBaseSize 耗时: ${end - st}")
DB size: 2605368184 耗时: 44033

可以看到估算一个较大的对象时,该方法的耗时还是比较可观的,所以代码中要慎用,因为耗时问题查了好久最后定位是这个方法 o(╥﹏╥)o

Spark - SizeEstimator.estimate 字节估算之时间都去哪了相关推荐

  1. Spark SQL 在字节跳动的核心优化实践

    10月26日,字节跳动技术沙龙 | 大数据架构专场 在上海字节跳动总部圆满结束.我们邀请到字节跳动数据仓库架构负责人郭俊,Kyligence 大数据研发工程师陶加涛,字节跳动存储工程师徐明敏,阿里云高 ...

  2. spark存储模块之内存存储--MemeoryStore

    MemeoryStore 上一节,我们对BlockManager的主要写入方法做了一个整理,知道了BlockMananger的主要写入逻辑,以及对于块信息的管理.但是,由于spark的整个存储模块是在 ...

  3. Spark Shuffle源码分析系列之PartitionedPairBufferPartitionedAppendOnlyMap

    概述 SortShuffleWriter使用ExternalSorter进行ShuffleMapTask数据内存以及落盘操作,ExternalSorter中使用内存进行数据的缓存过程中根据是否需要ma ...

  4. Spark源码解读之Shuffle计算引擎剖析

    Shuffle是Spark计算引擎的关键所在,是必须经历的一个阶段,在前面的文章中,我们剖析了Shuffle的原理以及Map阶段结果的输出与Reduce阶段结果如何读取.该篇文章是对前面两篇文章 [S ...

  5. sparkcore分区_Spark学习:Spark源码和调优简介 Spark Core (二)

    本文基于 Spark 2.4.4 版本的源码,试图分析其 Core 模块的部分实现原理,其中如有错误,请指正.为了简化论述,将部分细节放到了源码中作为注释,因此正文中是主要内容. 第一部分内容见: S ...

  6. Spark源码和调优简介 Spark Core

    作者:calvinrzluo,腾讯 IEG 后台开发工程师 本文基于 Spark 2.4.4 版本的源码,试图分析其 Core 模块的部分实现原理,其中如有错误,请指正.为了简化论述,将部分细节放到了 ...

  7. Spark官方调优文档翻译(转载)

    Spark调优 由于大部分Spark计算都是在内存中完成的,所以Spark程序的瓶颈可能由集群中任意一种资源导致,如:CPU.网络带宽.或者内存等.最常见的情况是,数据能装进内存,而瓶颈是网络带宽:当 ...

  8. Spark 安装部署与快速上手

    核心概念 Spark 是 UC Berkeley AMP lab 开发的一个集群计算的框架,类似于 Hadoop,但有很多的区别. 最大的优化是让计算任务的中间结果可以存储在内存中,不需要每次都写入 ...

  9. [spark] 内存管理 MemoryManager 解析

    概述 spark的内存管理有两套方案,新旧方案分别对应的类是UnifiedMemoryManager和StaticMemoryManager. 旧方案是静态的,storageMemory(存储内存)和 ...

最新文章

  1. SpringBoot第二十五篇:2小时学会springboot
  2. 【Linux】一步一步学Linux——groups命令(93)
  3. 开源系统管理资源大合辑
  4. spring-boot注解详解(七)
  5. python bottle框架 运维_python bottle 框架实战教程:任务管理系统 V_1.0版 | linux系统运维...
  6. 小程序开发(13)-location定位
  7. 建立一个成功的OpenStack组
  8. Spring IoC反转控制的快速入门
  9. 显示2位小数 python3_自动化常用语言python,版本3和2有何变化?
  10. 029 Android WebView的使用(用来显示网页)
  11. stm32c语言arctan函数,超高速的反正切算法,纯整数运算
  12. 通用数据链接(UDL)的用法
  13. 保活 进程唤醒_Android 8.0以上系统应用如何保活
  14. 训练集、验证集和测试集的比较
  15. 【JavaSE基础 】Eclipse教程
  16. 2021年10月数学一及第十三届大数赛部分复习
  17. 宠物王国6java变态版,宠物王国外传999999级变态版
  18. 重做raid后,重启无法进入系统
  19. 特征图注意力_深度学习入门之注意力机制
  20. 上海域格CLM920_NC3模块连接oneNET平台

热门文章

  1. 昆明第十二中学计算机笔试题目,昆明市第十二中学2015年招考笔试、面试、课堂考核安排公告...
  2. suspend(挂起)和resume(继续执行)线程
  3. 单片机六位抢答器c语言程序,单片机八人抢答器程序设计
  4. WIFI智能家居之智能插座
  5. 基于音频和文本的多模态语音情感识别(一篇极好的论文,值得一看哦!)
  6. 2022年保研经验贴建议个人经历:计算机软件工程
  7. URL解码:比照法的问题
  8. C++中方括号[]的作用总结
  9. windows注册表命令大全
  10. 谨慎使用Encoding.Default