追 MediaWiki 頁面很慢的問題

我自己有個 wiki 拿來堆一些資料,前陣子覺得 Live 這頁很慢,實際測試發現要跑五秒多,想說到底是什麼鬼...

先從 MediaWiki 的各種 optimization 設定開始看,像是 Manual:Cache 這邊提到的東西,一路看下來看起來都已經設好最佳化了。

只好拿 profiling 工具來翻,在 PHP 上面用的是 Xdebug,裝了以後希望是特殊的情況才跑 profiling,所以參考了「Profiling」這邊的說明,在 php.ini 裡面這樣設定:

xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.trigger_value=StartProfileForMe

接著依照說明,你要找個地方把這個值傳進去:

Xdebug's profiler will only start when either the environment variable XDEBUG_TRIGGER is set to StartProfileForMe, the GET or POST variable XDEBUG_TRIGGER is set to StartProfileForMe, or when the cookie XDEBUG_TRIGGER has the value StartProfileForMe.

我是在網址列上直接用 ?XDEBUG_TRIGGER=StartProfileForMe 觸發,預設值會把 profiling 結果丟到 /tmp 下面,接下來就是把 callgrind 檔案視覺化了。

首先是把 xdebug 的 callgrind 格式輸出檔案轉成 DOT 格式,這邊參考「Interpreting callgrind data」的說明,先裝 gprof2dot,這個軟體在 PyPI 上有,所以可以用 pipx 裝,接著把 callgrind 轉完後再轉成 PNG 檔案。

我把圖放到最後面 (點開可以看大圖),第一張是 Live 這頁的 profiling,第二張是 Hosting 這頁的。

我注意到在 Live 這頁 php::mysqli 這邊被呼叫了幾千次,這邊就算是本地端的資料庫,幾千次的 latency 累積起來還是很驚人,畢竟是跨 process 之間的溝通,而且已知是 ping-pong 要等待的情況。

接著去翻是什麼造成的,看起來跟 selectRow 有關,但線索到這邊就斷掉了,上面的 User->load 看起來並沒有 loop 導致大量呼叫 selectRow

但至少有個方向,從網路上沒什麼討論看起來,應該不會是本體的效能問題,而是 extension 造成的,從這個角度開始追與帳號 & 權限有關的 extension,第一發懷疑是 Extension:AccessControl 造成的,結果果然沒錯... 拔掉後頁面的速度就從 5.1 秒左右降到 1.5 秒左右了。

沒有實際追程式碼,但猜測是 wiki 裡面用的 Template 造成 AccessControl 都會去檢查權限,加上這邊沒有上 cache,造成頁面上 Template 一多效能很差...

最後,這是 Live 頁的 profiling graph:

這是 Hosting 頁的 profiling graph:

HTTP/3 + QUIC 的效率問題

前天看到討論蠻熱烈問題,提到了 HTTP/3 + UDP-based 的 QUIC 的效率比起 HTTP/2 + TCP-based 的 TLS 慢很多的問題:「QUIC is not quick enough over fast internet (acm.org)」,原文在「QUIC is not Quick Enough over Fast Internet」,如果要看 PDF 的話可以看預印本上的「QUIC is not Quick Enough over Fast Internet」。

之所以會有這麼多討論,是因為這篇文章說 HTTP/3 + QUIC 得到的效果與當初宣稱的完全相反,在所有的測試下都比 HTTP/2 + TLS 慢很多。

包括了在高速的網路下差了 45.2% (而且是網路愈快差距愈大):

We find that over fast Internet, the UDP+QUIC+HTTP/3 stack suffers a data rate reduction of up to 45.2% compared to the TCP+TLS+HTTP/2 counterpart. Moreover, the performance gap between QUIC and HTTP/2 grows as the underlying bandwidth increases.

以及在輕量的使用下也是 HTTP/2 + TLS 比較快,這邊提到包括各種桌機與手機環境,以及各種瀏覽器:

We observe this issue on lightweight data transfer clients and major web browsers (Chrome, Edge, Firefox, Opera), on different hosts (desktop, mobile), and over diverse networks (wired broadband, cellular).

讀完 PDF 的分析後,看起來是因為 TCP 大家花了很多精力在上面最佳化 50 年了,這邊累積的資產不只是軟體層級上面的最佳化,OS 與硬體上面也是有很多 offload 幫助。

而自己幹輪子的結果就是軟體層也還缺很多東西,硬體層也不支援新的協定...

lite-youtube-embed

繼續清 tab,在「YouTube embeds are heavy and it’s fixable (frontendmasters.com)」這邊看到的是提出改善 YouTube 的外嵌功能 (embed),因為 loading 太肥了。原文在「YouTube Embeds are Bananas Heavy and it’s Fixable」,裡面提到一個只有 YouTube 的 embed (iframe) 頁面就抓了 1.3MB 的資料:

On a page with literally nothing at all on it other than a YouTube Embed, we’re looking at:

32 requests
1.3 MB of data transfer
2.76s to load the page on my current WiFi connection

而「One YouTube Embed weighs almost 1.2 MB」這邊更提到了這邊的 resource 會線性疊加不會共用的:

The weight also grows linearly with every embed—resources are not shared: two embeds weigh 2.4 MB; three embeds weigh 3.6 MB (you get the idea).

測了一下 https://home.gslin.org/tmp/ytembed.html 這個,是 1.2MB transferred:

如果放兩個一樣的影片,也就是 https://home.gslin.org/tmp/ytembed2.html 的話,變成 2.4MB transferred:

所以不共用的部分的確超大,懷疑 iframe 之間不共用資源是不是跟 cache partition 的實作有關:「Google Chrome 要藉由拆開 HTTP Cache 提昇隱私」。

Anyway,所以作者提案用 lite-youtube-embed 這個套件改善:

Provide videos with a supercharged focus on visual performance. This custom element renders just like the real thing but approximately 224× faster.

不過這種事情你想得到,Google 也一定想得到,全篇只講 lite-youtube-embed 的好處一定哪邊有問題。

所以翻一下 Hacker News 上,在 id=40897582 這邊就有人提到缺點了,很明顯 lite-youtube-embed 的載入速度比較慢:

The author says they don't believe that a lighter version has been shown to reduce engagement.

I, on the other hand, fully believe that.

The recommended lite-youtube-embed project page has a demo of both lite and regular players [0], and the lite version takes noticeably longer to start playing the video.

Every additional millisecond of load time will reduce engagement, and here the difference is more on the order of hundreds of milliseconds or more.

[0] https://paulirish.github.io/lite-youtube-embed/

yeah,這樣就合理了。

即使 embed 吃超多資源,但因為 YouTube 是影音網站,主要的流量還是影音的部分,利用這個方法增加載入速度,在成本結構上面可以接受,而且還可以拿到更多瀏覽資料?

但對於網站端以及使用者端就不是什麼愉快的事情,所以網站端要不要用這個套件就是看各自的取捨了。

Nagle's algorithm + TCP delayed acknowledgment

Hacker News 上看到「It's always TCP_NODELAY (brooker.co.za)」,在講常遇到的 TCP 效能問題,原文在「It’s always TCP_NODELAY. Every damn time.」這邊。

這邊提到了兩個 TCP 上的演算法,Nagle's algorithm 是把小封包積著,等到收到 ack 後再集中丟出去,這樣可以降低 TCP overhead;而 TCP delayed acknowledgment 則是在收到封包後要傳回的 ack 累積起來縮成一個 ack 丟出去 (或是等到 timeout),也是為了降低 TCP overhead。

可以看到這兩者的邏輯上雖然都是想要降低 TCP overhead,但方法剛好會打架。而且這兩個在 Linux 下系統預設都會啟用,所以成立條件不算少見,只要發送方的每個封包都比較小就容易觸發 (大封包的情況則是因為把 buffer 塞滿後就會丟出去,所以就不會延遲)。

這時候遇到 application protocol 很吃 latency 的設計時 (像是 ping-pong 類型的溝通),就容易撞到效能問題。

也因為很常見,所以 Hacker News 上也有好幾個人都有提到他們在工作上解過好幾次。

技術上的解決方案是關掉其中一個就可以了,但可以看到通常都是關掉 Nagle's algorithm (也就是設定 TCP_NODELAY),一方面因為大多數伺服器端的軟體都提供這個選項,改起來比較方便 (因為會被回報);另外一方面是是「趕快把封包送出去」會比「趕快收到 ack」來的有效率...

算是因為網路發展後產生的問題,以前只有 64kbps 專線 (8KB/sec) 的年代會斤斤計較這些東西:一個 IPv4 header 要 20 bytes,TCP header 也要 20 bytes,只傳 1 byte 的資料的確很傷頻寬。

但現在網路環境不太一樣了,尤其是文章裡面提到的環境通常是機房,1Gbps 與 10Gbps 算是常態,遇到 bandwidth 不會吃滿,但很需要 rps (request per second) 數量時,拿之前的演算法就容易中獎了...

關於 Hacker News 上面,「假設 CPU 速度上限只有現在的 1/20」的討論

算是 Hacker News 上面的閒聊文章,如果 CPU 只有現在 1/20 的速度的話,軟體開發會變成什麼樣子:「How might software development have unfolded if CPU speeds were 20x slower?」。

其實也沒那麼難想像,如果是拿 CPU 頻率來算 1/20 的話,上限大約是 250~300MHz?這大概是 Pentium II 的年代,1997 年的 CPU,當年主流的作業系統應該是 Windows 95...

裡面有很多討論,不過在 id=39977430 這邊看到:

No electron apps.

幹。

PHP 8.3 相比於 PHP 8.2 的效能提升

找資料的時候意外發現 PHP 8.3 相對於 PHP 8.2 的效能提升好像不算小?目前看到這兩個地方有提到:

前面那篇的 benchmark 數據可以看出來愈大愈複雜的框架,提升的效能就愈多:

  • 乾淨的 WordPress 從 158 rps 成長到 169 rps,大約 7% 的增加。
  • 如果是 WooCommerce 的話從 49 rps 到 58 rps,大約是 18.4%。
  • 接著 Laravel 則是從 670 rps 到 925 rps,提升了 38.1%。
  • Drupal 則是 941 rps 到 1432 rps,提升了 52.2%。

在「Make your app faster with PHP 8.3」這邊提到了 PHP 8.3 改善了很多關於效能的項目。

首先提到的是 JIT 的改善:

The Just-In-Time (JIT) compiler has been further optimized for better efficiency. The execution of scripts is faster and consumes less CPU time. This is especially beneficial for resource-intensive tasks.

然後是 opcode 這邊的改善:

PHP has refined how it handles opcodes (the instructions in the PHP bytecode). Version 8.3 uses more efficient ways to interpret and execute these opcodes. This reduces the execution time of scripts.

然後 GC 機制也改善了:

PHP 8.3 enhances the garbage collection mechanism, which is responsible for freeing memory occupied by unused objects. This results in more efficient memory usage and can significantly improve performance for memory-intensive applications.

array 的改善:

Other improvements include optimizations for handling arrays and an enhanced type system.

對於複雜的應用就很容易都受惠,然後就有頗大的提升...

VirtualBox 內的 Windows 上傳速度很慢的問題

因為我電腦有兩張網卡,兩條線分別接到自己拉的 HiNet 以及社區網路 (不過出去也是 HiNet,這是另外一回事了)。

我桌機的預設 routing 是走自己拉的 HiNet,但我希望 VM 是走社區網路,所以用 bridge mode 設定到網卡上,用 DHCP 取得分享器給的 private IP。

之前一直都沒注意到,前幾天用 Line 傳照片的時候很慢 (之前就有發生了,一直忘記去追問題),花了點時間追問題的時候發現是 VM 裡面的 Windows 10 上傳很慢,這點可以從 Speedtest 的測試結果看到:

先講最後的結論,在交叉測了很多組合後,我發現遇到的問題是把網卡裡的 Large Send Offload (IPv4) (也就是 LSO) 從 Enabled 改成 Disabled

回到當時抓問題的情況,當時先用筆電與 host 測試都沒看到問題,所以看起來應該是 VM 裡面的狀況,但不確定是什麼情況,畢竟不是斷掉...

由於下載速度正常,只有上傳速度卡住,一開始想到的是跟 MTU 相關的問題,所以找了指令降到 1400 後測試,還是一樣...

後來先把 VM 的網路改成 NAT,再測試上傳速度就正常了...

接著想要換個網路卡類型看看,結果卡在找不到 driver。

本來已經想拿 tcpdump 出來追了,但想說先去看看 Windows 10 網卡設定裡面的設定,結果看到 LSO... 就先關看看 (算是以前在 FreeBSD 以及 Linux 下的經驗?)。

然後一關就正常了,交叉再開關兩次確認這個參數有影響,就肯定這個 workaround 應該是有效了...

另外在自己找完問題後,在「Virtualbox 7.0.12 slow upload speed in any Guest OS」這邊看到了類似的問題以及同樣的 workaround。

LSO 過了十幾年還是...

arXiv 上了 Fastly CDN

看到 arXiv 宣佈上了 FastlyCDN:「Faster arXiv with Fastly」。

翻了一下 arxiv.org 的 DNS record,可以看到現在是這樣:

;; ANSWER SECTION:
arxiv.org.              10      IN      A       151.101.131.42
arxiv.org.              10      IN      A       151.101.3.42
arxiv.org.              10      IN      A       151.101.67.42
arxiv.org.              10      IN      A       151.101.195.42

mtr 測試,看起來 HiNet 過去的 routing 還是進到新加坡。

不過 static.arxiv.org 是在 CloudFront 上:

;; ANSWER SECTION:
static.arxiv.org.       3600    IN      CNAME   daa2ks08y5ls.cloudfront.net.
daa2ks08y5ls.cloudfront.net. 60 IN      A       13.35.35.100
daa2ks08y5ls.cloudfront.net. 60 IN      A       13.35.35.29
daa2ks08y5ls.cloudfront.net. 60 IN      A       13.35.35.88
daa2ks08y5ls.cloudfront.net. 60 IN      A       13.35.35.127

依照官方的說明看起來還在換,只是不知道已經在 CloudFront 上的 (像是上面提到的 static.arxiv.org) 會不會換過去:

That includes our home page, listings, abstracts, and papers — both PDF and HTML (more on that soon).

AMD Zen 3 與 Zen 4 上 FSRM (Fast Short REP MOV) 的效能問題

前幾天 Hacker News 上討論到的一篇:「Rust std fs slower than Python? No, it's hardware (xuanwo.io)」,原文則是在「Rust std fs slower than Python!? No, it's hardware!」。

原因是作者收到回報,提到一段 Rust 寫的 code (在文章裡面的 read_file_with_opendal(),透過 OpenDAL 去讀) 比 Python 的 code 還慢 (在文章裡面的 read_file_with_normal(),直接用 Python 的 open() 開然後讀取)。

先講最後發現問題是 Zen 3 (桌機版 5 系列的 CPU) 與 Zen 4 (桌機版 7 系列的 CPU) 這兩個架構上 REP MOV 系列的指令在某些情境下 (與 offset 有關) 有效能上的問題。

FSRM 類的指令被用在 memcpy()memmove() 類的地方,算是很常見備用到的功能,這次追蹤的問題發現在 glibc 裡面用到導致效能異常。

另外也可以查到在 Linux kernel 裡面也有用到:「Linux 5.6 To Make Use Of Intel Ice Lake's Fast Short REP MOV For Faster memmove()」,所以後續應該也會有些改善的討論...

Ubuntu 這邊的 issue ticket 開在「Terrible memcpy performance on Zen 3 when using rep movsb」這,上游的 glibc 也有對應的追蹤:「30995 – Zen 4: sub-optimal memcpy on very large copies」。

從作者私下得知的消息,因為 patch space 的大小限制,AMD 可能無法提供 CPU microcode 上的 patch,直接解決問題:

However, unverified sources suggest that a fix via amd-ucode is unlikely (at least for Zen 3) due to limited patch space. If you have more information on this matter, please reach out to me.

所以目前比較可行的作法是在 glibc 裡面使用到 FSRM 的地方針對 Zen 3 與 Zen 4 放 workaround,回到原來沒有 FSRM 的方式處理:

Our only hope is to address this issue in glibc by disabling FSRM as necessary. Progress has been made on the glibc front: x86: Improve ERMS usage on Zen3. Stay tuned for updates.

另外在追蹤問題的過程遇到不同的情境,得拿出不同的 profiling 工具出來用,所以也還蠻值得看過一次有個印象:

一開始的 timeit 算是 Python 裡面簡單的 benchmark library:

接著的比較是用 command line 的工具 hyperfine 產生出來的 (給兩個 command 讓他跑),查了一下發現在 Ubuntu 官方的 apt repository 裡面有包進去 (22.04+):

再來是用 strace 追問題,這個算是經典工具了,可以拿來看 syscall 被呼叫的時間點:

到後面出現了 perf 可以拿來看更底層的資訊,像是 CPU 內 cache 的情況:

接續提到的「hotspot ASM」應該也還是 perf 輸出的格式,不過不是那麼確定... 在「perf Examples」這邊可以看到 function 的分析:

而文章裡的則是可以看到已經到 assembly 層級了:

差不多就這些...

FreeBSD 14.0 對比於 13.2 有顯著的效能提升

Hacker News 上看到「FreeBSD 14.0 Delivering Great Performance Uplift (phoronix.com)」這篇,原文在「FreeBSD 14.0 Is Delivering Great Performance Uplift & Running Well In Early Tests」這邊。

測試平台是 AMD EPYC™ 8534P (64 cores & 128 threads),是個今年九月才推出的 CPU,另外底層 filesystem 是跑 ZFS

翻了一輪測試的資訊,幾乎是所有的項目都有提升 (少數幾樣有些微退步),但以 Phoronix 的測試計算,整體計算起來有 18% 的提升,對於 OS 升級帶來的提升算是蠻巨大的:

Across the span of five dozen benchmarks carried out on this AMD EPYC 8534P server of FreeBSD 13.2 vs. FreeBSD 14.0, the newly-released FreeBSD 14 was on average 18% faster than its predecessor. Not bad for a simple OS upgrade. I've been seeing very healthy gains on other x86_64 servers tested so far while due to hardware availability haven't yet tried any AArch64 servers.

依照他的說明,後續會有跟其他 Linux distribution 的比較,到時候可以回來再看看:

I'll be running more FreeBSD 14.0 server benchmarks shortly along with following that up by looking at the FreeBSD 14.0 performance against the latest leading Linux distributions. In any event I'm quite happy thus far with the performance and experience in my FreeBSD 14.0 testing.

回頭看報告裡面比較特別的部分,一個是 OpenSSL 的部分有下滑一些,這點應該跟版本更新有關,在 FreeBSD 14.0 的 Release Notes 裡面有提到大版本升級:

OpenSSL has been upgraded to version 3.0.12. This is a major upgrade from version 1.1.1, which has reached its end of life. Many components of the base system use a backward-compatible API, but will be migrated later. aa7957345732 930cec16d9ee b077aed33b7b (Sponsored by The FreeBSD Foundation)

另外一點是在 Page 4 裡面,可以看到 PostgreSQL 16 的效能提升非常明顯,無論是 TPS 還是 latency 都有非常巨大的改善。