写字哥 發表於 2024-5-27 18:05:00

Modern Pascal is Still in the Race (Modern Pascal 仍在竞赛中)

<h1 id="modern-pascal-仍在竞赛中">Modern Pascal 仍在竞赛中</h1>
<p>作者:Arnaud Bouchez,2022年11月26日。永久链接</p>
<ul>
<li>Pascal 编程</li>
<li>博客</li>
<li>集合</li>
<li>跨平台</li>
<li>数据库</li>
<li>Delphi</li>
<li>FPC</li>
<li>垃圾回收器</li>
<li>泛型</li>
<li>Go</li>
<li>优秀实践</li>
<li>元编程</li>
<li>mORMot</li>
<li>mORMot2</li>
<li>性能</li>
<li>RTTI</li>
<li>Rust</li>
</ul>
<p>最近在Lazarus/FPC 论坛上的一项民意调查突显了一个事实:Pascal 程序员比其他大多数程序员年龄都大。通常来说,到了我们这个年纪,应该做管理人员而非开发人员了。但我们仍喜欢用 Pascal 编程。几十年过去了,它仍然很有趣!</p>
<p>但这是否意味着你不应该使用 Pascal 来做任何新项目呢?语言/编译器/库是否过时了呢?</p>
<p>在我目前工作的公司里,我们有一些年轻的程序员,他们有的是刚毕业,有的还在上学,他们加入了团队并写出了出色的代码!</p>
<p><img src="https://blog.synopse.info/public/blog/performance.jpg" alt="img" loading="lazy"></p>
<p>最近在同一个论坛上的一个帖子讨论了使用 C#、Go、Scala、TypeScript、Elixir 和 Rust 等语言实现 REST 服务器的比较。</p>
<p>即将贡献出几个 Pascal 版本,其中之一就是 <em>mORMot</em> 大放异彩的版本。</p>
<h4 id="挑战与算法">挑战与算法</h4>
<p>最初的挑战可在 transit-lang-cmp 找到,其中包含所有这些花哨语言和库的源代码。</p>
<p>实际上,此测试程序的目标是加载两个大型 CSV 到内存中(80MB + 2MB),然后通过 HTTP 提供由路由标识符生成的 JSON,同时连接两个 CSV。</p>
<p>生成的 JSON 大小可能在 30KB 到 2MB 之间。所有数据都是根据内存中的 CSV 实时生成的。</p>
<p>说句公道话,一个普通的商业程序员会为此使用数据库。而不是傻傻的内存结构。并要求资金支持,以建立一组庞大的云计算机器和负载均衡。😃</p>
<h4 id="遵循mormot的方式">遵循mORMot的方式</h4>
<p>对于FPC中的<em>mORMot</em>版本,我采用了另一种方法,使用了两种不同的算法:</p>
<ul>
<li>我确保列表在内存中是已排序的,然后在其中进行了O(log(n))的二分查找;</li>
<li>所有存储的字符串都被“内联”了,即相同的文本共享一个字符串实例,FPC的引用计数发挥了它的魔力。</li>
</ul>
<p>这里没有使用像手动生成JSON或使用复杂数据结构这样的底层技巧——数据结构仍然是高级的,具有可读的字段名等。逻辑和意图都是清晰可读的。<br>
我们只是利用了Pascal语言和<em>mORMot</em>的特性。例如,如果需要,字符串内联是框架的一部分。</p>
<p>请查看我们存储库中的源代码。</p>
<p>结果如下:</p>
<ul>
<li>代码仍然可读、简洁且高效(大部分处理都是由<em>mORMot</em>完成的,例如CSV、搜索、JSON);</li>
<li>它使用的内存要少得多——保存数据时使用的内存比Go少10倍,提供服务时使用的内存比Go少5倍;</li>
<li>性能与Go及其经过高度调优/优化的编译器和RTL一样快。</li>
</ul>
<p><img src="https://blog.synopse.info/public/blog/mORMot2-small.png" alt="img" loading="lazy"></p>
<h4 id="算法的重要性">算法的重要性</h4>
<p>主要的思想是让算法匹配输入数据和预期的结果集。<br>
就像程序员在编程游戏时所做的那样。而不像编码人员在编写商业软件时那样随意。😉</p>
<ul>
<li>由于使用了<em>mORMot</em>有效地映射了动态数组存储及其CSV和JSON功能,源代码仍然非常可读。<code>TDynArray</code></li>
<li>我猜源代码对于校外程序员来说仍然是可以理解的——比Rust等语言更可读。</li>
</ul>
<p>公平地说,我使用了类型化指针,但理解它们的目的并不难,而且FPC会将此函数编译成非常高效的汇编代码。我本来可以使用带有索引的常规动态数组访问,这样虽然会稍微慢一点,但并不更容易跟踪,也不更安全(如果我们在没有范围检查的情况下编译)。<code>TScheduler.BuildTripResponse</code></p>
<p>值得注意的是,我们没有进行任何特定的调优,例如像其他框架所做的那样使用常量预分配结果。我们只是指定了数据,然后让<em>mORMot</em>来处理它——仅此而已。<br>
<em>mORMot</em>的RTTI级别符合我们对现代框架的期望:不仅仅是用一些类来存储JSON,还可以使用像类或记录这样的结构进行方便的序列化/反序列化。<br>
使用现代Pascal动态数组和记录来定义数据结构,让编译器为我们利用内存,无需编写任何块和使用接口。Pascal的“手动内存管理”不是强制性的,而且可以很容易地绕过。只有对于WebServer,我们有一个预期会关闭它的 <code>try..finally..Free</code>和 <code>Free</code>。</p>
<h4 id="给我一些数字">给我一些数字</h4>
<p>以下是与Go的性能比较(左侧为FPC,右侧为Go):</p>
<table>
<thead>
<tr>
<th>参数</th>
<th>FPC性能</th>
<th>Go性能</th>
</tr>
</thead>
<tbody>
<tr>
<td>-</td>
<td>在968.43毫秒内解析了1790905个停车时间</td>
<td>在3.245251432秒内解析了1790905个停车时间</td>
</tr>
<tr>
<td>-</td>
<td>在39.54毫秒内解析了71091次行程</td>
<td>在85.747852毫秒内解析了71091次行程</td>
</tr>
<tr>
<td>-</td>
<td>正在运行(0m33.4s),00/50 VUs,348个完成,0个中断</td>
<td>正在运行(0m32.3s),00/50 VUs,320个完成,0个中断</td>
</tr>
<tr>
<td>-</td>
<td>默认✓ [===================] 50 VUs 30</td>
<td>默认✓ [===================] 50 VUs 30</td>
</tr>
<tr>
<td>数据接收量</td>
<td>31 GB,933 MB/s</td>
<td>31 GB,971 MB/s</td>
</tr>
<tr>
<td>数据发送量</td>
<td>3.2 MB,97 kB/s</td>
<td>3.0 MB,92 kB/s</td>
</tr>
<tr>
<td>http请求被阻止</td>
<td>平均=9微秒,最小=1.09微秒</td>
<td>平均=6.77微秒,最小=1.09微秒</td>
</tr>
<tr>
<td>http请求连接</td>
<td>平均=2.95微秒,最小=0秒</td>
<td>平均=1.73微秒,最小=0秒</td>
</tr>
<tr>
<td>http请求持续时间</td>
<td>平均=47.59毫秒,最小=97.28微秒</td>
<td>平均=49.02毫秒,最小=123.81微秒</td>
</tr>
<tr>
<td></td>
<td>平均=47.59毫秒,最小=97.28微秒</td>
<td>平均=49.02毫秒,最小=123.81微秒</td>
</tr>
<tr>
<td>http请求失败</td>
<td>0.00%,✓ 0,✗</td>
<td>0.00%,✓ 0,✗ 3</td>
</tr>
<tr>
<td>http请求接收</td>
<td>平均=9.66毫秒,最小=15.35微秒</td>
<td>平均=5.92毫秒,最小=14.76微秒</td>
</tr>
<tr>
<td>http请求发送</td>
<td>平均=87.24微秒,最小=5.2微秒</td>
<td>平均=70.71微秒,最小=5.2微秒</td>
</tr>
<tr>
<td>http请求TLS握手</td>
<td>平均=0秒,最小=0秒</td>
<td>平均=0秒,最小=0秒</td>
</tr>
<tr>
<td>http请求等待</td>
<td>平均=37.83毫秒,最小=54.74微秒</td>
<td>平均=43.02毫秒,最小=91.84微秒</td>
</tr>
<tr>
<td>http请求</td>
<td>34452,1032.205528/秒</td>
<td>31680,981.949476/秒</td>
</tr>
<tr>
<td>迭代持续时间</td>
<td>平均=4.72秒,最小=3.54秒</td>
<td>平均=4.86秒,最小=2.19秒</td>
</tr>
<tr>
<td>迭代次数</td>
<td>348,10.426318/秒</td>
<td>320,9.918682/秒</td>
</tr>
<tr>
<td>vus</td>
<td>30,最小=30,最大</td>
<td>15,最小=15,最大</td>
</tr>
<tr>
<td>vus_max</td>
<td>50,最小=50,最大</td>
<td>50,最小=50,最大</td>
</tr>
</tbody>
</table>
<p>其实文本也很好看</p>
<pre><code>parsed 1790905 stop times in 968.43ms                        | parsed 1790905 stop times in 3.245251432s
parsed 71091 trips in 39.54ms                                  | parsed 71091 trips in 85.747852ms

running (0m33.4s), 00/50 VUs, 348 complete and 0 interrupted   | running (0m32.3s), 00/50 VUs, 320 complete and 0 interrupted
default ✓ [======================================] 50 VUs30    default ✓ [======================================] 50 VUs30

   data_received..................: 31 GB933 MB/s          |      data_received..................: 31 GB971 MB/s
   data_sent......................: 3.2 MB 97 kB/s         |      data_sent......................: 3.0 MB 92 kB/s
   http_req_blocked...............: avg=9µs   min=1.09µs   |      http_req_blocked...............: avg=6.77µsmin=1.09µs
   http_req_connecting............: avg=2.95µsmin=0s       |      http_req_connecting............: avg=1.73µsmin=0s   
   http_req_duration..............: avg=47.59ms min=97.28µs|      http_req_duration..............: avg=49.02ms min=123.81µ
       { expected_response:true }...: avg=47.59ms min=97.28µs|      { expected_response:true }...: avg=49.02ms min=123.81µ
   http_req_failed................: 0.00%✓ 0         ✗|      http_req_failed................: 0.00%✓ 0          ✗ 3
   http_req_receiving.............: avg=9.66msmin=15.35µs|      http_req_receiving.............: avg=5.92msmin=14.76µs
   http_req_sending...............: avg=87.24µs min=5.2µs    |      http_req_sending...............: avg=70.71µs min=5.2µs
   http_req_tls_handshaking.......: avg=0s      min=0s       |      http_req_tls_handshaking.......: avg=0s      min=0s   
   http_req_waiting...............: avg=37.83ms min=54.74µs|      http_req_waiting...............: avg=43.02ms min=91.84µs
   http_reqs......................: 344521032.205528/s   |      http_reqs......................: 31680981.949476/s
   iteration_duration.............: avg=4.72s   min=3.54s    |      iteration_duration.............: avg=4.86s   min=2.19s
   iterations.....................: 348    10.426318/s       |      iterations.....................: 320    9.918682/s
   vus............................: 30   min=30      ma|      vus............................: 15   min=15       max
   vus_max........................: 50   min=50      ma|      vus_max........................: 50   min=50       max
</code></pre>
<p>因此,CSV加载速度要快得多,而HTTP服务器的性能则几乎相同。</p>
<h4 id="no-alzheimer---无阿尔茨海默症">No Alzheimer---无阿尔茨海默症</h4>
<p>以下是一些关于内存消耗的数据:</p>
<blockquote>
<p>在完成CSV加载后,mORMot仅占用80MB,天哪,这么少。听起来有点神奇。但在负载测试中,它在250-350MB之间波动,测试结束后又回到80MB。Go版本在加载完CSV后占用925MB。在负载测试中,它最高达到了1.5GB,之后又回到了925MB。</p>
</blockquote>
<p>读起来不错。😃</p>
<h4 id="pascal拥有一个现代且功能强大的生态系统">Pascal拥有一个现代且功能强大的生态系统</h4>
<p>这篇文章不仅仅是关于Pascal的,还涉及到算法和库。<br>
最初的挑战是比较它们。不仅仅是作为不现实的微观基准测试,或“计算机语言基准测试游戏”,而是作为实际应用场景中的数据处理能力。</p>
<p><strong>而且……Pascal当然还在竞争中!</strong><br>
不仅仅是为了像我这样的“老人”——我刚满50岁(译者注:本文作者Arnaud Bouchez发表于2022年11月26日 )。😉</p>
<p>我们传播的这类信息越多,就越少有人会拿Pascal程序员开玩笑。<br>
Delphi和FPC与Java一样古老,所以是时候看到大局了,而不是跟随市场趋势。</p><br><br>
来源:https://www.cnblogs.com/hieroly/p/18216108
頁: [1]
查看完整版本: Modern Pascal is Still in the Race (Modern Pascal 仍在竞赛中)