在我们的场景中,会将大量的业务配置信息存放在DB, 便于前端修改, 后端使用时,会先将DB导出到本地XML格式文件,再各自加载。 这里之所以不直连数据库一是考虑后端高并发时的性能问题,二是数据一般变动不频繁,存放XML也合适。

起初也曾参考一些现有的xml解析库,但由于我们的XML文件都是从DB导出的,对有些情况不很合适,因而考虑手写。

为什么会手写:

1 ) 允许xml的key为纯数字,因为要从数据库导出到XML,而数据库的内容往往难以避免有纯数字的出现

2 ) 允许空key,数据库里往往使用空字段表示默认,也比较方便

3 ) 兼容xml格式,更好的出错说明

我们希望使用完全的自左向右扫描分析,如对

<a><as od=sdsd</as></a>

能提示 a/as key 缺少 > 更好些, 而不是某一行缺少闭合的>不完整

Tips:曾经对一个800K的上万行xml文件进行排错,包括notepad++/chrome/IE 等浏览器的出错信息让人崩溃,往往只有  某一行缺少;等

4 ) 更好的把握

5 ) 学习,只有亲手写并和其他解析器对比,才能知道真正的瓶颈和问题在哪, 希望能挖掘性能极限,看解析究竟能有多快

经过一段时间的调教,得到如下结果:

1 ) 俺的满足系统要求的XML解析器核心代码只有50行,纯模板,外围功能还支持增量解析合并,易用

2 ) 解析性能可以对比 strlen ,媲美RapidXML,是一般的TinyXML的数十倍。

关于 RapidXML :

1)      号称比 TinyXml 快30~60倍,是 Boost.PropertyTree 的默认xml解析器

2)      对一个约1.5M,utf-8编码包含中/英文,有一定层次深度,大约3.3万行的范例文件解析 TinyXml: 54ms  而 RapidXml: 4ms!

(数据参考 http://hi.baidu.com/5iprog/blog/item/8dde8759dff2558f800a18dc.html )

3)      可对比strlen

ps :为什么拿strlen对比,

由于解析一般都是对文件字符进行的判断处理,而strlen最简单,且由于对0值判断的特点,多数系统都采用了特别的优化,可以按系统位长(如32bit就是4字节)来进行比较,效率很高并且是稳定的,可以作为基础标尺。

测试及结论:

系统 Win2003Server + MinGW gcc4.6.1

AMD 单核机器 1.8GHz 内存 DDR333 1G

(为了能明显看出差异,特别动用了一台老爷机, 当然不用过于担心现在机器的差异,拜AMD不进取所致和频率墙限制,intel那边现在也是缝缝补补又三年,单核的性能这几年几乎没有多少比例提高)

系统内存带宽: 2.7GB/s  AIDA64 内存读取性能约 1200MB/s

(现在一般DDR3 1600的内存带宽 12GB/s, AIDA64 检测单通道读取约5.8GB/s)

范例文件 1.5M 数据 重复 1000 次, memcpy 3s , strlen 2.8s ,自己写的按字节 mystrlen 12.1s

基本上自测的数据是上述AIDA64测试性能的再对半折,这里面也可以看出X86的字节不对齐影响不大。

仍旧是范例文件, 重复 1000 次, RapidXML 14.2s , MyParse 14.1s ,与按字节写的 mystrlen 基本相当!!! 多出的主要是些逻辑判断等,这应该是目前语言层面所能达到的基本极限了 ,更深的挖掘,可能需要考虑CPU扩展指令如SSE的使用了,不过这类解析都是一个前后关联的过程,并行化并不容易。

如何做到的:

1)      只做一遍扫描分析,CPU的内部运算及带宽相对内存还是快很多的,一个字串多次比较和每次多个值比较会慢些,所以不要做多次子串的扫描,在测试中这减少了近2s

而一般的解析器为了简单等原因,可能会对源串做多次子串操作,譬如当分析了一个合规范的段后,剩余内容子段化, A – >  A ’ + B(A substr or erase A ’ ), 这里面都可能包含大量的内存复制操作,会导致性能的迅速低下,如我优化前的解析器,性能就差50倍还多,快两个量级。

2)      使用查表法换时间,当只做一个自左至右的分析时,不可避免会对字串多次比较,

如<abc id1= ’ xxx ’ id2= ’ yyy ’ />, 对 key abc 每一个字符的判断,便不能为 \n \r \t space  = / >

使用查表法便可以解决这个速度问题,现代的cpu缓存一般每核心都有二级256K, 三级2M,足够缓存表值,见RapidXML

File: rapidxml.hpp  Version: 1.13  2423行

3)      模板编程可趋于减少运行时间,编译器会优化掉中间的多层转换及调用问题

4)      减少内存的分配,特别是小内存的频繁分配释放

在测试中只将每个key、id、value转换成string,时间就已拉升到了40ms左右,转换到简单DOM树要50ms,RapidXML则使用了自己特别的mempool(RapidXML只从内存解析,使用时会修改给定的内存段并复制),几乎避免了内存的多次分配,时间也就几乎保持了。

最后谈一下文本与二进制协议的看法:

1 ) 二进制协议其实也要有拷贝及判断等,极限情况下,只算一次比较及复制(二进制也难以避免多次读取判断, 特别是字段复杂和多的情况下),按字符比较占1/3算,需要

4×1/3 + 1×2/3= 2,即2倍strlen,加上更复杂的逻辑运算,仅比XML解析快一倍不到

2 ) 解析往往不是问题,存储才是。RAPIDXML之所以快,还在于mempool的应用, 基本没有内存的来回拷贝和小对象问题, 而一般的应用,包括我们的解析库,最后都回到了对map 类 STL 的使用,STL不可避免会有多次的内存拷贝,加上中间小对象的频繁分配释放,性能会立马降低几倍,在这个比较上,纯粹解析的差别就微乎其微。在实际的测试中,存储往往会是一个更大的影响, 而非解析。测试中, 将映射关系存入map,立即就到 ~50ms

3 ) 选择自己喜欢的协议来吧,这真的不是太大问题, 建议不非常渴求的情况下, 使用字符串或xml可读性及可维护性好的协议

4 ) 对于简单又短小的协议,可用二进制

5 ) 一家之言,欢迎PK讨论J

本文原创自无线技术运营空间: http://wireless.qzone.qq.com 及 http://blog.csdn.net/wireless_tech (专注无线技术运营 —— 无线技术 ( 操作系统 / 数据库 /WEB 前端 / 负载均衡 / 系统容灾 / 系统安全 / 短信接入 /WAP 接入 /3G 等 ) 、无线业务运营、无线开放平台、统计分析 ( 用户行为分析 / 数据挖掘 ) 、 CP 合作,联系我们: 1780551083@qq.com )

【经验总结】XML解析到底能多快?----by abllen近来,因为工作的关系,需要对大XML文件进行解析使用,就专门对解析效率问题作了下研究。  场景:在我们的场景中,会将大量的业务配置信息存放在DB, 便于前端修改, 后端使用时,会先将DB导出到本地XML格式文件,再各自加载。 这里之所以不直连数据库一是考虑后端高并发时的性能问题,二是数据一般变动不频繁,存放XML也
XML 有两种 解析 方式:SAX和DOM。DOM是w3c推荐标准,SAX民间推崇标准。                dom模式下, 解析 引擎会装载整个 xml 文档,然后把文档中的所有节点变成内存中的一个对象,并按照 xml 文档节点的层级关系,在内存中建立对象的关系,在内存中形成一个类似树状结构的对象树。 1. XML 简介 XML 即可扩展的标记语言,可以定义语义标记(标签),是元标记语言。 XML 不像超文本标记语言HTML,HTML只能使用规定的标记,对于 XML ,用户可以定义自己需要的标记。 XML 文档以层级标签的形式来组织数据,多用于配置文件、存储静态数据、交换数据。 2. XML 语法
C++ Xml解析 的效率比较(Qt/Tiny Xml 2/Rapid Xml /Pugi Xml )C Xml解析 的效率比较QtTiny Xml 2Rapid Xml Pugi Xml 测试 环境 Qt - QDomDocument Tiny Xml -2 Rapid Xml Pugi Xml 总结通常我们在一些软件的初始化或者保存配置时都会遇到对 XML文件 的操作,包括读写 xml文件 , 解析 内容等等。在我的工作中就遇到了这
Go 语言支持多种方式 解析 XML ,其中最常用的是内置的 encoding/ xml 包。该包提供了简单易用的 API,可以快速有效地 解析 XML 文档。 下面是一个简单的示例: package main import ( "encoding/ xml " "fmt" type Person struct { XML Name xml .Name ` xml :"person"` Name string ` xml :"name"` Age int ` xml :"age"` func main() { xml File, err := os.Open("person. xml ") if err != nil { fmt.Println("Error opening XML file:", err) return defer xml File.Close() var person Person decoder := xml .NewDecoder( xml File) err = decoder.Decode(&person) if err != nil { fmt.Println("Error decoding XML :", err) return fmt.Println("Person:", person) 这段代码将 解析 如下所示的 XML 文档: <person> <name>John Doe</name> <age>30</age> </person> 在上面的代码中,我们定义了一个 Person 结构体,并使用 xml 包中的 xml .Name 和 xml 标签标记指定 XML 文档中字段的名称。然后,我们使用 os 包的 os.Open() 函数打开 XML 文件,并使用 xml .NewDecoder() 函数创建一个解码器。最后,我们调用 decoder.Decode() 函数解码 XML 文档,并将 解析 出的内容存储在 person 结构体中。