Pony ORM

Simon Willison 的 blog 上看到的東西:「Python’s “Disappointing” Superpowers」,裡面提到的原文是「Python’s “Disappointing” Superpowers」這篇,在講 Python 的工具。

雖然是說「disappointing」,但實際上是反義,在原文裡面提到了很多特別的工具,其中 Pony ORM 算是我覺得最有趣的了,他的寫法就非常的 Python:

select(c for c in Customer if sum(c.orders.price) > 1000)

也可以用 lambda 的形式來寫:

Customer.select(lambda c: sum(c.orders.total_price) > 1000)

這樣會產生出對應的 SQL:

SELECT "c"."id"
FROM "customer" "c"
  LEFT JOIN "order" "order-1"
    ON "c"."id" = "order-1"."customer"
GROUP BY "c"."id"
HAVING coalesce(SUM("order-1"."total_price"), 0) > 1000

不會產生 syntax error 的原因是因為他直接解讀 bytecode 分析,產生出對應的 SQL query:

A normal understanding of generator expressions suggests that the select function is consuming a generator. But that couldn’t explain the behaviour here. Instead, it actually introspects the frame object of the calling code, then decompiles the byte code of the generator expression object it finds, and builds a Query based on the AST objects.

用這樣的設計來達到語法的自由度。

看了一下也有一些 integration,像是 Flask 的「Integration with flask」與 FastAPI 的「Integration with FastAPI」。

不過應該是先看看,目前 Python 上用的主力還是 Django,有自己的 ORM 架構...

Content Defined Chunking (CDC)

前幾個禮拜在 Hacker News Daily 上看到「CDC File Transfer (github.com/google)」這則,連結是指到 GoogleGitHub 專案上,裡面實做了 FastCDC 演算法,另外說明他們為什麼要解這個問題以及對應的成果:「google/cdc-file-transfer」。

Google 的人看起來像是是在 CI/CD 階段遇到頻寬上的問題 (從「The builds are 40-45 GB large.」這邊猜),用 scprsync 看起來都不能解,所以他們自己刻了 FastCDC 演算法來解。

但我對 Content Defined Chunking (CDC) 不熟,所以先查一下 CDC 是什麼東西,就查到 restic 這篇講得很清楚:「Foundation - Introducing Content Defined Chunking (CDC)」。

要計算 delta 很直覺的作法就是要切 chunk,而接著的直覺就是固定大小的 chunk 切開,像是這樣每 16 bytes 切一個 chunk:

0123456789abcdef 0123456789abcdef 0123456789abcdef 0123456789abcdef

如果其中一個地方有變化,但其他沒變化的話就可以透過 cryptographic hash function (像是 SHA-256) 確認 chunk 內容一樣,進而省下很多傳輸的頻寬:

0123456789abcdef 0123456789ABCDEF 0123456789abcdef 0123456789abcdef

但可以馬上看出來這個方法的大缺點是只能處理 replacement,很難處理 insert & delete 的部份,舉例來說,如果變更是在開頭的地方加上 ABC,就會造成 chunk 會完全不一樣,而導致全部都要再傳一次:

ABC0123456789abc def0123456789abc def0123456789abc def0123456789abc def

這邊其實是個經典的演算法問題:想要找出兩個 string 的差異 (把舊的內容當作一個 string,新的內容也當作一個 string)。

這個問題算是 Edit distance 類型的題目,但你會發現解 Edit distance 的演算法會需要先傳輸完整個 string 才能開始跑演算法,這就本末倒置了。

而另外一個想法是,放棄固定的 chunk 大小,改用其他方式決定 chunk 的邊界要切在哪裡。而 CDC 就是利用一段 sliding window + hash 來找出切割的點。

文章裡面提到的 sliding window 是 64 bytes,這邊就可以算出對應的 HASH(b0, b1, ..., b63),然後往右滑動變成 HASH(b1, b2, ..., b64),再來是 HASH(b2, b3, ..., b65),一直往右滑動計算。

接下來 restic 會看 hash 值,如果最低的 21 bits 都是 0 就切開,所以 chunk 大小的期望值應該是 2MB?(這邊不確定,好像不能直接用 2^21 算,應該用積分之類的方法...)

For each fingerprint, restic then tests if the lowest 21 bits are zero. If this is the case, restic found a new chunk boundary.

而這個演算法可以適應新增與刪除的操作,不會造成從新增或刪除後的資料都要重傳,只有自己這個 chunk 需要重傳 (可能前或後的 chunk 也會要)。

然後挑一下 hash function 的特性,就可以讓計算的速度也很快。這邊提到了 hash function 可以用 Rolling hash,可以很快的從 HASH(b0, b1, ..., b63) 算出 HASH(b1, b2, ..., b64),而不需要全部重算。

有了 chunk 後,再用 cryptographic hash function 比較 chunk 的內容是否一樣,這樣就可以大幅降低傳輸所需要的頻寬了。

GNU Make 在 4.4 引入的 --shuffle

Hacker News 首頁上看到的,作者送了一個提案到 GNU Make,後來被採用,在 4.4 版引入了 --shuffle 指令:「New make --shuffle mode」。

這個功能主要是想要找出在 Makefile 裡面沒有被定義好,平常是因為 side effect 而沒有出錯的地方。

像是作者就發現 libgfortran 沒有把 libquadmath 放到 dependency 的問題:

For example gcc’s libgfortran is missing a libquadmath build dependency. It is natural not to encounter it in real world as libquadmath is usually built along with other small runtimes way before g++ or gfortran is ready.

他的基本想法是把 target 的順序打亂掉,也就是在有指定 --shuffle 時,不一定會照 a -> b -> c 的順序往下遞迴,而可能會是 c -> b -> a 或是其他的順序:

all: a b c

這樣對於抓那些在 -j 平行編譯時會出包的套件也很有幫助,不需要在 -j 開很大的情況下才能重製問題,而是平常就有機會在 CI 環境下被抓出來。

YAML 常見的問題

Hacker News Daily 上看到「The yaml document from hell」這篇在抱怨 YAML 的問題,而 Hacker News 上對應的討論在「The Yaml document from hell (ruudvanasseldonk.com)」這邊。

翻了一下我之前也有提到好幾次不同來源的抱怨:「YAML 的地雷 (2014)」、「YAML 的痛點 (2019)」、「YAML 的問題 (挪威問題) (2021)」。

這篇提到的東西還是類似之前提到的,但整理的蠻不錯的?他給了一個看起來蠻正常的 YAML,然後裡面全部都是地雷,你可以看他的說明知道是什麼問題。

不過他提出來的問題都是可以加上 double quote 來避開,但把這個方式當作 common practice 用 YAML 會變得很痛苦。

不過市場上還沒有能取代的東西,只能先繼續邊用邊罵了,看了一下 Hacker News 上的留言,簡單一點的東西 (只是要放幾個值的) 大家都覺得 INI 還可以拿來用用...

KataGo 1.12.0 與 UEC 杯用的 model:b18c384nbt-uec.bin.gz

剛剛看到 KataGo 出了 1.12.0,同時也放出了在 2022 年十一月 UEC 比賽時用的 model:「New Neural Net Architecture!」。

1.12.0 比較特別的新的類神經網路架構:

This version of KataGo adds support for a new and improved neural net architecture!

這個新的架構以及其他的改善讓訓練的速度改善:

The new neural nets use a new nested residual bottleneck structure, along with other major improvements in training. They train faster than KataGo's old nets and learn more effectively.

另外一個是他把 UEC 比賽時用的 model 放出來了,很特別的是採用 b18c384,而 KataGo Distributed Training 這邊目前主要是 b40c256 與 b60c320,看起來是為了比賽而一次性訓練出來的。

依照他的說法這個 b18c384 版本跟目前訓練網站上的 b60c320 有差不多強度,但計算速度會比 b60c320 快不少,甚至在一些機器上會跟 b40c256 差不多快:

Attached to this release is a one-off net b18c384nbt-uec.bin.gz that was trained for a tournament in 2022, which should be of similar strength to the 60-block nets on http://katagotraining.org/, but on many machines will run much faster, on some machines between 40-block and 60-block speed, but on some machines even as fast as or faster than 40-block.

另外一個大改變是他把訓練工具從 TensowFlow 跳槽到 PyTorch

The training code has been all rewritten to use pytorch instead of tensorflow.

在 release note 裡沒有提到原因,但這個頗讓人好奇的...

fp32、fp16 與 bf16

nanoGPT 這個專案下面看到 bf16 這個浮點格式:(這段在新版的 README.md 被改寫了)

Currently fp16 is much faster than bf16. Potentially revert back to using fp16 and re-introduce the gradient scaler?

翻了一下 bf16,是由 Google 的團隊所提出來的想法,主打是與 fp32 相同的表示範圍,但是犧牲精度,這樣一來既有使用 fp32 的程式很有機會可以直接換成 bf16 降低計算量 & 提昇效率 (也就是 drop-in replacement):

然後同一頁也看到各家自己推出不同的格式,這邊看起來比較像是市場競爭的關係...

Debian 移除 Python 2 套件

Hacker News 首頁上看到 Debian 移除 Python 2 的消息:「Python 2 removed from Debian (debian.org)」,對應的 ticket 在這邊:「#1027108 - RM: python2.7 -- RoQA; Obsolete - Debian Bug report logs」。

Python 2 從 2015 年喊 EoL 喊很久,終於在 2020/04/20 發行最後一版 Python 2.7.18

大多數的套件應該都有 Python 3 的支援了,有需要的人 (像是還是沒支援 Python 3 的 Trac) 可以透過 pyenv 建立 Python 2 的環境出來跑,或是丟進 Docker 裡面跑。

銀河的歷史又翻過了一頁...

Git 在 SHA-256 支援上遇到的問題

在 Hacker News 首頁上的「Whatever happened to SHA-256 support in Git?」這篇,大概半年前,今年六月的文章,在講 GitSHA-256 支援上遇到的問題。

最大的問題還是沒有 transition plan:

Bjarmason pointed out that there is still no interoperability between SHA-1 and SHA-256 repositories, and that none of the Git hosting providers appear to be supporting SHA-256.

這點導致了目前場上看的到的服務與 open source 軟體都沒有支援的計畫,這點算是一個超級失敗的 transition plan。

另外乾脆請 OpenAIChatGPT 幫忙寫:

Git是一種源代碼管理系統,它使用散列函數將每個對象(例如文件的每個版本)和每個提交都轉換為一個數值,用於存儲該對象。散列函數的安全性是整個倉庫完整性的重要組成部分。如果攻擊者可以用具有相同散列值的另一個提交替換提交,他們可能會在不冒檢測風險的情況下將惡意代碼植入倉庫中。這種前景令任何依賴 Git 倉庫中代碼安全性的人感到擔心,也就是所有人。

Git 項目早已選擇 SHA-256 作為 SHA-1 的替代品。Git 最初是使用 SHA-1 寫成的,但是這些代碼已被重構,並且可以處理多種散列類型,其中 SHA-256 是第二種支持的類型。現在可以使用 SHA-256 創建 Git 倉庫(只需使用 --object-format=sha256 標誌),大多數本地操作都會正常工作。Git 中支持替代散列算法的基礎是2020年發布的2.29版本的一部分,並且似乎很穩定。

然而,自從2021年3月發布的2.31版本中出現了修復程序以來,2.29版本是最後一個在任何重大方面支持替代散列的版本。專

續寫前文,Git 專案在支持 SHA-256 之前一直使用 SHA-1 作為散列算法。然而,由於 SHA-1 的終極破解,Git 項目被迫尋找替代方案。Git 原本的代碼是深深地嵌入 SHA-1 的,但是這些代碼已經被重構,並且可以處理多種散列類型,其中 SHA-256 是第二種支持的類型。因此,現在可以使用 SHA-256 創建 Git 倉庫(只需使用 --object-format=sha256 標誌),大多數本地操作都會正常工作。

然而,目前尚不存在 SHA-1 和 SHA-256 倉庫之間的相互操作性,並且 Git 托管提供商似乎都不支持 SHA-256。這種支持(或缺乏支持)很重要,因為無法推送到 Git 熔爐的倉庫對許多人來說將是無用的。此外,使用 SHA-256 的較長散列可能會破壞 Git 項目以外開發的工具,這種風險也無法完全消除。整體而言,這是一種尚未準備好用於現實世界的功能。

雖然如此,值得注意的是,迄今為止完成大部分散列轉換工作的 brian m. carlson 對 Bjarmason 的評估持不同看法。在他看來,目前唯一“可以辯解

的”使用 SHA-1 的原因是與 Git 熔爐提供商的相互操作性。否則,他認為 SHA-1 已經過時,並且使用 SHA-256 的性能可能“大幅提升”。但是,他同意所需的相互操作性目前尚不存在,且沒有人表示它即將在短期內到來。

因此,目前看來,Git 專案在支持 SHA-256 方面的工作似乎已經停止。至於什麼原因導致了這種情況,目前還不清楚。但是,這並不意味著 Git 將永遠不會支持 SHA-256 或其他散列算法。相反,可能是因為尚未解決所有技術問題,或者可能是因為其他原因(例如技術資源有限)。無論原因是什麼,重要的是要記住,Git 項目的開發人員一直在努力使其更強大、更安全,並且將繼續努力。

Mac 上用 Homebrew 安裝 Java 的方式

寫個自己看的,順便整理一些簡單的歷史。

一開始可以先看一下 java 跑起來如何:

$ java --version
The operation couldn't be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.

順便一提,這邊的 java 可以用 which java 看到是出自 /usr/bin/java

然後透過 Homebrew,可以選擇不同的 JDK 套件安裝,在網路上常見的答案是 temurin (adoptopenjdk 的後繼者):

brew install temurin

這個好處是裝完可以直接用:

$ java --version
openjdk 19.0.1 2022-10-18
OpenJDK Runtime Environment Temurin-19.0.1+10 (build 19.0.1+10)
OpenJDK 64-Bit Server VM Temurin-19.0.1+10 (build 19.0.1+10, mixed mode)

另外一種是 OpenJDK,裝完後還得補個 symbolic link:

brew install openjdk
sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk

可以看到這個版本的輸出不太一樣:

$ java --version
openjdk 19.0.1 2022-10-18
OpenJDK Runtime Environment Homebrew (build 19.0.1)
OpenJDK 64-Bit Server VM Homebrew (build 19.0.1, mixed mode, sharing)

然後不管哪種裝法都記得設定 JAVA_HOME

export JAVA_HOME="$(/usr/libexec/java_home)"

基本上就能動了。

Ruby 3.2.0 把 YJIT 列為穩定功能了

去年有寫過 RubyYJIT 帶來的效能提昇:「YJIT 帶給 Ruby 大量的效能提昇」。

在這次的 Ruby 3.2.0 發布就把 YJIT 列為穩定功能了:「Ruby 3.2.0」。

  • YJIT is no longer experimental
    • Has been tested on production workloads for over a year and proven to be quite stable.

另外就是支援的平台,看起來是多了 arm64 這邊的支援,所以馬上列表就多了一堆新機器:

  • YJIT now supports both x86-64 and arm64/aarch64 CPUs on Linux, MacOS, BSD and other UNIX platforms.
    • This release brings support for Apple M1/M2, AWS Graviton, Raspberry Pi 4 and more.

另外是每個程式語言幾乎都會遇到的 regexp 類的問題,這次 Ruby 3.2.0 利用 Memoization 的方式降低某些 regexp 的消耗:

# This match takes 10 sec. in Ruby 3.1, and 0.003 sec. in Ruby 3.2
/^a*b?a*$/ =~ "a" * 50000 + "x"

而另外一組 regexp 也可以看出類似的效果:

用一些記憶體空間換取效能,降低被 DoS 的一些機會。另外一方面,引入了 regexp timeout 的 workaround,緩解真的被打的時候的資源消耗上限:

The optimization above cannot be applied to some kind of regular expressions, such as those including advanced features (e.g., back-references or look-around), or with a huge fixed number of repetitions. As a fallback measure, a timeout feature for Regexp matches is also introduced.