Theme NexT works best with JavaScript enabled
0%

在換到 HP Spectre x360 之後,有件事情一直困擾著我:明明已經在電源設定裡將自動調整亮度給關閉了,為什麼在切換到 Terminal 之後,亮度還是會自己調暗,但是系統的亮度設定卻還是正常呢?而且一切到較亮的視窗(例如白底的網頁),亮度又會神奇地自我調整回來。於是使用的過程中就得不斷適應各種不一的亮度,久而久之眼睛也更容易疲勞。

在爬過各地的文章後才知道,原來一切都是一個叫作 Intel Display Power Saving Technology 的技術所造成的。這個技術的原意是在顯示較暗的圖像時,自動調低亮度以增加筆電的使用時間。雖然立意良好,但是實作的成果仍是蠻讓人惱怒的。

在早期的機器上,可以透過 Intel 內顯的驅動程式來關掉 Intel DPST ,但是像是我的 HP Spectre x360 有著自定版本的驅動程式,所以就算你到 Intel 官網上下載最新的驅動程式回來,安裝時仍會出現錯誤,不給你安裝的機會。

這時候就只能透過修改機碼的方式來關閉 Intel DPST:

先開始 regedit ,並透過下面的路徑找到 FeatureTestControl

1
[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000]

Imgur

找到之後,可以發現它是用 16 進位(HEX)方式存值,這時我們要修改的是第 5 個 bit 。如果你不是如我這種的阿宅工程師,你可以打開系統內建的計算機,選擇程式設計人員模式後,用 16 進位輸入值,最下方的 BIN 就可以看到二進度的表示方式,此時將第二個位數加上 1 (這裡是 8200 加上 1 後為 8210 ),看到下面右手邊數來第五個位數為 1 即可,接著只要重新開機就能夠關閉煩人的 DPST 了。

Imgur

這兩天因為想改善一點 Bash on Ubuntu on Windows 的效能,所以開始把一些常用/核心的工具加上 -mtune=native 重新編譯,結果越編越起勁,把 glibc 也跟著重編後, Bash 就開不起來了 XD

這裡紀錄一下重新安裝 Windows Subsystem for Linux 的方式:

lxrun /uninstall /full : 解除安裝

lxrun /install : 安裝

非常地直覺,不過這時我又遇到了解除安裝失敗的問題,這會造成在重新安裝的時候出現 0x80070091 的錯誤,這時候就要到 %localappdata%\lxss 下把 Ubuntu 根路徑的檔案全部移除後再安裝。

Recently, after updated Grape from 0.15 to 0.19.1, this unexpectedly slowed our test suite down by almost 60% overall. In an extreme case, an example is 26x slower than it was. For a guy who just made the test suite able to finish within 5 minutes, it is definitely unacceptable.

So first off, I identified the slowest example (the one I mentioned that is 26x slower) and StackProf it. By profiling that specific example, I was able to say with a little bit more confident that the regression was introduced by Grape::Util::StackableValues#[] since it accounted for 65% of CPU time.

Next, I found the very version introduced the regression is 0.17. Scanned through its commits, one particularly caught my eyes: Improve grape performance

1
2
3
4
5
6
7
8
9
10
11
      def [](name)
return @froozen_values[name] if @froozen_values.key? name
- value = [@inherited_values[name], @new_values[name]]
- value.compact!
- value.flatten!(1)
+
+ value = []
+ value.concat(@inherited_values[name]) if @inherited_values[name]
+ value.concat(@new_values[name]) if @new_values[name]
value
end

It’s easy to see why the second implementation is preferred since Array#concat does automatic flattening if given object is an array.

The problem lies in the effort to prevent nil, which was handled by Array#compact! in previous versions. Here, it is done by adding conditionals to each #concat statement. Which may seems to be harmless and intuitive, these two conditionals actually cause multiple recursive calls of Grape::Util::StackableValues#[], one in conditional, and the other in the statement itself if the conditional is true. As we can see from following profiling result, the difference is enormous:

1
2
3
# v0.16
TOTAL (pct) SAMPLES (pct) FRAME
627 (33.7%) 98 (5.3%) Grape::Util::StackableValues#[]
1
2
3
# v0.19.1
TOTAL (pct) SAMPLES (pct) FRAME
84460 (131.5%) 5726 (8.9%) Grape::Util::StackableValues#[]

From what we’ve known, the solution should be eliminating duplicated calls of Grape::Util::StackableValues#[] in a single statement, while prevent feeding nil to Array#concat.

Here’s my commit to Grape, which is merged and expected to be included in next minor version:

1
2
3
4
5
6
7
8
9
10
      def [](name)
return @froozen_values[name] if @froozen_values.key? name

value = []
- value.concat(@inherited_values[name]) if @inherited_values[name]
- value.concat(@new_values[name]) if @new_values[name]
+ value.concat(@inherited_values[name] || [])
+ value.concat(@new_values[name] || [])
value
end

There was a concern on using empty array would cause extra memory consumption. In the end, we’re convinced it would only take a minor GC to recycle that array thus no memory overhead.

年初的時候將敝社的通知 API 效能提昇了約 90 % ,其中使用到一個技巧,就是將外部的 API calls 放進 Celluloid Futures 中作平行化的 I/O 處理。

一般我們常會看到有人在說 MRI (Matz’s Ruby Implementation) 因為有 Global Interpreter Lock 的緣故,每個 process 最多只能完整地使用一個 CPU 核心的資源,沒辦法作到平行化處理,真的需要平行化處理,需要使用其它 Ruby VM 實作,通常會是最成熟的 JRuby。 或者你想玩最不成熟最實驗性,由我寫的 GobiesVM 也沒人阻止你。</de;>

但其實,自 1.9 開始, MRI 就已經有了 OS 原生等級的 thread 支援,所以你在 Ruby 裡建立的 thread 其實與你在 C 裡建立的 thread 所能提供的平行化相去不遠,只是不能吃滿 CPU 而已。

簡單來說,如果你所需要的平行化處理並不會需要大量的 CPU 資源,而是像網路連線之類會有等待的 I/O 時, MRI 本身的平行化能力就以足夠讓你的程式加速數倍了。

來個例子,下面的這段程式是沒有平行化處理的版本,所以會依序等候每一個 HTTP request 完成後才處理下一個,如果剛好有一個 request 花了比較長的時間回應,整支程式就會停在那裡等,整體的執行時間就會是 (等待時間1 + 等待時間2 + 等待時間3)

1
2
3
4
require 'open-uri'

urls = %w[brucehsu.org life.brucehsu.org blog.brucehsu.org]
urls.each { |url| open("http://#{url}") }

接著則是使用 MRI 提供的 thread 來達到平行化處理的版本,在這個版本中,執行時間會是 max([等待時間1, 等待時間2, 等待時間3])

1
2
3
4
5
6
7
8
require 'open-uri'

urls = %w[brucehsu.org life.brucehsu.org blog.brucehsu.org]
threads = []
urls.each do |url|
threads << Thread.new { open("http://#{url}") }
end
threads.each { |t| t.join }

這樣就可以很簡單地透過平行化來加快多個 I/O 事件的執行時間了。

「咦,那標題的 Celluloid Future 是要用在哪裡?」

很多時候,我們所需要作的,並不只是單純的 I/O ,而是透過 I/O 取得資料後的處理。如果資料彼此之間沒有相依性,當然可以很簡單地在 thread block 中就解決掉,但是若現在要作的是像對資料排序,這種會需要處理到資料之間關係的動作,就會遇到同一個物件被多個 threads 存取時的 synchronization 問題。(附帶一提,在 MRI 當中因為有 GIL 的緣故,所以你就算完全沒有處理這個問題基本上也不太會出錯,但是拿到 JRuby 上就會有不可預期的結果。這裡當然是建議大家,既然我們知道這個問題的存在就正視它吧,更何況 Ruby 3.0 的一個重點就是要將 GIL 拿掉呢。)

這裡要介紹的 Celluloid Future ,其實就是一個將 thread 包裝起來的抽象化概念,讓使用者可以不用處理到複雜且容易出錯的 synchronization 。

我們直接來改寫上面的程式碼片段:

1
2
3
4
5
6
7
8
9
10
11
12
require 'open-uri'

require 'celluloid'
require 'celluloid/future'

urls = %w[brucehsu.org life.brucehsu.org blog.brucehsu.org]
futures = []
urls.each do |url|
futures << Celluloid::Future.new { open("http://#{url}") }
end

futures.each { |future| puts future.value }

Celluloid Future 會幫我們將值給儲存起來,當呼叫 future.value 時,才會嘗試存取結果。在這個例子裡,若是 HTTP request 尚末完成,整支程式則會 block 在那裡,等待回應。