Spark中的堆外和堆内内存以及内部行数据表示UnsafeRow

Spark Word Count深度解析:从单机HashMap到分布式DAG Word Count是理解分布式计算范式的经典入口,其本质远超单词统计——它揭示了数据分片、惰性求值、Shuffle机制与容错血缘等核心原理。作为Apache Spark的‘Hello World’,它承载着RDD抽象、DAG执行图和Tungsten优化引擎的技术价值,广泛应用于日志分析、实时流处理与数仓ETL等生产场景。通过对比单机Java实现与Spark分布式实现,可清晰把握从本地内存计算到集群协同调度的关键跃迁。本文聚焦Spark Java API下的Word Count实践,深入解析reduceBy 阅读详情

背景

本文基于 Spark v4.0.0
写此文章的目的是为了说明 Spark计算引擎在堆内和堆外内存的处理,以及在内部行处理的优化。

分析

堆外和堆内内存

Spark中的内存分为执行内存和存储内存,
执行内存用来在用来缓存在shufle过程中的一些数据,比如说 shuffle 用来排序的内存。
存储内存用来缓存RDD数据以及broadcast的数据等。

具体的代码在 MemoryManager 中:

 private[memory] final val tungstenMemoryAllocator: MemoryAllocator = {
    tungstenMemoryMode match {
      case MemoryMode.ON_HEAP => MemoryAllocator.HEAP
      case MemoryMode.OFF_HEAP => MemoryAllocator.UNSAFE
    }
  }
...

 MemoryAllocator UNSAFE = new UnsafeMemoryAllocator();

 MemoryAllocator HEAP = new HeapMemoryAllocator();
  • 执行内存中如何被使用
    以 ShuffleExternalSorter举例,在进行shuffle数据写入的时候,会经过如下数据流:
UnsafeShuffleWriter.write
    ||
    \/
insertRecordIntoSorter
    ||
    \/
ShuffleExternalSorter.insertRecord
    ||
    \/
acquireNewPageIfNecessary
    ||
    \/
allocatePage
    ||
    \/
TaskMemoryManager.allocatePage
    ||
    \/
memoryManager.tungstenMemoryAllocator().allocate(acquired)
    ||
    \/
MemoryAllocator.allocate // UnsafeMemoryAllocator 或者 HeapMemoryAllocator
  • 存储内存如何被使用
    以缓存RDD的结果为例,经过的数据流如下:
 Executor.run
    ||
    \/
 env.blockManager.putByte
    ||
    \/
 BlockStoreUpdater.save()
    ||
    \/
 saveSerializedValuesToMemoryStore
    ||
    \/
 MemoryManager.putBytes
    ||
    \/
 MemoryManager.acquireStorageMemory
    ||
    \/
 storagePool.acquireMemory // StorageMemoryPool(this, MemoryMode.ON_HEAP)或者StorageMemoryPool(this, MemoryMode.OFF_HEAP)
  • 堆内堆外内存的分配和释放

UnsafeMemoryAllocator.allocate 的方法调用Unsafe.allocateMemory的方法,得到一个native的地址offeset:

 long address = Platform.allocateMemory(size);
 MemoryBlock memory = new MemoryBlock(null, address, size);

UnsafeMemoryAllocator.free直接调用 Platform.freeMemory(memory.offset);进行内存的释放:

Platform.freeMemory(memory.offset);

HeapMemoryAllocator.allocate的方法直接new一个Long类型的数组:

long[] array = new long[numWords];
MemoryBlock memory = new MemoryBlock(array, Platform.LONG_ARRAY_OFFSET, size);

HeapMemoryAllocator.free的方法是通过释放java对象的引用来达到释放该内存对象的目的:

 memory.setObjAndOffset(null, 0);
 LinkedList<WeakReference<long[]>> pool =
          bufferPoolsBySize.computeIfAbsent(alignedSize, k -> new LinkedList<>());
        pool.add(new WeakReference<>(array));

并且通过弱引用来引用该对象,方便二次利用(如果该对象没有被gc释放的话)。

UnsafeRow

这个UnsafeRow是Spark InternalRow的一种实现,他的作用

  1. 减少 JVM GC 的压力
  2. 将 JVM 对象序列化为字节数组进行存储,且不影响正常的数据操作,减少了数据存储内存,可以精确计算内存的使用情况
  3. 减少了 Shuffle 过程中序列化和反序列化的的消耗
  • 减少GC压力
    相比SpecificInternalRow / GenericInternalRow 都是以Array进行存储的的,而且Scala中所有的数据类型都是对象,没有java中原生类型的概念。
    比如说 Int final abstract class Int extends _root_.scala.AnyVal 其实是一个抽象类。
    这种采用了对象存储的方式,会天然存在GC的问题
    而 UnsafeRow 采用字节数组的形式,只有单个字节数组的对象,不像SpecificInternalRow 中的的每一个字段都是可以GC的对象,这样在GC标记可达对象阶段,可以减少时间

  • 不影响正常的数据操作,减少了数据存储内存,精确计算内存的使用情况
    像BaseGenericInternalRow都有对应的 setXXX和getXXX方法,用来设置或者获取对应数据类型的值,
    UnsafeRow 也有 setXXX和getXXX方法,背后是对应Unsafe.putXXX和Unsafe.getXXX方法,所以说不影响正常的数据操作
    正常的jvm对象在堆中会包含对象头,实例数据,对齐填充等信息,而unsafeRow只存储对应的数据本身,节约了额外信息
    由于UnsafeRow只存储了数据,对应的占用大小就可以直接计算出来; 而jvm对象不容易计算,因为对象中的字段可能还会包含其他的引用对象

  • 减少了 Shuffle 过程中序列化和反序列化的的消耗
    在Shuffle write写磁盘阶段,会将对应的Row数据序列化,对于是 UnsafeRow 这种序列化的话,直接把当前的byte数组的数据写入就行(参考UnsafeShuffleWriter.write方法实现),用的 ShuffleExchangeExec.serializer 的序列化 ,也就是 UnsafeRowSerializer
    而不像 JavaSerializer 这种序列化,还需要重新序列化整个对象

在shuffle 读阶段,会将数据反序列化为 内部Row, 对于 UnsafeRow 的话,直接读取对应的的字节数据到 UnsafeRow 就行了(参考 ShuffleManager.getReader 实现),用的是ShuffleExchangeExec.serializer.deserializeStream.asKeyValueIterator方法.

Byte 大小端

大端模式和小端模式是指CPU对多字节数据的存储方式。
小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。
在Spark中 以Long的方式在读取Byte数组数据的时候,就会涉及到大小端的问题,这样才能正确读取Long类型数据,比如在ByteArray.getPrefix中就会对大端和小端分别进行处理:

private static final boolean IS_LITTLE_ENDIAN =
      ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
 ...
static long getPrefix(Object base, long offset, int numBytes) {
    // Since JVMs are either 4-byte aligned or 8-byte aligned, we check the size of the bytes.
    // If size is 0, just return 0.
    // If size is between 1 and 4 (inclusive), assume data is 4-byte aligned under the hood and
    // use a getInt to fetch the prefix.
    // If size is greater than 4, assume we have at least 8 bytes of data to fetch.
    // After getting the data, we use a mask to mask out data that is not part of the bytes.
    final long p;
    final long mask;
    if (numBytes >= 8) {
      p = Platform.getLong(base, offset);
      mask = 0;
    } else if (numBytes > 4) {
      p = Platform.getLong(base, offset);
      mask = (1L << (8 - numBytes) * 8) - 1;
    } else if (numBytes > 0) {
      long pRaw = Platform.getInt(base, offset);
      p = IS_LITTLE_ENDIAN ? pRaw : (pRaw << 32);
      mask = (1L << (8 - numBytes) * 8) - 1;
    } else {
      p = 0;
      mask = 0;
    }
    return (IS_LITTLE_ENDIAN ? java.lang.Long.reverseBytes(p) : p) & ~mask;
  }

对于转换的问题可以参考SparkSQL InternalRow

Spark 之 unsafeRow UnsafeRow UnsafeRow is an InternalRow that is backed by raw memory instead of Java objects. UnSafeRow has three parts: [null bit set] [values] [variable length portion] 阅读详情

相关推荐

Spark Tungsten:数据结构与代码生成的双重革新

Tungsten是Apache Spark的性能优化项目,通过引入Unsafe Row数据结构和基于内存页的管理,以及全阶段代码生成(WSCG),减少存储开销、提升GC效率,并优化CPU缓存命中率,从而显著提高Spark的性能。

数据与算法架构提升之路专栏 552

Spark数据格式UnsafeRow

1. 简介 UnsafeRow是InternalRow的子类,它表示一个可变的基于原始内存(raw-memory)的二进制行格式,简单来说UnsafeRow代表一行记录,用于替代java对象(属于Tungsten计划的一部分,可以减少内存使用以及GC开销) InternalRow:spark sql内部使用的表示行的抽象类,对应表示输出的行有org.apache.spark.sql.Row/GenericRow/GenericRowWithSchema 2. 类属性 private Object base

1032851561的博客 8881

Spark 之 InternalRow

InternalRow — Abstract Binary Row Format InternalRow is also called Catalyst row or Spark SQL row. abstract class InternalRow extends SpecializedGetters with Serializable { } UnsafeRow UnsafeRow is a...

zhixingheyi_tian的博客 1008

SparkSQL InternalRow

文章目录lnternalRow 体系数据源 RDD[lnternalRow]Shuffle RDD[InternalRow]Transform RDD[InternalRow]强类型化转换算子利用内置的schmea隐式转换算子连续的强类型化转换算子Encoder对InternalRow的影响总结 SparkSQL在执行物理计划操作RDD时,会全部使用RDD<InternalRow>类型进行操作。 lnternalRow 体系 在SparkSQL 内部实现中, InternalRow 就是用来表示

qq_41775852的博客 1875

手把手教你用OpenClaw+飞书打造论文写作AI助手(附踩坑实录)

面对几十篇文献,逐一阅读摘要都费时。我:“请提取桌面‘论文参考文献’文件夹中所有PDF的摘要,整理成表格,包含:论文标题、作者、发表年份、核心方法、主要结论。AI助手:“正在处理...已提取18篇论文摘要,汇总表格已保存到桌面‘文献摘要汇总.xlsx’。这比我自己一个个打开PDF复制粘贴快了不止100倍。OpenClaw+飞书的组合,让AI真正成为了我的“数字员工”。虽然配置过程有些曲折,但看到它在飞书里回应指令、帮我处理繁琐工作时,那种成就感无可替代。如果你也在准备论文,不妨试试这套方案。记住,

AcademicIdeas的博客 1041

STM407ZET6+OV5640调试心得

最近一个项目用了STMF407ZET6 +OV5640,调试了好几天做些总结; STMF407ZET6 开发板用的是正点原子的开发板,OV5640摄像头用的是秉火摄像头; 这其中有一个大坑,开发板的DCMI接口跟摄像头的接口不一样,要重新用杜邦线进行连接; 本人是在正点原子的“”15,ATK-OV5640摄像头模块“”源码上进行修改的。

kingseacjh的专栏 8325

深入理解SPARK SQL 中HashAggregateExec和ObjectHashAggregateExec以及UnsafeRow

感悟和理解 翻译这篇文章是为了更好的理解ObjectHashAggregateExec和UnsafeRow,关于UnsafeRow的文章,可以参考Spark源码修改系列 - UnsafeRow内存布局和代码优化。 其实明显,UnsafeRow是模拟了offheap的分配方式而在堆上进行的操作,它并没有脱离了jvm,而是使用了Java unsafe API(Java提供的底层非安全操作接口),允许用户像操作C++指针一样来申请和访问内存地址,JVM也不会对这部分非安全数据进行额外的类型检查和垃圾回收。 事实上

monkeyboy_tech的博客 2960

实战指南:从零搭建ROS2 + Cartographer 2D激光SLAM系统

本文详细介绍了如何从零开始搭建ROS2与Cartographer结合的2D激光SLAM系统,涵盖环境准备、激光雷达驱动配置、Cartographer安装与参数调优等关键步骤。通过实战案例和常见问题解决方案,帮助开发者快速掌握激光雷达建图技术,提升机器人自主导航能力。

weixin_29062255的博客 402

spark堆内和堆外内存

由于Driver的内存管理较为简单,内存管理主要对Executor的内存管理进行探讨。 一、堆内(On-Heap Memory)和堆外(Off-Heap Memory)内存规划 ​ Executor作为一个JVM进程,Executor的内存管理建立在JVM的内存管理之上。Spark对堆内内存进行JVM内存管理,引入了堆外内存,使之可以直接在工作节点的系统内存中开辟空间,进一步优化了内存的使用;其中,堆外内存直接向操作系统申请。 二、堆内内存(On-Heap Memory) ​ 堆内内存概述:

写轮眼之大数据 2901

【微信读书】数据内容接口逆向解析实战:从请求捕获到文本提取

本文详细解析了微信读书网页版数据内容接口的逆向工程实战过程。通过浏览器开发者工具捕获网络请求,定位关键API接口,并深入JavaScript代码分析数据解析逻辑,最终实现从请求模拟到文本提取的完整流程。文章旨在技术学习,探讨Web应用数据交互原理,帮助开发者理解现代前端数据流与编码机制。

game4的博客 420

西门子CPU与汇川伺服通信与控制

第三方驱动器的配置内容和西门子的配置内容基本一致,基本上会一种即可知道其他品牌的配置默认报文,需要激活硬限位才能收到限位信号默认报文,原点信号不能实时输出到PLC;自定义报文,需要伺服端设置DIN点位。

大小曲奇的博客 1万+

Spark Tungsten

Tungsten 地址 : 统一 128 位内存地址 , 为了统一管理 Off Heap 和 On Heap 内存空间。迭代器嵌套 :同个 Stage 内部,把多个 RDD 的 compute 函数合成一个函数,再一次性输出数据。WSCG 工作过程 : 基于性能较差的代码,在运行时动态地 (On The Fly) 重构出性能更好的代码。WSCG :基于同一 Stage 内操作符的调用关系,把所有计算进行手写,并合成一个函数。Tungsten 引入 WSCG 机制,消除了 VI 模型引入的计算开销。

记录点点滴滴 768

7.29华为OD机试真题 新系统 - 能量对撞 (Java/Py/C/C++/Js/Go)

7.29华为OD机试真题 新系统 - 能量对撞 (Java/Py/C/C++/Js/Go)

算法大师! 1317

[Spark]调用RDD[InternalRow]的filter方法过滤csv文件

import org.apache.spark.sql.SparkSession object SqlExample { def main(args: Array[String]): Unit = { val spark = SparkSession .builder() .master("local") .appName("Spark sql ...

I have a adream 1372

语音信号的AGC自适应增益调整matlab仿真

语音信号的自动增益控制(Automatic Gain Control,AGC)是音频处理领域中的一项重要技术。其核心功能是实时调整输入信号的幅度,使其保持在一定范围内,以优化输出信号的质量。AGC广泛应用于语音通信、语音识别、音频录制和播放等领域,旨在解决输入信号幅度波动问题,确保输出信号的稳定性和清晰度。一、AGC的基本原理1.1 幅度调整自动增益控制的基本原理是根据输入信号的幅度动态地调整系统的增益。

平时工作较为繁忙,私信消息一般晚上回复,谢谢大家~~ 919

YOLOv8 道路病害检测全栈工程|22 类路面裂缝坑洞 VOC&YOLO 数据集、PyQt 精美 GUI、ONNX 量化、全套训练评估曲线落地

城市道路、国省道、高速公路路面长期受车辆碾压、雨水侵蚀,衍生纵向 / 横向裂纹、坑洼、车辙、接缝剥落等二十余种路面病害。传统人工道路巡检存在致命行业痛点:人工徒步 / 乘车目视巡检效率极低,单日有效巡查里程不足 20 公里;毫米级细微线性裂纹肉眼极易漏检,雨水冲刷后微小病害快速扩张为大坑槽,养护成本翻倍;强光暴晒、阴雨积水、路面油污场景人工分辨难度大;海量巡检图片人工分类统计病害台账耗时数天,养护决策缺乏量化数据支撑。传统阈值、边缘检测图像处理仅能识别大面积坑洞,无法区分二十余种细分裂纹类型,泛化能力极差。

getusushu的博客 120

spark sql(一)源码分析sql解析流程

spark sql解析sql主要基于Catalyst框架,它将复杂的sql解析分为很多的阶段,如未解析的逻辑计划、解析后的逻辑计划、优化后的计划、物理计划等阶段,且每个阶段都有专门的工具类进行处理。

Interest1_wyt的博客 2340

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

spark统一内存模型 详解

Spark 的统一内存模型通过动态调整执行内存和存储内存的划分,极大地提高了内存资源的利用率。通过引入堆外内存支持、灵活的内存共享机制以及动态扩展策略,Spark 能够在不同类型的任务(如批处理、流处理、机器学习)之间高效地分配和管理内存资源。我们从底层原理和源代码的角度详细解析了 Spark 内存管理的工作机制,了解了如何动态管理和调度内存,以及内存的申请、释放与回收机制。掌握这些底层实现细节有助于在实际应用中优化 Spark 性能,提升资源利用率。

goTsHgo的博客 2776

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Spark中Shuffle的前世今生

1.Shuffle的变迁 Spark 0.8及以前 Hash Based Shuffle Spark 0.8.1 为Hash Based Shuffle引入File Consolidation机制 Spark 0.9 引入ExternalAppendOnlyMap Spark 1.1 引入Sort Based Shuffle,但默认仍为Hash Based Shuffle Spark 1.2 默认的Shuffle方式改为Sort Based Shuffle Spark 1.4 引入Tungsten-Sort

weixin_43418390的博客 459

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

三、Spark 内存管理

Spark 内存管理

Shyllin的博客 897

[Spark]UnsafeShuffleWriter的深入理解

UnsafeShuffleWriter的深入理解

YYLong0的博客 959

spark needsUnsafeRowConversion java.util.NoSuchElementException: None.get

spark版本 3.0.1 在spark 中引入了一个bug,该bug的详细信息如下: None.get java.util.NoSuchElementException: None.get scala.None$.get(Option.scala:529) scala.None$.get(Option.scala:527) org.apache.spark.sql.execution.FileSourceScanExec.needsUnsafeRowConversion$lzycompute(DataSo

monkeyboy_tech的博客 1605

Spark的内存管理机制

Spark的内存管理机制

qq_41544550的博客 867

Spark 内存管理

根据 #User、#Storage、#Execution 预估大小 ,调整相关的内存配置项。区分 Execution Memory/Storage Memory 的初始大小。优化:把创建好的 Map 字典封装成广播变量,分发到各个 Executors。广播变量消耗 Storage Memory。

记录点点滴滴 922

Spark 内存管理 :Spark如何高效利用有限的内存空间?

Spark内存的管理模式 在管理方式上,Spark 会区分堆内内存(On-heap Memory)和堆外内存(Off-heap Memory)。这里的“堆”指的是 JVM Heap,因此堆内内存实际上就是 Executor JVM 的堆内存;堆外内存指的是通过 Java Unsafe API,像 C++ 那样直接从操作系统中申请和释放内存空间。 其中,堆内内存的申请与释放统一由 JVM 代劳。堆外内存则不同,Spark 通过调用 Unsafe 的 allocateMemory 和 freeMemory 方法

Nice的博客 2322

源码解析Spark各个ShuffleWriter的实现机制(一)——Shuffle Writer的选择

源码解析Spark shuffle过程中的Shuffle Writer的选择

Christopher_L1n的博客 1759
上一篇: 记录一下 StarRocks 点查的 Profile Metrics
下一篇: Flink中的 BinaryRowData 以及大小端
鸿乃江边鸟
博客等级 码龄6年 3746粉丝 235原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值