BoostKit 大数据 OmniRuntime 性能优化原理分析

1. 背景

Apache Spark 是目前主流的大数据计算框架。随着数据量的增长,基于 JVM 的 Spark 原生执行引擎在性能上面临以下瓶颈:

  1. CPU 流水线效率低:Spark 采用"火山模型"(Volcano Model),以行(Row)为单位处理数据。每次处理一行数据涉及虚函数调用(Next()),导致 CPU 分支预测失败和指令缓存未命中(Instruction Cache Miss)增加。
  2. GC(垃圾回收)开销:大量数据对象在 JVM 堆(Heap)中创建和销毁,引发频繁的 GC,严重时导致 OOM。
  3. 硬件利用率不足:JVM 的 JIT 编译器在利用 SIMD(单指令多数据)等现代 CPU 特性方面,效率不如 C++ 编译器。

华为鲲鹏 BoostKit 大数据使能套件 提供了 OmniRuntime(OmniOperator),通过将 Spark 的计算密集型算子下沉到 Native C++ 层,并利用鲲鹏处理器的 NEON 指令集进行矢量化加速,解决了上述问题。

华为鲲鹏 **BoostKit官网:**https://www.hikunpeng.com/boostkit。

2. 核心原理

BoostKit OmniRuntime 并不是对 Spark 做"局部打补丁式优化",而是从执行模型、内存模型和数据布局三个维度,对 Spark SQL 物理执行层进行重构式加速

OmniRuntime 是 BoostKit 大数据加速底座的核心组件,包含 OmniOperator(算子加速)、OmniShuffle(Shuffle 加速)等模块,本章将重点讲解其对 Spark SQL 的执行模型、内存模型、数据布局的重构式优化原理。

其优化核心可以概括为三个关键词:

Native 化、矢量化、列式化

从 Spark SQL 的执行链路来看,OmniRuntime 主要作用于 Catalyst 生成物理计划之后、Task 实际执行之前,将 JVM Row-based 执行路径,透明替换为 Native Columnar 执行路径。

2.1 Native 矢量化计算

在原生 Spark SQL 执行模型中,算子运行在 JVM 上,数据以 Row 为单位逐行处理,即使启用了 WholeStageCodeGen,本质仍然是 标量执行模型。这种模式在 CPU 指令级并行、Cache 利用率以及函数调用开销方面均存在天然瓶颈。

复制代码
// Spark Row-based 标量执行(简化示例)
while (iter.hasNext()) {
    UnsafeRow row = (UnsafeRow) iter.next();
    int a = row.getInt(0);
    int b = row.getInt(1);
    output.append(a + b);
}

OmniRuntime 在物理执行阶段接管算子执行,将输入数据按照 Batch 组织(通常为 4096 行),并以列式结构传入 C++ Native Runtime。在 Native 层,算子逻辑不再以"行"为中心,而是以"列"为基本计算单元,充分利用鲲鹏 CPU 的 ARM NEON SIMD 指令集进行并行计算。

复制代码
// C++ Native + NEON SIMD 计算示例
int32_t* col_a   = ptr_a;
int32_t* col_b   = ptr_b;
int32_t* col_out = ptr_out;

for (int i = 0; i < row_count; i += 4) {
    int32x4_t vec_a = vld1q_s32(col_a + i);
    int32x4_t vec_b = vld1q_s32(col_b + i);

    int32x4_t vec_res = vaddq_s32(vec_a, vec_b);

    vst1q_s32(col_out + i, vec_res);
}

通过 SIMD,一条指令即可同时完成多个数据元素的计算,相比 JVM 标量执行,显著提升了 CPU 吞吐能力。同时,列式数据在内存中连续存放,访问模式更加规整,进一步提高了 L1/L2 Cache 的命中率,为算子执行带来叠加收益。

2.2 Off-Heap 内存管理

在传统 Spark 执行过程中,大量中间结果存储在 JVM 堆内,容易引发频繁 GC,尤其在 Join、Aggregate 等场景下,GC 抖动往往成为性能瓶颈。

OmniRuntime 采用 Off-Heap(堆外)内存模型,将算子执行过程中的核心数据全部存储在 Native 内存中。JVM 侧仅保留必要的元信息(如内存地址、长度等),从而彻底规避 GC 对执行路径的影响。

复制代码
// Native 侧堆外内存分配
void* buffer = aligned_alloc(64, buffer_size);

// 构建列式向量
int32_t* column_data = reinterpret_cast<int32_t*>(buffer);

在 JVM 与 Native 之间,OmniRuntime 通过 JNI 传递内存地址,实现 Zero-Copy 数据共享。Spark 的 UnsafeRow 或 ColumnarBatch 不再发生 Java 堆与 Native 堆之间的数据拷贝,Native Runtime 可以直接对数据进行读写。

复制代码
// JVM 侧:传递 Native 地址
long nativeAddr = omniColumn.getNativeAddress();
nativeExecute(nativeAddr, rowCount);

// Native 侧:直接访问 JVM 传递的地址
int32_t* data = reinterpret_cast<int32_t*>(nativeAddr);

这种 Off-Heap + Zero-Copy 的设计,使 OmniRuntime 在大数据量、高并发场景下依然能够保持稳定的延迟表现,并显著降低 Executor 对 JVM Heap 大小的依赖。

Heap 分配代码:

复制代码
public class HeapGcTest {

    public static void main(String[] args) throws Exception {
        long last = System.nanoTime();

        for (int i = 0; i < 200; i++) {
            // 模拟 Join / Aggregate 产生的大量中间结果
            byte[] data = new byte[50 * 1024 * 1024]; // 50MB

            long now = System.nanoTime();
            System.out.printf("Iter %d latency: %.2f ms%n",
                    i, (now - last) / 1_000_000.0);
            last = now;

            Thread.sleep(20);
        }
    }
}

运行的时候需要加运行参数:

复制代码
-Xms512m -Xmx512m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps

Off-Heap 分配:

复制代码
import java.nio.ByteBuffer;

public class OffHeapGcTest {

    public static void main(String[] args) throws Exception {
        long last = System.nanoTime();

        for (int i = 0; i < 200; i++) {
            // 使用 Off-Heap 内存
            ByteBuffer buffer =
                    ByteBuffer.allocateDirect(50 * 1024 * 1024);

            long now = System.nanoTime();
            System.out.printf("Iter %d latency: %.2f ms%n",
                    i, (now - last) / 1_000_000.0);
            last = now;

            Thread.sleep(20);
        }
    }
}

运行结果对比:

表格 还在加载中,请等待加载完成后再尝试复制

2.3 Columnar Shuffle

Shuffle 是 Spark SQL 执行过程中最容易成为瓶颈的阶段之一。传统 Shuffle 需要在列式与行式之间反复转换,并依赖 Java 序列化与反序列化,带来较高的 CPU 和内存开销。

OmniRuntime 在 Shuffle 阶段引入 Columnar Shuffle 机制,直接对列式数据进行序列化与网络传输,避免了"列转行 → 编解码 → 行转列"的冗余过程。

复制代码
// Columnar Shuffle 序列化示例(简化)
for (auto& column : batch.columns()) {
    serialize(column.data(), column.size());
}

在数据压缩方面,OmniRuntime 使用 Native 实现的 LZ4 / ZSTD 算法,相比 Java 版本具有更低的指令开销和更高的压缩吞吐。

复制代码
// Native ZSTD 压缩示例
size_t compressed_size = ZSTD_compress(
    dst, dst_capacity,
    src, src_size,
    compression_level
);

通过列式传输与 Native 压缩的组合优化,Shuffle 阶段的 CPU 开销和网络传输成本均得到有效降低,在 Join、Group By 等典型 OLAP 场景中表现尤为明显。

3. 实战配置

华为鲲鹏 BoostKit OmniRuntime 为 Apache Spark 提供了开箱即用的性能优化能力,仅需完成简单的环境部署与配置,即可让 Spark SQL 作业自动启用 Native 列式执行引擎,无需修改业务代码。

在官网我们也可以找到相对应的安装教程:

软件包安装:

可通过鲲鹏官网提供的 YUM 源或离线包安装 BoostKit 软件包:

复制代码
# 配置鲲鹏 YUM 源(以 openEuler 为例)
echo -e "[kunpeng-boostkit]\nname=Kunpeng BoostKit - openEuler 22.03 LTS\nbaseurl=https://repo.oepkgs.net/openeuler/rpm/openEuler-22.03-LTS/extras/aarch64/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/kunpeng-boostkit.repo

# 安装 OmniOperator 软件包
yuminstall-yboostkit-omnioperator--nogpgcheck

Spark 配置:

OmniRuntime 的配置支持两种方式:全局配置(spark-defaults.conf) (对所有作业生效)或作业提交时指定(仅对当前作业生效),可根据业务需求选择。

全局配置:

编辑 Spark 集群的 spark-defaults.conf 文件(默认路径:$SPARK_HOME/conf/spark-defaults.conf),添加以下配置项:

复制代码
# 1. 启用 OmniOperator 列式执行扩展(核心配置)
spark.sql.extensions=com.huawei.boostkit.spark.ColumnarPlugin

# 2. 配置 Native 动态库路径(指向 BoostKit 安装的 .so 库目录)
spark.driver.extraLibraryPath=/opt/omni-operator/lib
spark.executor.extraLibraryPath=/opt/omni-operator/lib

# 3. 启用列式 Shuffle(优化 Shuffle 性能,建议开启)
spark.shuffle.manager=org.apache.spark.shuffle.sort.ColumnarShuffleManager

# 4. 优化内存配置:减少堆内内存,增加堆外内存供 Native 层使用
spark.executor.memory=20g                # 堆内内存
spark.memory.offHeap.enabled=true        # 开启堆外内存
spark.memory.offHeap.size=20g            # 堆外内存大小(建议与堆内内存相当)

# 5. 可选:关闭 Spark 原生 WholeStageCodeGen(OmniRuntime 已提供更优的 Native 代码生成)
spark.sql.codegen.wholeStage=false

作业级配置:

提交 Spark 作业时,通过 --conf 参数指定配置项,示例如下:

复制代码
spark-submit \
  --class com.example.YourSparkJob \
  --master yarn \
  --deploy-mode cluster \
  --executor-memory 20g \
  --num-executors 30 \
  --executor-cores 8 \
  # OmniRuntime 核心配置
  --conf spark.sql.extensions=com.huawei.boostkit.spark.ColumnarPlugin \
  --conf spark.driver.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.executor.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.shuffle.manager=org.apache.spark.shuffle.sort.ColumnarShuffleManager \
  --conf spark.memory.offHeap.enabled=true \
  --conf spark.memory.offHeap.size=20g \
  # 作业 Jar 包
  your-spark-job.jar

验证配置生效:

在 Spark Shell 中执行 SQL 并通过 explain() 方法查看物理执行计划,若计划中出现Columnar 前缀 的算子(如 ColumnarHashAggregateColumnarFileScan),则表示 Native 列式执行引擎已生效。

复制代码
# 启动 Spark Scala Shell(需带上 OmniRuntime 配置)
spark-shell \
  --conf spark.sql.extensions=com.huawei.boostkit.spark.ColumnarPlugin \
  --conf spark.driver.extraLibraryPath=/opt/omni-operator/lib

# 执行 SQL 并查看执行计划
scala> spark.sql("SELECT sum(price) FROM sales GROUP BY city").explain()

配置成功:

4. 性能对比

为验证鲲鹏 BoostKit OmniRuntime 对 Spark 的性能提升效果,我们基于鲲鹏 920 集群,采用 TPC-DS 1TB 标准数据集,设计了对比测试方案,通过实战代码执行测试并量化分析性能差异。

4.1 测试环境

本次测试的硬件与软件环境保持统一,仅区分是否启用 OmniRuntime 优化,保证其他条件都是一致的,这样我们可以更加直观的看出鲲鹏 BoostKit OmniRuntime 对 Spark 的性能提升效果:

表格 还在加载中,请等待加载完成后再尝试复制

4.2 实战测试代码

我们通过 Spark Submit 提交 TPC-DS 测试任务,分别执行开源 SparkBoostKit Spark 测试,以下是核心测试代码与提交脚本:

数据读取与查询执行代码(Scala):

复制代码
import org.apache.spark.sql.SparkSession
import java.util.concurrent.TimeUnit

object TPCDSBenchmark {
  def main(args: Array[String]): Unit = {
    // 初始化 SparkSession
    val spark = SparkSession.builder()
      .appName("TPCDS-Benchmark")
      .getOrCreate()

    // 读取 TPC-DS 1TB 数据集(Parquet 格式,已提前生成)
    val tpcdsPath = "hdfs:///tpcds/1tb/parquet"
    // 示例:读取核心表(实际测试会遍历所有 25 张表)
    val storeSalesDF = spark.read.parquet(s"$tpcdsPath/store_sales")
    val dateDimDF = spark.read.parquet(s"$tpcdsPath/date_dim")
    storeSalesDF.createOrReplaceTempView("store_sales")
    dateDimDF.createOrReplaceTempView("date_dim")

    // 定义测试查询(以 TPC-DS Q67 为例,复杂查询包含多表关联、聚合、过滤)
    val q67 =
      """
        |SELECT dt.d_year,
        |       item.i_brand_id brand_id,
        |       item.i_brand brand,
        |       SUM(ss_ext_sales_price) sum_agg
        |FROM date_dim dt, store_sales ss, item
        |WHERE dt.d_date_sk = ss.ss_sold_date_sk
        |  AND ss.ss_item_sk = item.i_item_sk
        |  AND item.i_manufact_id = 436
        |  AND dt.d_moy = 11
        |GROUP BY dt.d_year, item.i_brand_id, item.i_brand
        |ORDER BY dt.d_year, sum_agg DESC, brand_id
        |LIMIT 100;
        |""".stripMargin

    // 执行查询并统计耗时(多次执行取平均值,排除缓存影响)
    val repeatTimes = 5
    val times = new Array[Long](repeatTimes)
    for (i <- 0 until repeatTimes) {
      val start = System.nanoTime()
      spark.sql(q67).collect() // 触发执行
      val end = System.nanoTime()
      times(i) = TimeUnit.NANOSECONDS.toSeconds(end - start)
      println(s"第 ${i+1} 次执行耗时:${times(i)} 秒")
    }

    // 计算平均耗时
    val avgTime = times.sum / repeatTimes.toDouble
    println(s"\nQ67 平均执行耗时:$avgTime 秒")

    spark.stop()
  }
}

开源 Spark 提交脚本(spark-submit-open.sh):

复制代码
#!/bin/bash
spark-submit \
  --class TPCDSBenchmark \
  --master yarn \
  --deploy-mode cluster \
  --executor-memory 20g \
  --driver-memory 8g \
  --num-executors 60 \
  --executor-cores 8 \
  # 启用 OmniRuntime 核心配置
  --conf spark.sql.extensions=com.huawei.boostkit.spark.ColumnarPlugin \
  --conf spark.driver.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.executor.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.shuffle.manager=org.apache.spark.shuffle.sort.ColumnarShuffleManager \
  --conf spark.memory.offHeap.enabled=true \
  --conf spark.memory.offHeap.size=20g \
  --conf spark.sql.adaptive.enabled=true \
  ./tpcds-benchmark-1.0.jar

BoostKit Spark 提交脚本(spark-submit-boostkit.sh):

复制代码
#!/bin/bash
spark-submit \
  --class TPCDSBenchmark \
  --master yarn \
  --deploy-mode cluster \
  --executor-memory 20g \
  --driver-memory 8g \
  --num-executors 60 \
  --executor-cores 8 \
  # 启用 OmniRuntime 核心配置
  --conf spark.sql.extensions=com.huawei.boostkit.spark.ColumnarPlugin \
  --conf spark.driver.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.executor.extraLibraryPath=/opt/omni-operator/lib \
  --conf spark.shuffle.manager=org.apache.spark.shuffle.sort.ColumnarShuffleManager \
  --conf spark.memory.offHeap.enabled=true \
  --conf spark.memory.offHeap.size=20g \
  --conf spark.sql.adaptive.enabled=true \
  ./tpcds-benchmark-1.0.jar

代码运行结果对比:

4.3数据对比

通过执行上述测试代码,我们统计了整体查询耗时典型复杂查询耗时CPU 利用率等核心指标,具体对比数据如下表:

表格 还在加载中,请等待加载完成后再尝试复制

从测试数据可以看出:

  1. BoostKit Spark 借助 Native 矢量化计算、Off-Heap 内存管理和 Columnar Shuffle 优化,整体性能较开源 Spark 提升近 50%,复杂查询性能直接翻倍。
  2. CPU 利用率的提升和 GC 停顿时间的降低,说明 OmniRuntime 有效解决了 JVM 行式执行的瓶颈,让硬件资源得到更充分的利用。
  3. Shuffle 阶段的耗时大幅减少,印证了 Columnar Shuffle 规避数据格式冗余转换的优化效果。

5. 总结

BoostKit OmniRuntime 通过将 SIMD 和 Native 内存管理技术引入 Spark,解决了 JVM 带来的性能瓶颈。用户只需简单的配置即可获得 Native 级的性能体验,从而在不增加硬件成本的情况下提升大数据计算效率。

相关推荐
ShenLiang20252 小时前
识别SQL里的列名
大数据·人工智能·python
百胜软件@百胜软件2 小时前
百胜软件入选“2025业务中台企业排行TOP30”,持续赋能零售企业数字化变革
大数据·零售
曜华激光2 小时前
太阳能电池串质量检测仪自动生成报告——高效赋能光伏质检闭环
大数据·人工智能
大力财经2 小时前
贾跃亭总结2025:独在异乡为异客,万里归心向北京
大数据·人工智能·物联网
paj1234567892 小时前
elasticsearch 导出数据命令
大数据·elasticsearch·搜索引擎
云老大TG:@yunlaoda3603 小时前
华为云国际站代理商MSGSMS的服务质量如何?
大数据·数据库·人工智能·华为云
shaominjin1233 小时前
使用Git自带的SSH协议搭建git服务器
大数据·elasticsearch·搜索引擎
m0_672656543 小时前
JavaScript性能优化实战技术文章大纲
开发语言·javascript·性能优化
Macbethad3 小时前
机器学习开发技术报告
大数据