GTA Online 釋出官方修正,大幅改善啟動效能

看到「GTA Online load time fix released, shaves off actual minutes of waiting for some」這邊的消息,先前在「GTA 的啟動讀取效能問題」這邊提到 GTA Online 啟動速度很慢的問題,官方正式推出修正版本了:「GTAV Title Update 1.53 Notes (PS4 / Xbox One / PC)」。

抓了一些在 Reddit 的討論「Loading Times Have FINALLY been patched - Discussion Thread」。

這則降的比率與當時 workaround 的修正差不多:

Insane. GTA menu -> GTA: Online.

Dropped from 7 minutes to 1:57

i7-2600k,GTX1070,16GB RAM and the game is on HDD.

這個就有點誇張了,這是 90% 吧?

Dropped from 5-8 minutes to 35 seconds

這個差不多 70%~80%:

Loading time 2m 20s for online directly from steam. Before it was like 8-10 minutes for me. Damn

Edit: 50s for story mode. 35s from story mode to online. So it seems it's still faster to load into online from story mode.

這個也差不多 70%:

From 4-5 minutes to 1 a minute and 22 seconds. Y e s p l e a s e

然後 PS4 的版本原來也受到一樣的影響?

Currently tested on PS4 , from main menu to online : 3min 45 sec From story mode to online: 1min 20sec (😩 i can't tell for sure )

整體看起來是正面的,畢竟大家等這個問題等超久了... 另外也可以看出來當初的 workaround patch 其實相當精準的把問題都解掉了,官方的修正並沒有快更多。

來繼續關注 libc 那邊的問題...

GTA 的啟動讀取效能問題

這件事情也已經過了一個禮拜,來整理一下發生什麼事情...

起因是 GTA Online 的遊戲開啟速度很慢,而有人一路 reverse engineering 找出問題並且解決:「How I cut GTA Online loading times by 70%」,對應的 Hacker News 討論有提到其他有趣的事情也可以看看:「How I cut GTA Online loading times by 70% (nee.lv)」。

作者的電腦不算太差,但光開啟 GTA Online 就需要六分鐘,網路上甚至有辦投票蒐集大家的等待時間,發現也有很多人反應類似的問題:

接下來就開始 reverse engineering 了,先觀察各種狀態後發現是卡在 CPU,而不是網路或 Disk I/O,然後就拿出 Luke Stackwalker 這個工具 profiling,不過因為沒有 debug symbol 幫忙 group,所以只能人工判斷後,可以看到兩個問題:

第一個問題發現效能是卡在 strlen(),而 call stack 可以看出來是從 sscanf() 一路打進去的:

反追發現是在處理 10MB 的 JSON 檔造成的,裡面 sscanf() 因為拉出 strlen(),於是就造成把整個 10MB 的 JSON 掃過很多次 (一開始是 10MB,掃到後面會愈來愈少,平均下來應該是 5MB):

第二個問題產生的時間會在第一個問題跑完後,另外看問題的性質,應該跟第一個 JSON 處理有關,他會把 JSON 處理過的資料丟進 array,每個 entry 長這樣:

struct {
    uint64_t *hash;
    item_t   *item;
} entry;

丟進 array 是 OK 的,但問題在於他需要判斷 entry 是否重複,卻沒有用 hash 或是 tree 的結構,而這邊大約有 63k 筆資料,用 array 實做就產生了 O(n^2) 的演算法:

But before it’s stored? It checks the entire array, one by one, comparing the hash of the item to see if it’s in the list or not. With ~63k entries that’s (n^2+n)/2 = (63000^2+63000)/2 = 1984531500 checks if my math is right. Most of them useless. You have unique hashes why not use a hash map.

作者在 PoC 的章節裡面描述他怎麼解這兩個問題。

第一個問題比較好的解法是修正 JSON Parser,但這太複雜,所以他用 workaround 解:把 strlen() 包起來,針對長字串加上一層 cache:

  • hook strlen
  • wait for a long string
  • “cache” the start and length of it
  • if it’s called again within the string’s range, return cached value

而第二個問題他直接把檢查是否有重複的跳過,因為資料本身不重複:

And as for the hash-array problem, it’s more straightforward - just skip the duplicate checks entirely and insert the items directly since we know the values are unique.

整個開啟的速度從六分鐘降到一分五十秒,還是偏慢,但算是大幅緩解的 GTA Online 啟動速度的問題了。

不過故事到這邊還沒結束,有人一路去挖,發現其實 sscanf() 的效能地雷已經不是第一次了:YAML 的 Parser 也中過一樣的問題:「Parsing can become accidentally quadratic because of sscanf」,這篇也一樣上了 Hacker News:「Parsing can become accidentally quadratic because of sscanf (github.com/biojppm)」。

然後這又帶出了六年前在 StackOverflow 上就有人問過這個問題:「Why is glibc's sscanf vastly slower than fscanf on Linux?」。

另外也有人整理出來,應該是大家把同樣的演算法拿來實做:

JdeBP 3 days ago

I found this while making a collection of what C implementation does what at https://news.ycombinator.com/item?id=26298300.

There are two basic implementation strategies. The BSD (FreeBSD and OpenBSD and more than likely NetBSD too), Microsoft, GNU, and MUSL C libraries use one, and suffer from this; whereas the OpenWatcom, P.J. Plauger, Tru64 Unix, and my standard C libraries use another, and do not.

The 2002 report in the comp.lang.c Usenet newsgroup (listed in that discussion) is the earliest that I've found so far.

後續的更新動作可以再追一下進度 (包括 GTA Online 與各家的 libc)。

AWS 的 ALB 可以用最少未處理連線數分配了

AWSALB 總算提供「最少連線數」的功能了:「Application Load Balancer now supports Least Outstanding Requests algorithm for load balancing requests」。

先前只有 Round robin (RR),現在支援 Least outstanding requests (LOR) 後可以往比較少的塞了,這個方法在大多數的應用上比 RR 合理多了,現有的 ALB 應該都可以考慮換過去...

補功能不算快,但有種已知用火的感覺 XD

延遲載入 CSS 的方式

在「Simpler way to load CSS asynchronously」這邊看到的技巧,利用了 onload<noscript> 的方式,達成延遲載入 CSS 的效果:

<link rel="stylesheet" href="/path/to/my.css"
      media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="/path/to/my.css"></noscript>

對於非必要的 CSS 可以用這樣的方式加強,看起來頗不賴... 這邊提到的方式,作者是引用「Smashing Newsletter: Issue #234」,不過查了一下發現 2015 的時候有人在 StackOverflow 上回答過:「How to load CSS Asynchronously」,不確定有沒有更早的資料...

另外一個比較新的語法是使用 rel="preload",但支援度就不太好了,需要靠 polyfill 之類的方式補上 (於是又多了一些東西要 load),反而不如前面提到的方式來的簡單。

Twitter 對 2x 與 3x 的圖片的研究...

所以發現很多時候用 2x 的圖片就夠了?:「Capping image fidelity on ultra-high resolution devices」。

會這樣討論主要是發現螢幕特性:

The most modern screens are OLED. These screens boast some really great features like pure blacks, and are marketed as 3x scale. However, nearly no "3x scale" OLED actually has perfect 3x3 pixels per dot on their screen.

因為螢幕不是真的到 3x 的要求,丟 2x 的圖片出去就好,省頻寬又省下載時間:

This means that most OLED screens that say they are 3x resolution, are actually 3x in the green color, but only 1.5x in the red and blue colors. Showing a 3x resolution image in the app vs a 2x resolution image will be visually the same, though the 3x image takes significantly more data. Even true 3x resolution screens are wasteful as the human eye cannot see that level of detail without something like a magnifying glass.

省下 38% 的資料量,32% 的時間:

There's no difference that the human eye can see, but will save 38% on data and 32% on latency on the capped image load for this particular example which is reflective of most images that load on Twitter.

這也另外帶出了其他的想法,如果沒有太多時間研究的話,可以考慮先提供 2x 的就好,不需要特地做 3x 的版本...

針對 JavaScript 時代調整網頁的效能評估指標

早期網頁的效能評估指標都沒有考慮 JavaScript 的情況,大多都是 TTFB (Time to First Byte) 或是網頁大小以及 DOMContentLoaded 或是 load 這類 DOM event 為主,但因為 Goodhart's law,現代的網頁設計會故意將許多 JavaScript 要做的事情搬到 load 以後開始做,以降低 load 被延遲的問題,讓前端的「KPI」比較好看:

When a measure becomes a target, it ceases to be a good measure.

但在 load 之後整個網站還是不能用,使用者的體驗其實很差,這個評估方式的價值變低不少。所以「Measuring Jank and UX」這篇就再找出一些新的指標,來評估 JavaScript 造成的問題。

可以看到文章裡面評估了很多關於 CPU loading 與操作時間的指標,也許這一兩年還會有用,不過我覺得還是會遇到 Goodhart's law 描述的問題... XD

Chrome 對各種 JavaScript 的優先順序

前陣子看到「JavaScript Loading Priorities in Chrome」這篇,在分析 Google Chrome 對各種 JavaScript 的優先順序。

優先順序分成讀取的「Loading priority (network/Blink)」與執行的「Execution priority」,另外文章裡也有整理建議「Where should this be used?」。

看起來 <script defer> at the end of <body> 是全部裡面最低的,建議是給 Load "Related articles" 或是 "Give feedback" 這類功能,不過應該沒什麼人真的這樣用...

然後要注意的是,這邊分析的對象是 Google Chrome,實際在設計時應該要先考慮一般性的定義,再考慮對各瀏覽器的最佳化... (雖然以現在市占率來說沒什麼人想管其他瀏覽器...)

用 link="preload" 提高下載的優先度

除了讓 browser 自己決定優先權外,在「Preload Scripts」這邊看到的技巧,可以跟 browser 說明哪些資源比較重要,請儘快先下載:

<link rel="preload" href="main.js" as="script">

Link rel=preload is useful for downloading any important resource more quickly, such as stylesheets that contain critical CSS, fonts that are used in important design elements, and hero images. It's especially important for scripts because they block page content from rendering and consume the most CPU during page load.

以作者的想法,這個技巧應該用在會卡住頁面呈現的部分,確保這些資源可以優先下載。

另外作者也提到了可以直接把這個資訊放到 HTTP header 裡面,理論上會更快:

Link: <main.js>; rel="preload"; as="script"

尤其是 sync script 應該會有幫助,建議可以跑 A/B test 看看效果:

We know that synchronous scripts block rendering, which makes the user experience feel slow. And we know that most scripts today are downloaded synchronously (rather than async). And yet only 1% of sites are using link rel=preload to download their scripts. If your site has any synchronous scripts, do an A/B test adding link rel=preload for them. It's likely this will be a win and help you create a more joyous experience for your users.

Linux 下 RAID1 的 SSD 會有讀取不平均問題

在「Unbalanced reads from SSDs in software RAID mirrors in Linux」這邊看到作者看 S.M.A.R.T. 數據時發現兩顆 SSD 硬碟組成的 RAID1 有很明顯的讀取不平均的問題:

242 Total_LBAs_Read [...] 16838224623
242 Total_LBAs_Read [...] 1698394290

原因是因為 Linux 對 RAID1 的 SSD 有不一樣的演算法:

The current state of RAID1 read balancing is kind of complex, but the important thing here in all kernels since 2012 is that if you have SSDs and at least one disk is idle, the first idle disk will be chosen.

2016 時演算法就更激進了,變成非 SSD 會:

In kernels with the late 2016 change, this widens to if at least one disk is idle, the first idle disk will be chosen, even if all mirrors are HDs.

加上 SSD 很快,這造成 loading 幾乎都在第一顆上... 這對 SSD 應該是還好啦 (理論上 SSD 的讀取不傷壽命),不過還是有點怪就是了。

Facebook 用 PJPEG 的技巧

Facebook 在「Faster Photos in Facebook for iOS」這篇提到使用 PJPEG 降低流量並且提昇大圖的速度。

實際上我覺得有點詭異,依照說明,降低流量的部份是因為原來有縮圖與大圖:

而現在只要抓一張,所以綜合起來降低了:

但不是每一張都這樣吧?另外是降低了到一定品質所需 image loading 的時間,但這樣看起來用小縮圖應該會更好?雖然 Facebook 的人是以 A/B testing 驗證,但以目前 Facebook 提供的數據並不通,看不太懂他的邏輯...

先丟著吧...