Modern Pascal is Still in the Race (Modern Pascal 仍在競賽中)

海利鸟發表於2024-05-27

Modern Pascal 仍在競賽中

作者:Arnaud Bouchez,2022年11月26日。永久連結

  • Pascal 程式設計
  • 部落格
  • 集合
  • 跨平臺
  • 資料庫
  • Delphi
  • FPC
  • 垃圾回收器
  • 泛型
  • Go
  • 優秀實踐
  • 超程式設計
  • mORMot
  • mORMot2
  • 效能
  • RTTI
  • Rust

最近在Lazarus/FPC 論壇上的一項民意調查突顯了一個事實:Pascal 程式設計師比其他大多數程式設計師年齡都大。通常來說,到了我們這個年紀,應該做管理人員而非開發人員了。但我們仍喜歡用 Pascal 程式設計。幾十年過去了,它仍然很有趣!

但這是否意味著你不應該使用 Pascal 來做任何新專案呢?語言/編譯器/庫是否過時了呢?

在我目前工作的公司裡,我們有一些年輕的程式設計師,他們有的是剛畢業,有的還在上學,他們加入了團隊並寫出了出色的程式碼!

img

最近在同一個論壇上的一個帖子討論了使用 C#、Go、Scala、TypeScript、Elixir 和 Rust 等語言實現 REST 伺服器的比較。

即將貢獻出幾個 Pascal 版本,其中之一就是 mORMot 大放異彩的版本。

挑戰與演算法

最初的挑戰可在 transit-lang-cmp 找到,其中包含所有這些花哨語言和庫的原始碼。

實際上,此測試程式的目標是載入兩個大型 CSV 到記憶體中(80MB + 2MB),然後透過 HTTP 提供由路由識別符號生成的 JSON,同時連線兩個 CSV。

生成的 JSON 大小可能在 30KB 到 2MB 之間。所有資料都是根據記憶體中的 CSV 實時生成的。

說句公道話,一個普通的商業程式設計師會為此使用資料庫。而不是傻傻的記憶體結構。並要求資金支援,以建立一組龐大的雲端計算機器和負載均衡。😃

遵循mORMot的方式

對於FPC中的mORMot版本,我採用了另一種方法,使用了兩種不同的演算法:

  • 我確保列表在記憶體中是已排序的,然後在其中進行了O(log(n))的二分查詢;
  • 所有儲存的字串都被“內聯”了,即相同的文字共享一個字串例項,FPC的引用計數發揮了它的魔力。

這裡沒有使用像手動生成JSON或使用複雜資料結構這樣的底層技巧——資料結構仍然是高階的,具有可讀的欄位名等。邏輯和意圖都是清晰可讀的。
我們只是利用了Pascal語言和mORMot的特性。例如,如果需要,字串內聯是框架的一部分。

檢視我們儲存庫中的原始碼

結果如下:

  • 程式碼仍然可讀、簡潔且高效(大部分處理都是由mORMot完成的,例如CSV、搜尋、JSON);
  • 它使用的記憶體要少得多——儲存資料時使用的記憶體比Go少10倍,提供服務時使用的記憶體比Go少5倍;
  • 效能與Go及其經過高度調優/最佳化的編譯器和RTL一樣快。

img

演算法的重要性

主要的思想是讓演算法匹配輸入資料和預期的結果集。
就像程式設計師在程式設計遊戲時所做的那樣。而不像編碼人員在編寫商業軟體時那樣隨意。😉

  • 由於使用了mORMot有效地對映了動態陣列儲存及其CSV和JSON功能,原始碼仍然非常可讀。TDynArray
  • 我猜原始碼對於校外程式設計師來說仍然是可以理解的——比Rust等語言更可讀。

公平地說,我使用了型別化指標,但理解它們的目的並不難,而且FPC會將此函式編譯成非常高效的彙編程式碼。我本來可以使用帶有索引的常規動態陣列訪問,這樣雖然會稍微慢一點,但並不更容易跟蹤,也不更安全(如果我們在沒有範圍檢查的情況下編譯)。TScheduler.BuildTripResponse

值得注意的是,我們沒有進行任何特定的調優,例如像其他框架所做的那樣使用常量預分配結果。我們只是指定了資料,然後讓mORMot來處理它——僅此而已。
mORMot的RTTI級別符合我們對現代框架的期望:不僅僅是用一些類來儲存JSON,還可以使用像類或記錄這樣的結構進行方便的序列化/反序列化。
使用現代Pascal動態陣列和記錄來定義資料結構,讓編譯器為我們利用記憶體,無需編寫任何塊和使用介面。Pascal的“手動記憶體管理”不是強制性的,而且可以很容易地繞過。只有對於WebServer,我們有一個預期會關閉它的 try..finally..FreeFree

給我一些數字

以下是與Go的效能比較(左側為FPC,右側為Go):

引數 FPC效能 Go效能
- 在968.43毫秒內解析了1790905個停車時間 在3.245251432秒內解析了1790905個停車時間
- 在39.54毫秒內解析了71091次行程 在85.747852毫秒內解析了71091次行程
- 正在執行(0m33.4s),00/50 VUs,348個完成,0箇中斷 正在執行(0m32.3s),00/50 VUs,320個完成,0箇中斷
- 預設✓ [===================] 50 VUs 30 預設✓ [===================] 50 VUs 30
資料接收量 31 GB,933 MB/s 31 GB,971 MB/s
資料傳送量 3.2 MB,97 kB/s 3.0 MB,92 kB/s
http請求被阻止 平均=9微秒,最小=1.09微秒 平均=6.77微秒,最小=1.09微秒
http請求連線 平均=2.95微秒,最小=0秒 平均=1.73微秒,最小=0秒
http請求持續時間 平均=47.59毫秒,最小=97.28微秒 平均=49.02毫秒,最小=123.81微秒
平均=47.59毫秒,最小=97.28微秒 平均=49.02毫秒,最小=123.81微秒
http請求失敗 0.00%,✓ 0,✗ 0.00%,✓ 0,✗ 3
http請求接收 平均=9.66毫秒,最小=15.35微秒 平均=5.92毫秒,最小=14.76微秒
http請求傳送 平均=87.24微秒,最小=5.2微秒 平均=70.71微秒,最小=5.2微秒
http請求TLS握手 平均=0秒,最小=0秒 平均=0秒,最小=0秒
http請求等待 平均=37.83毫秒,最小=54.74微秒 平均=43.02毫秒,最小=91.84微秒
http請求 34452,1032.205528/秒 31680,981.949476/秒
迭代持續時間 平均=4.72秒,最小=3.54秒 平均=4.86秒,最小=2.19秒
迭代次數 348,10.426318/秒 320,9.918682/秒
vus 30,最小=30,最大 15,最小=15,最大
vus_max 50,最小=50,最大 50,最小=50,最大

其實文字也很好看

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 VUs  30    default ✓ [======================================] 50 VUs  30

     data_received..................: 31 GB  933 MB/s          |      data_received..................: 31 GB  971 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µs  min=1.09µs
     http_req_connecting............: avg=2.95µs  min=0s       |      http_req_connecting............: avg=1.73µs  min=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.66ms  min=15.35µs  |      http_req_receiving.............: avg=5.92ms  min=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......................: 34452  1032.205528/s     |      http_reqs......................: 31680  981.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

因此,CSV載入速度要快得多,而HTTP伺服器的效能則幾乎相同。

No Alzheimer---無阿爾茨海默症

以下是一些關於記憶體消耗的資料:

在完成CSV載入後,mORMot僅佔用80MB,天哪,這麼少。聽起來有點神奇。但在負載測試中,它在250-350MB之間波動,測試結束後又回到80MB。Go版本在載入完CSV後佔用925MB。在負載測試中,它最高達到了1.5GB,之後又回到了925MB。

讀起來不錯。😃

Pascal擁有一個現代且功能強大的生態系統

這篇文章不僅僅是關於Pascal的,還涉及到演算法和庫。
最初的挑戰是比較它們。不僅僅是作為不現實的微觀基準測試,或“計算機語言基準測試遊戲”,而是作為實際應用場景中的資料處理能力。

而且……Pascal當然還在競爭中!
不僅僅是為了像我這樣的“老人”——我剛滿50歲 (譯者注:本文作者Arnaud Bouchez發表於2022年11月26日 )。😉

我們傳播的這類資訊越多,就越少有人會拿Pascal程式設計師開玩笑。
Delphi和FPC與Java一樣古老,所以是時候看到大局了,而不是跟隨市場趨勢。

相關文章