Delphi 百万级配置保存性能优化:从 20 秒到 2 秒的实战
背景
最近在项目中遇到一个性能问题,某工业软件的配置模块,一个配置保存操作,需要保存 100 MB+ 的复杂结构配置文件时,参数保存操作耗时高达 20 秒。该文件包含:
- 各类型自定义结构体,需要自行对参数进行序列化和反序列化
- 多层级的配置树
- 多级安全校验规则
问题分析
通过对代码分析,发现主要性能瓶颈在于:
- I/O 风暴:每个节点单独写入,导致频繁的磁盘 I/O 操作
- 内存碎片:字符串拼接不合理,产生过多的临时对象,导致频繁的内存分配和回收
- 编码冗余:多次进行UTF8编码和解码操作
问题示例:
// 典型递归写入模式
procedure SaveNode(Node: TConfigNode; Writer: TStreamWriter);
begin
Writer.WriteLine(Node.Header); // 触发磁盘I/O
foreach ChildNode in Node.Children do
SaveNode(ChildNode, Writer); // 递归调用
end;
优化方案
核心方案:三级缓冲
使用
TStringBuilder
缓冲内容:将所有节点的内容先缓存到TStringBuilder
中,减少字符串拼接产生的临时对象,减少内存分配和复制的开销// 原代码 nodeStr = format('%sobject %s : %s', [copy(SPACES, 1, Level*2), ParamName, ParamClassName]); OutputWriter.WriteLine(nodeStr); // 修改后的代码 sb.AppendLine(format('%sobject %s : %s', [copy(SPACES, 1, Level*2), ParamName, ParamClassName]));
预分配缓冲: 预先分配关键缓冲区的内存,减少内存分配和回收
// 根据历史数据动态调整缓冲区 BufferSize := Trunc(LastFileSize * 1.2); Builder := TStringBuilder.Create(BufferSize);
减少 I/O 操作:将所有节点的内容先缓存到内存中,最后一次性写入磁盘,减少频繁的磁盘 I/O 操作
// 原代码 procedure TParamWriter.WriteValue(Writer: TStreamWriter); begin Writer.WriteLine(Format('Value=%s', [Value])); // 直接I/O end; // 优化后 procedure TParamWriter.BuildString(var Buffer: TStringBuilder); begin Buffer.AppendLine(Format('Value=%s', [Value])); // 内存操作 end; procedure SaveToFile(FileName: string); var Buffer: TStringBuilder; Writer: TStreamWriter; begin Buffer := TStringBuilder.Create(1024*1024); // 预分配1MB try BuildConfigString(Buffer); // 递归生成内容 Writer := TStreamWriter.Create(FileName); Writer.Write(Buffer.ToString); // 单次I/O finally Buffer.Free; Writer.Free; end; end;
优化树形遍历:
procedure TraverseNode(Node: TTreeNode; var Buffer: TStringBuilder); begin Buffer.Append(Node.Header); // 避免递归中的反复内存分配 foreach Child in Node.Children do TraverseNode(Child, Buffer); // 传递缓冲引用 end;
优化效果
指标 | 优化前 | 优化后 | 测试环境 |
---|---|---|---|
100M 配置保存时间 | 20.4s | 2.1s | 7200RPM HDD |
CPU 峰值使用率 | 92% | 18% | i5-6500 |
磁盘写入次数 | 10w+ | n | / |
总结
总结以下本次优化过程给我带来的启示:
- 资源的创建和释放次数 : 特别是树状数据结构,存在递归操作时
- 字符串拼接优化:使用
TStringBuilder
替代+
操作 - 缓冲机制的重要性:减少磁盘 I/O 次数
- 性能测试的必要性:进行多次性能测试,验证优化效果