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 都有非常巨大的改善。

Ruby 3.3 的速度再次提升

在「Ruby 3.3's YJIT Runs Shopify's Production Code 15% Faster」這篇提到了 Ruby 3.3 的速度再次提升的消息。

Shopify 上面的測試,3.3.0-preview2 的速度已經比 3.2.2 (兩者都有開啟 YJIT) 快了 13%,而且 p50/p90/p99 都有對應的改善:

不過有提到一些要注意的點,像是記憶體的用量又會再更高 (本來開 YJIT 的時候就已經有增加了),如果是對記憶體比較敏感的環境,會需要注意這點:

Since Ruby 3.3.0-preview2 YJIT generates more code than Ruby 3.2.2 YJIT, this can result in YJIT having a higher memory overlead. We put a lot of effort into making metadata more space-efficient, but it still uses more memory than Ruby 3.2.2 YJIT. We’re looking into skipping compilation of paths that are less frequently executed.

但 server 端應該是還好 (記憶體給多一點),整體是個可以期待的方向...

Amazon EC2 推出 m7a 系列的機種

上一篇完全讀錯段落了,重寫...

Amazon EC2 推出了新的 m7a 的機種:「New – Amazon EC2 M7a General Purpose Instances Powered by 4th Gen AMD EPYC Processors」。

號稱與 m6a 相比有 50% 效能上的提升:

Today, we’re announcing the general availability of new, general purpose Amazon EC2 M7a instances, powered by the 4th Gen AMD EPYC (Genoa) processors with a maximum frequency of 3.7 GHz, which offer up to 50 percent higher performance compared to M6a instances.

不過查了一下價錢,us-east-1m6a.large 是 $0.0864/hr,m7a.large 則是 $0.11592/hr (都是 2 vCPU + 8GB RAM),漲了 34% 左右,如果計算 price performance 的話大約是 10%~15%?的確是不高所以不提 price performance,不過這次 m7a 提供了更小台的 m7a.medium (1 vCPU + 4GB RAM) 來補這塊 (m6a 最小的是 m6a.large),$0.05796/hr。

這樣看起來新的機種對於需要單核效能的應用應該會不錯?

再來是可以租到的區域,目前看起來只有歐美的傳統大區有,亞洲區還要再等等:

Amazon EC2 M7a instances are now available today in AWS Regions: US East (Ohio), US East (N. Virginia), US West (Oregon), and EU (Ireland).

下載 YouTube 影片的技術限制與繞過方法

Hacker News 上看到這篇「How They Bypass YouTube Video Download Throttling」在講 YouTube 防止下載的各種方式。

透過 API 拿到的 URL 直接抓很慢,大約 40-70KB/sec:

However, attempting to download from this URL leads to really slow download:

The speed is always limited to around 40-70kB/s.

這邊需要一個 javascript 環境計算出 n,帶入後續的 request 以「證明」你是官方的網頁 client:

Since mid-2021, YouTube has included the query parameter n in the majority of file URLs. This parameter needs to be transformed using a JavaScript algorithm located in the file base.js, which is distributed with the web page. YouTube utilizes this parameter as a challenge to verify that the download originates from an “official” client. If the challenge is not resolved and n is not transformed correctly, YouTube will silently apply throttling to the video download.

The JavaScript algorithm is obfuscated and changes frequently, so it’s not practical to attempt reverse engineering to understand it. The solution is simply to download the JavaScript file, extract the algorithm code, and execute it by passing the n parameter to it. The following code accomplishes this.

但即使算出 n,也還是會限速,可以看到作者策出來大約是 4MB/sec,雖然比以前快很多了,但還是看得出來有限速。這主要是避免 client 端過度 buffer 浪費頻寬:

With this new URL containing the correctly transformed n parameter, the next step is to download the video. However, YouTube still enforces a throttling rule. This rule imposes a variable download speed limit based on the size and length of the video, aiming to provide a download time that’s approximately half the duration of the video. This aligns with the streaming nature of videos. It would be a massive waste of bandwidth for YouTube to always provide the media file as quickly as possible.

接下來的方式就是利用 Range 拆成很多個 HTTP request 打,這樣因為 buffering algorithm 在開始限速前會先全速塞資料給你,就可以用這點避開限速的問題了。

把多的 request 與處理時間都算進去後,整體大約可以到 50-70MB/sec,算是可以接受的下載速度了:

However, the average speeds typically ranged between 50-70 MB/s or 400-560 Mb/s, which is still pretty fast.

後面有一些合併處理的指令 (因為 YouTube 會把影與音分離成兩個檔案),就不是重點了...