100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 解决【Windows+Delphi+多线程+String】效率低的问题

解决【Windows+Delphi+多线程+String】效率低的问题

时间:2021-01-31 14:55:39

相关推荐

解决【Windows+Delphi+多线程+String】效率低的问题

文章目录

(一)问题现象(1.1)按行读取文本慢(1.2)多线程和单线程速度一样(二)原因分析(三)解决办法(3.1)FastMM5(3.2)FastMM4(3.3)ScaleMM2(3.4)TCMalloc(3.5)TBBMalloc(3.6)避免使用字符串类型(3.7)改用 Free Pascal(3.8)换开发语言(四)总结和实测(4.1)性能(4.2)内存占用

(一)问题现象

某Delphi程序用10个线程,分别读取10个不同的文本文件,逐行读取。

发现整体速度和仅用1个线程顺序读完10个文件的速度差不多,整体速度很慢。

观察CPU占用也差不多,占用率都很低,

最早没细想,以为是Pascal语言就这个速度。

但后来发现几乎同样的代码,用Lazarus(FPC)编译后,速度就快了很多,基本上接近Go语言的速度了。

这才确定是Delphi(而不是Pascal)的问题。

严格的说这是两个问题

按行读取文本慢。多线程和单线程速度一样(多线程效率未提升)。

(1.1)按行读取文本慢

这个问题其实主要是用ReadLn()的方式读取TextFile

如果你的Delphi版本提供了TStreamReader,那么用它会快得多。

仅考虑Ansi编码的话还可以参考我的:🔗《提升老版本Delphi按行读取文本文件的效率》 比TStreamReader还快一点点呢。

总之这个问题不是本文的重点。

(1.2)多线程和单线程速度一样

这个问题仅出现在同时满足下面4个条件的情况下:

WindowsDelphi (D7 - D11 现象一样)多线程字符串处理(String)

也就是说换成Linux,用其它语言,或者Delphi多线程网络通信,都是没问题的。

这有个以前测试的图标,大概能看出鱼丸粗面组合,啊不,上面4项组合情况下到底多慢。

主机配置差异大,所以横向对比主机没意义,主要看语言/线程的差异。

所有语言都是最基础的编写方式,比如都是用String读写,没做任何优化。

看到Go时,我能听到Delphier心碎的声音……

PS:后来又看到单线程Python更受打击……

(二)原因分析

省略中间过程……

总之最后经过求助,通过论坛上各位热心同学讨论和帮助,以及实际程序验证。

发现是Delphi使用的内存管理在多线程下效率有问题,无法充分利用CPU多核心。

(三)解决办法

最简单就是替换默认内存管理

(3.1)FastMM5

主页:🔗/pleriche/FastMM5

使用FastMM5,在项目最前面加入FastMM5引用就可以了。

program MyAPP;usesFastMM5,SysUtils,......

FastMM5是双协议,

你可以选择在GPL v3许可证的限制下免费使用它,

或者付费进行商业软件(非开源)的开发。

这有个对比,

PS:我测试中未发现不同的模式对速度/内存占用的影响。

自己实测多线程速度有少量的提升

(3.2)FastMM4

主页:🔗/pleriche/FastMM4

据说Delphi新版就是用的FastMM4呢。

使用FastMM4,在项目最前面加入FastMM4引用就可以了。

但是如果不进行任何设置,则没有任何效果,需要修改FastMM4Options.inc文件中的配置。

就是打开NeverSleepOnThreadContention,打开的方式如下,简单说如果前面有个.就去掉。

......{Enable this option to not call Sleep when a thread contention occurs. Thisoption will improve performance if the ratio of the number of active threadsto the number of CPU cores is low (typically < 2). With this option set athread will usually enter a "busy waiting" loop instead of relinquishing itstimeslice when a thread contention occurs, unless UseSwitchToThread isalso defined (see below) in which case it will call SwitchToThread instead ofSleep.}{$define NeverSleepOnThreadContention}......

关于这个选项的一些讨论

1)这个选项默认关闭是有原因的,只在特定的情况下有效。

2)应该只在线程数低于内核数(真实内核,而不是超线程内核)时使用它。

自己实测多线程速度有少量的提升

(3.3)ScaleMM2

主页:🔗/andremussche/scalemm

使用ScaleMM2,在项目最前面加入ScaleMM2引用就可以了。

自己实测多线程速度有很大的提升,接近Go的速度,追平Lazarus (FPC)的效果了。

但是程序内存消耗增加了1/3到1/4……!!!

所以小心,对于有些内存吃紧的情况,由于物理内存用完而用到虚拟内存时,是会大幅降低程序速度的。

(3.4)TCMalloc

由Google发布的Thread-Caching Malloc线程缓存型内存分配机制。

它为每一个线程都缓存一些可分配内存,因此在多线程场景下,TCMalloc能够尽可能规避多个线程同时分配/释放内存时的锁争用问题,这使得TCMalloc相较于其它内存分配机制,内存分配和回收速度更快。

💡不是Pascal而是C++实现,可用于Linux(吧?)。

【Google Performance Tools】仓库:🔗/gperftools/gperftools

可以用Visual Studio自己编译出libtcmalloc.dll(我喜欢自己都试一下)——注意区分64或32位。

【tcmalloc】仓库:🔗/google/tcmalloc

这怎么肥四?声明:这不是一个谷歌官方支持的产品……

懒得折腾直接下载现成的:

比如这里:🔗/obones/tcmalloc-delphi 有32/64位的Windows下的DLL,以及Delphi的接口单元。

接口单元实现不只一个,可以找别人的,也可以自己写(有现成的干嘛要自己写?)

使用TCMalloc(libtcmalloc.dll):

在项目最前面加入TCMalloc单元引用。并将DLL文件放入程序所在目录,或操作系统目录(x64放system32,x86放syswow64目录)。

自己实测多线程速度有较大的提升

(3.5)TBBMalloc

由Intel发布的Threading Building Blocks Malloc属于Intel oneAPI 线程构建模块。

由灵活的 C++ 库简化了应用程序添加并行性工作的复杂性。

💡不是Pascal而是C++实现,可用于Linux。

【官方介绍】页面:🔗 /content/www/cn/zh/developer/tools/oneapi/onetbb.html (不是中文)

【oneTBB】仓库:🔗/oneapi-src/oneTBB

呃,怎么都找不到现成的接口单元项目呢……

只好自己上传了一个 🔗/download/ddrfan/86723070

使用TBBMalloc(libtbbmalloc.dll):

在项目最前面加入TBBMalloc单元引用。并将DLL文件放入程序所在目录,或操作系统目录(x64放system32,x86放syswow64目录)。

自己实测多线程速度有较大的提升

(3.6)避免使用字符串类型

手动修改代码,避免使用String。

比如ReadLN->BlockRead

用固定大小的缓冲区+字符串指针pchar代替String

用固定长度的字符指针数组代替StringList

尽可能传递指针(地址/引用)而不是复制数据,等等…… 总之就是自行优化。

类似把C++程序改为纯C实现……

道理都懂,但是方便性下降得厉害,俺是快速开发工具啊。

(3.7)改用 Free Pascal

之前居然忘了这一段最重要的,赶紧补上。

使用Lazarus+Free Pascal Compiler完全没效率问题(参考下方对比表格)

主页:🔗https://www.lazarus-/

也可以用CodeTyphon(相当于加上丰富的组件大礼包)。

主页:🔗/sitejoom/

都是Pascal语言,程序移植简单(如果没有用到大量第三方组件的话 😄 )

(3.8)换开发语言

换种开发语言,比如golang 📖 不用任何技巧,快得要死!

主页:🔗/

(四)总结和实测

似乎没有Delphi下完美的解决方案。

全面不用String在实际项目中难以做到。

速度/内存/复杂度不能兼得,用动态库得考虑部署。

(4.1)性能

下面是个测试例子,除了最基本用了单线程。

内存管理均为4个线程同时读取4个文本文件,

读出每一行用Tab符号拆分为列表(文件大概750MB)

同样数据,同样处理,用Lazarua(FPC)多线程作为对比:

(4.2)内存占用

某些耗内存较多的程序,内存使用量的变化也很重要。

由于耗几十GB内存的程序处理时间太长,所以只对比了下面这个耗几个GB级别的。

虽然是对比内存,但处理速度也基本符合上面4.1测试的梯队。

最终结果内存消耗差异相当大……不得不慎重。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。