[simdjson] document_stream | iterate_many() | batch_size | 线程加速 | 轻量handle

第七章:文档流

欢迎回来

在前面的章节中,我们学习了如何使用解析器结合填充字符串获取表示JSON根节点的文档,并通过按需API(On-Demand API)遍历值、对象和数组,同时使用simdjson_result进行错误处理。

到目前为止,我们专注于逐个解析单个JSON文档。

但如果需要处理包含多个连续JSON文档的大型文件或网络流呢?

常见格式如NDJSON(换行符分隔的JSON)或JSON Lines,其中每行都是完整且有效的JSON文档。

例如:

{"user":"Alice", "id":1}
{"user":"Bob", "id":2}
{"user":"Charlie", "id":3}
...(可能有数百万行)

将整个文件加载到内存逐个解析会非常低效且消耗大量内存。这正是文档流抽象要解决的问题。

什么是文档流?

simdjson::ondemand::document_stream(DOM API对应simdjson::dom::document_stream,但我们聚焦按需API的iterate_many)旨在高效处理包含多个JSON文档的大型输入。

  • 不同于逐个提供文档字符串,iterate_many(DOM API用parse_many一次性接收整个缓冲区或文件内容。

  • iterate_many不会立即解析所有内容,而是建立内部机制,在迭代流时逐个查找并解析文档。

想象成传送带:将整个大型容器装载到传送带(iterate_many),然后逐个处理到达面前的物品(循环遍历document_stream)。无需同时存储所有物品,只需处理当前项。

这种方法有两大优势:

  1. 内存高效:无需同时加载整个GB级流到内存(尽管需要初始大缓冲区)。解析器为每个文档复用内部缓冲区。
  2. 高性能:Simdjson可用高速方法处理流块。按需API甚至能用后台线程在您处理当前文档时查找下一个文档!

获取文档流

通过simdjson::ondemand::parser实例的iterate_many()方法获取document_stream

链接:https://github.com/simdjson/simdjson/blob/master/doc/iterate_many.md

iterate_many()参数通常包括:

  1. 填充JSON数据缓冲区指针(const uint8_t*padded_string
  2. 数据长度(size_t
  3. 可选的batch_size(后续讨论)
  4. 可选的允许逗号分隔文档标志(NDJSON/JSONL不常用)

与其他simdjson操作类似,iterate_many()返回simdjson_result<simdjson::ondemand::document_stream>。使用流前必须检查结果是否有错误。

以下为示例NDJSON数据处理:

#include <simdjson.h>
#include <iostream>int main() {// 假设从大文件读取的NDJSON数据simdjson::padded_string ndjson_data = R"({"user":"Alice", "id":1}{"user":"Bob", "id":2}{"user":"Charlie", "id":3})"_padded; // _padded确保填充simdjson::ondemand::parser parser;// 获取文档流simdjson::simdjson_result<simdjson::ondemand::document_stream> stream_result =parser.iterate_many(ndjson_data);// 检查流设置是否成功if (stream_result.error()) {std::cerr << "文档流设置错误: " << stream_result.error() << std::endl;return EXIT_FAILURE;}// 从结果中获取document_stream对象simdjson::ondemand::document_stream doc_stream = std::move(stream_result.value());std::cout << "成功建立文档流。" << std::endl;// 现在可以迭代流...(见下节)return EXIT_SUCCESS;
}

注意iterate_many接收包含所有JSON文档的整个填充缓冲区。

迭代文档流

simdjson::ondemand::document_stream设计用于基于范围的for循环。循环中每个项是simdjson_result<simdjson::ondemand::document_reference>

为什么用document_reference而非document

因为流为高效性复用解析器内部单个document对象。document_reference轻量句柄,指向此内部可复用文档对象。每次循环移动时,内部文档对象更新为流中下一个JSON文档。

#include <simdjson.h>
#include <iostream>int main() {simdjson::padded_string ndjson_data = R"({"user":"Alice", "id":1}{"user":"Bob", "id":2}{"user":"Charlie", "id":3})"_padded;simdjson::ondemand::parser parser;auto stream_result = parser.iterate_many(ndjson_data);if (stream_result.error()) {std::cerr << "文档流设置错误: " << stream_result.error() << std::endl;return EXIT_FAILURE;}simdjson::ondemand::document_stream doc_stream = std::move(stream_result.value());// 遍历流中每个文档int doc_count = 0;for (auto doc_result : doc_stream) {// 每个'doc_result'是simdjson_result<simdjson::ondemand::document_reference>// 访问文档前检查错误if (doc_result.error()) {std::cerr << "解析文档" << doc_count << "错误: " << doc_result.error() << std::endl;// 可选择继续或停止continue; // 跳过损坏文档}// 获取文档引用simdjson::ondemand::document_reference doc = doc_result.value();std::cout << "处理文档 " << doc_count << ":" << std::endl;// 可像常规文档一样使用'doc'对象访问字段auto user_result = doc["user"].get_string();if (!user_result.error()) {std::cout << "  用户: " << user_result.value() << std::endl;} else {std::cerr << "  获取用户字段错误: " << user_result.error() << std::endl;}auto id_result = doc["id"].get_int64();if (!id_result.error()) {std::cout << "  ID: " << id_result.value() << std::endl;} else {std::cerr << "  获取ID字段错误: " << id_result.error() << std::endl;}doc_count++;}std::cout << "已完成处理 " << doc_count << " 个文档。" << std::endl;// 'parser'和'ndjson_data'在循环期间需保持有效return EXIT_SUCCESS;
}

输出:

在这里插入图片描述

此模式是使用document_stream的核心。遍历流时获取当前JSON文档句柄(document_reference),然后用标准按需API方法(get_object()[]get_string()等)处理。

错误处理至关重要。错误可能发生在流设置(stream_result.error())或循环内解析单个文档时(doc_result.error()),均需检查。

批处理大小与内存

调用iterate_many(buffer, len, batch_size)时,batch_size参数很重要。它定义simdjson初始结构分析(阶段1)时处理的输入缓冲区块大小。

  • batch_size需大于流中最大单个JSON文档。若文档大于批处理大小,simdjson无法在单批次正确解析。
  • 更大batch_size可能通过减少批次切换开销提升性能,但需解析器分配足够内存。
  • 合理默认值通常为1MB,但可根据文档大小调整。解析器容量需通过parser.allocate()设置足够大或自动扩展。

在这里插入图片描述

Simdjson读取batch_size字节(或剩余字节),在阶段1查找块内所有文档边界,迭代时从该块提供文档。消耗完块内文档后加载下一批次

线程加速

iterate_many的强大功能之一(当simdjson启用线程时)是使用后台线程重叠工作。

主线程处理当前批次文档时(循环调用document_reference方法),工作线程可同时在后台对下一批次数据执行阶段1分析。

在这里插入图片描述

这种重叠意味着后续批次阶段1的时间被"隐藏"在当前批次处理时间内。若单文档处理时间足够长,下批次阶段1可能已完成,使阶段1成本趋近于零!

(类似于 生产消费者模型的思想)

无需修改循环代码即可启用此功能。若simdjson编译时启用线程(SIMDJSON_THREADS_ENABLED),iterate_many自动在适当时使用后台线程。

底层机制(简化版)

document_stream对象管理输入缓冲区batch_size及跨文档和批次的迭代状态。包含:

  • 原始输入缓冲区指针及长度
  • simdjson::ondemand::parser指针
  • 当前处理批次信息(起始位置batch_start
  • 跟踪批次内当前文档的内部状态
  • 启用线程时管理stage1_worker线程对象和辅助parser实例(stage1_thread_parser

调用iterate_many(buffer, len, batch_size)时:

  1. 构建document_stream对象,存储缓冲区/长度/批次大小和解析器指针
  2. 启动循环(stream.begin())触发初始处理:
    • 主解析器内部缓冲区可能调整以适应batch_size
    • 用主解析器对输入缓冲区第一个batch_size字节执行阶段1
    • 设置初始document句柄指向第一个文档
    • 启用线程且有更多数据时,启动工作线程对下一批次执行阶段1
  3. 循环内(for (auto doc_result : doc_stream)document_reference包装主解析器内部文档状态。对doc的操作(get_object()[]等)执行当前文档阶段2解析
  4. 循环++运算符移动至下一文档时:
    • 主解析器内部迭代器跳过当前文档
    • 若迭代器到达当前批次末尾,检查工作线程是否完成下一批次阶段1
    • 若下一批次阶段1就绪,快速交换主解析器和工作解析器内部状态。工作解析器成为新主解析器
    • 禁用线程或工作线程未就绪时,主线程自行执行下一批次阶段1
    • 加载新批次后更新内部文档句柄指向首文档
    • 无更多批次时结束流迭代

关键在于document_stream管理缓冲区块和后台阶段1处理,通过迭代器接口逐个产出文档。

高级:位置与截断

如需更多控制或调试,document_stream::iterator(范围for循环隐式使用)提供额外方法:

  • iterator::current_index() const返回当前文档在原始缓冲区的字节偏移,用于记录错误或跟踪大文件进度
  • iterator::source() const:返回指向当前文档原始字节的std::string_view(可能含周围空白)
  • iterator::error() const:返回当前文档error_code

流迭代完成后,document_stream对象提供:

  • document_stream::truncated_bytes() const:返回缓冲区末尾无法解析为完整JSON文档的字节数,用于检测不完整输入或尾部垃圾

逗号分隔文档

默认iterate_many期望JSON空白分隔文档。

若文档用逗号分隔(如1, "hello", true, {}),可在iterate_many传入allow_comma_separated参数为true。但此模式需整个输入作为单批次处理(忽略batch_size)且禁用线程,不适合大规模流式处理。

总结

处理多JSON文档大型文件(如NDJSON/JSON Lines)需内存高效方案。simdjson::ondemand::document_stream专为此设计:

  • 通过parser.iterate_many(padded_data, ...)获取文档流
  • 用范围for循环迭代流:for (auto doc_result : stream)
  • 循环项是表示流中文档的simdjson_result<document_reference>,访问前需检查错误
  • batch_size参数控制阶段1处理块大小,应不小于最大文档
  • 启用线程时,iterate_many可重叠下批次阶段1与当前批次处理,极大提升吞吐
  • 循环中document_reference是轻量句柄,指向解析器复用内存

(通过指针移动,减少拷贝开销和零碎内存)

使用document_stream可高效解析多文档输入,使simdjson成为处理流式JSON数据的利器。

掌握解析、数据处理、导航、错误处理和流处理后,我们已建立坚实基础。下一章将揭秘simdjson通过实现/CPU派发机制实现高性能的奥秘。

下一章:实现/CPU派发


补充:

轻量句柄

轻量句柄是内存中对象的简易引用,不直接存储数据,仅指向已存在的资源(如解析后的文档)

循环中的document_reference作为轻量句柄,避免了重复解析,直接复用内存中的结果,节省计算开销。

类似书签标记书页位置,书签(轻量句柄)本身不是书页内容,但能快速定位到已存在的页面(内存数据)。

核心特点

  • 低开销:不复制数据,仅保存指向内存的指针或标识符。
  • 高效复用:通过引用共享已解析内容,避免重复处理。
  • 无所有权:轻量句柄不管理资源生命周期,需确保目标资源有效。

(通过指针移动,减少拷贝开销和零碎内存)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/89740.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/89740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】向量数据库选型指南:企业内网部署场景

向量数据库选型指南&#xff1a;企业内网部署场景一、选型背景与关键需求 在企业级机器学习应用中&#xff0c;特别是涉及图片、视频等非结构化数据的场景&#xff0c;向量数据库已成为核心基础设施。传统数据库难以高效处理高维向量的相似度检索需求&#xff08;如图片相似性搜…

Django母婴商城项目实践(八)- 数据渲染与显示之首页

8、数据渲染与显示 1 概述 Django作为Web框架,需要一种很便利的方法动态地生成HTML网页,因此有了模板这个概念。模板包含所需HTML的部分代码以及一些特殊语法,特殊语法用于描述如何将视图传递的数据动态插入HTML网页中。 Django可以配置一个或多个模板引擎(甚至是0个,如前…

Redis常见线上问题

文章目录 Redis常见线上问题 引言 报告背景与目的 Redis版本与环境说明 性能瓶颈问题 慢查询分析与优化 高CPU与网络延迟 内存管理问题 内存碎片成因与优化 BigKey与内存溢出 数据一致性与高可用问题 主从同步延迟 脑裂问题与解决方案 持久化机制问题 RDB与AOF对比 核心特性对比…

Typecho博客集成阿里云CDN+OSS实现全站加速方案

文章目录 Typecho博客系统集成阿里云CDN和OSS实现静态资源加速 引言 一、技术选型与准备工作 1.1 为什么选择阿里云CDN+OSS组合 1.2 准备工作 二、OSS存储桶创建与配置 2.1 创建OSS存储桶 2.2 配置Bucket权限 2.3 配置跨域访问(CORS) 三、CDN加速配置 3.1 添加CDN域名 3.2 配置…

计算机毕业设计Java网咖管理系统 Java技术实现的网咖综合管理系统开发 基于Spring Boot框架的网咖运营管理系统设计

计算机毕业设计Java网咖管理系统e0btvq7l &#xff08;配套有源码 程序 mysql数据库 论文&#xff09;本套源码可以先看具体功能演示视频领取&#xff0c;文末有联xi 可分享随着互联网技术的飞速发展和电子竞技的全球兴起&#xff0c;网咖作为一种新兴的休闲娱乐场所&#xff0…

Kotlin main函数

main() 函数 来仔细看看 main() 函数。实际上&#xff0c;它就是一个很常见的函数&#xff1a;你可以对它做任何你能对普通函数做的事。唯一的不同是&#xff1a;它是程序的入口点&#xff08;entry point&#xff09;。这意味着程序的执行从调用这个函数开始。 我们来拆解一下…

深入理解 Spring:事务管理与事件机制全解析

文章目录前言一、Spring 事务管理&#xff08;Transaction Management&#xff09;1. 使用 Transactional 管理事务2. 核心属性说明3. 事务传播行为详解&#xff08;Propagation&#xff09;4. 异常回滚策略分析5. 底层原理剖析&#xff08;源码级&#xff09;二、Spring 事件机…

AWD练习的平台搭建

ubuntu虚拟机搭建 前提资源准备 进行AWD我们需要在一个独立的虚拟机 现在就来搭建一个ubuntu的 这里我们使用的VMware是17的 然后下载镜像的地址&#xff1a;Ubuntu最全的国内镜像下载地址 - 哔哩哔哩 我下载的是中科大的 这里需要准备的前提资源就有了。 创建Ubuntu虚…

C++ 详谈继承体系下的构造函数和析构函数

前言 前面呢, 我们说了C中实现多态的原理, 其中也说了, 虚函数表和虚函数指针的创建时机, C 详谈多态实现原理-CSDN博客 , 这一节呢, 我们会说说在C中继承体系下的另一个知识点, 那就是: 继承体系下的构造函数和析构函数~~, 主要围绕两个问题: 执行顺序? 虚析构函数的作用? …

PostgreSQL 字段类型速查与 Java 枚举映射

1. 查询 SQLSELECTc.table_schema,c.table_name,c.column_name,c.data_type,c.udt_name,CASE-- 数值WHEN c.udt_name IN (int2,int4,int8,float4,float8,numeric,money)THEN NUMERIC-- 布尔WHEN c.udt_name boolTHEN BOOLEAN-- 日期/时间WHEN c.udt_name IN (date,time,timetz…

数据分析综合应用 30分钟精通计划

🔬 数据分析综合应用 30分钟精通计划(完整版含输出) ⏰ 时间分配 5分钟:数据加载与清洗基础 10分钟:探索性数据分析(EDA) 10分钟:数据分析实战案例 5分钟:分析报告生成 📚 第一部分:数据加载与清洗基础 (5分钟) 1. 模拟真实数据集 import pandas as pd import nu…

Python爬虫实战:研究psd-tools库相关技术

一、引言 1.1 研究背景 Adobe Photoshop 是目前最流行的图像处理软件之一,其原生文件格式 PSD(Photoshop Document)包含了丰富的图像信息和编辑历史。PSD 文件不仅在设计领域广泛使用,还在数字营销、版权保护和安全分析等领域具有重要价值。然而,手动分析大量 PSD 文件是…

基于卷积傅里叶分析网络 (CFAN)的心电图分类的统一时频方法

一、研究背景与核心问题​​ECG分类的挑战​&#xff1a;心电图&#xff08;ECG&#xff09;信号分类在心律失常检测、身份识别等领域至关重要&#xff0c;但传统方法难以同时有效整合时域和频域信息。现有方法包括&#xff1a;​时域分类&#xff08;CNN1D&#xff09;​​&am…

Linux——LinuxOS

cd,pwd,mkdir,rm,ls,touch,cat,echo,

深度学习篇---矩阵

在机械臂解算、深度学习网络等硬件和软件领域中&#xff0c;矩阵运算作为核心数学工具&#xff0c;承担着数据表示、变换、映射和优化的关键作用。以下从具体领域出发&#xff0c;详细总结涉及的矩阵运算及对应的核心知识&#xff1a;一、机械臂解算领域机械臂解算&#xff08;…

元宇宙:技术乌托邦与数字化未来——基于技术哲学的分析

一、技术哲学视域下的元宇宙本质哲学源流与技术基因的双重映射理想世界的千年回响&#xff1a;从柏拉图洞穴隐喻中的影子世界&#xff0c;到普特南“钵中之脑”对虚拟与现实界限的消弭&#xff0c;元宇宙的构想深植于人类对平行世界的永恒追问。中国传统神话中“天人二元结构”…

如何构建一个基于大模型的实时对话3D数字人?

近年来&#xff0c;随着元宇宙和AIGC技术的爆发&#xff0c;3D数字人从影视特效走向日常应用。无论是虚拟主播、AI客服&#xff0c;还是数字教师&#xff0c;其核心诉求都是**“能听、会说、有表情”**的实时交互能力。本文就带大家了解如何构建一个基于大模型的实时对话的3D数…

NULL值处理:索引优化与业务设计实践指南

一、NULL值的本质与影响NULL值在数据库中代表"未知状态"或"不适用"的特殊标记&#xff0c;与空字符串或0有本质区别12。其特性导致以下业务与性能问题&#xff1a;‌语义复杂性‌&#xff1a;NULL可能表示"未填写"(如用户手机号)或"不适用&…

【add vs commit】Git 中的 add 和 commit 之间的区别

关于git add和git commit还有一些有点不太清楚的地方&#xff0c;这里写一篇文章好好理一理git add&#xff1a;添加到暂存区 git add实际上是把工作区中的内容存入“暂存区” 通俗来讲就是告诉Git&#xff1a;“这些文件我准备好commit了” git add file.txt # 添加单个文件 …

【推荐100个unity插件】使用C#或者unity实现爬虫爬取静态网页数据——Html Agility Pack (HAP)库和XPath 语法的使用

文章目录前言一、安装HtmlAgilityPack1、从NuGet下载HtmlAgilityPack包2、获取HtmlAgilityPack.dll二、HtmlAgilityPack常用操作1、加载 HTML2、查询方式2.1 使用 XPath 查询&#xff08;推荐&#xff09;2.2 使用 LINQ 查询3、常用查询操作3.1 选择节点3.2 获取属性值3.3 遍历…