ISUCON10予選敗退してきました

例年は同僚と参加していたのですが、今年は予選申し込みが始まってから去年のメンバーに打診したら申し込み締め切りに間に合わなかったために、申し込みに成功してメンバーを募集されていた @catatsuy さんにお願いしてメンバーに入れてもらいました。 @catatusy さんの記事はこちら

結果は1503点で、惜しくもなんともない完敗でした。

序盤

事前に打ち合わせしていた通りに、お互いで共通の ssh_config を作る、除外すべき大きいファイルやディレクトリをチェックしつつ初期コードをコミットする(今回は適切に .gitignore がされていて git add . だけでいけました)、ローカルの開発環境を構築しその手順を共有する、netdata をインストールし netdata cloud に登録する、アクセスログ集計のための alp コマンドのオプションを共有するといったことをしていました。

小さいトラブルはあったものの、ここまではスムーズで良かったと思います。

中盤〜終盤

alp の結果とアクセスログを見比べて、estateとchairの検索が遅いこと、検索クエリのほとんどがシンプル(検索条件が1つ)であることなどを把握しました。

検索は LIMIT X OFFSET Y 型式のページングと、 COUNT(*) 型式の総数表示があります。前者はインデックスでソートすれば早めに打ち切れるので降順インデックスが使える MySQL8 にすることで十分な高速化が見込めますが、 COUNT(*) の方は工夫しないとインデックス全体のスキャンになります。MySQL側で工夫するよりもアプリ側で処理する方が早いと判断してオンメモリ化を始めます。。。。が、ここからドツボにはまります。

最初に簡単な estate の検索から始めたのですが、 CSV入稿に失敗したというエラーや /api/estate/search の結果が違うというエラーに悩まされます。安定してエラーになるのならまだ良いのですが、パスしたりエラーになったりするし、エラーの内容を教えてくれないのもあって迷走しました。

午後7時ごろ、迷子になった出前の配達員を迎えに走っていたときに、元は ORDER BY popularity DESC, id ASC だったのにアプリでは popularity の降順ソートしかしていないことに思い当たります。しかしそれを修正してからもエラーになったりならなかったりは続きます。

最終的に、オンメモリのデータの件数だけなら利用してもエラーにならないと判断し、 SELECT COUNT(*) を削除することにしましたが、スコアはあまり上がりませんでした。

反省点と感想

最近のISUCONだとベンチマーカーが間違っている点を詳しく教えてくれる事が多かったためにベンチマーカーをテストに使うという戦略に依存してしまい、そのためエラーの原因が最後までわからずに迷走してしまいました。せっかくローカルで動作する環境があったので自分でしっかりテストをするべきでした。

ただ、その反省を消化しようとローカルでオンメモリ版とDBを使う版でいくつかの条件でAPIを叩いてみてもAPIが出力するJSONが完全一致したので、いまだにエラーの原因がわかりません。後日ベンチマーカーが公開されたら改めてデバッグしようと思います。

またオンメモリ化も定型的な作業で自分なら楽勝だと信じきってしまっていたのもドツボにハマった原因です。次の機会ではよりシンプルでバグの少ないその他の選択肢(MySQLあるいはアプリでのクエリキャッシュを使うとか)を先に検討しようと思います。

最後になりますが、今年の問題はボリュームがとても小さいのにやらなければならないことは十分に多い良問でした。自分の実力と勉強不足を痛感させられました。ありがとうございました。

PyPIのTOP 4000 パッケージのsdistをダウンロードする

Deprecate済みの機能をそろそろ削除していいかどうか悩んだときに、Githubソースコード検索だとソースコードのコピーが置いてある個人リポジトリがたくさんあったりして役に立ちません。

PyPIのミラーを作って全部のパッケージを確認できればいいのですが面倒なので、とりあえずTOP4000パッケージだけを対象にしてソースパッケージのダウンロードして調査しています。

まずTOP4000パッケージのリストですが、 Top PyPI Packages というサイトから入手できます。BigQueryで自分でリストを作るよりもお手軽です。

hugovk.github.io

つぎにパッケージをダウンロードする方法ですが、 pip download だと --no-deps つけてもビルド依存関係を解決しようとしたりしてうまくいかなかったので、PyPIJSON APIからsdistを見つけてダウンロードするようにしています。

notes/download_sdist.py at master · methane/notes · GitHub

Python/C API を調べたかったのでこのスクリプトは sdist しかダウンロードしていませんが、 Universal wheel しか配布していないパッケージもあるので、Python標準ライブラリの利用状況を調べたい場合は sdist が存在しない場合に wheel をダウンロードするように改修する必要があります。

sdist の拡張子は .tar.gz, .tar.bz2, .zip なので、あとは単にシェルの機能を使って展開するだけです。

他にもいい方法ないかなと思ったら、 Sourcegraph が結構使えそうでした。これは PyEval_ReleaseLock というDeprecateされている関数の利用例をSourcegraphで探す例です。

PyEval_ReleaseLock file:… - Sourcegraph

PEP 8騒動について

今週PEP 8の小さい変更についてMLで騒動が起こってしまいました。

該当のコミットはこれです。

PEP 8: Change requirement to adhere to Standard English (#1470) · python/peps@0c6427d · GitHub

変更点はごくごくシンプルなものです。

- When writing English, follow Strunk and White.
+ Ensure that your comments are clear and easily understandable to other
+ speakers of the language you are writing in.

今まで知らなかったのですが、変更前の "Strunk and White" とは The Elements of Style というすごく有名なライティングに関する本らしいです。

www.amazon.co.jp

この変更は "Strunk and White" を知らない人にとって分かりやすくなるようにする変更で、これだけなら論争が起こることはなかったのですが、コミットログでこの変更がBLMの動きに関連づけられてしまったために論争が起こってしまいました。

Instead of requiring that comments be written in Strunk & White Standard English, require instead that English-language comments be clear and easily understandable by other English speakers.

This accomplishes the same goal without upholding relics of white supremacy. Many native English speakers do not use Standard English as their native dialect, so requiring conformation to Standard English centers whiteness in an inappropriate and unnecessary way, and can alienate and put up barriers for people of color and those whose native dialect of English is not Standard English.

This change is a simple way to correct that while maintaining the original intent of the requirement.

最初の段落はいいのですが、次の段落の冒頭の "This accomplishes the same goal without upholding relics of white supremacy." でまずギョっとします。

White=善/Black=悪というバイアスを強化する用語は、たとえ肌の色を意味していなくても排除しようという運動があり、最近は blacklist/whitelist に対する word policingが大きな話題になりました。そんな背景があったので、 "Struck & White Standard English" の White はもちろん著者の人名なんですが、まさか人名のWhiteすらword policing対象になるの!?と反射的に思ってしまう人がいました。

もちろん人名が問題になっているわけではありません。 Strunk & White Standard English は白人優位な社会で生まれた白人の標準語で、その利用を推奨することは黒人英語(AAVE)などの方言が標準英語ではないんだ、白人英語の方が標準(=優位)なんだというバイアスを助長するのが問題だとされています。とはいえ、コミットコメントでは Strunk & White がなぜ白人優位なのかの説明が抜けているので、まさか名前が問題なの?という誤解の原因になっています。

もう一つ論争になってしまっているのは標準語の利用を推奨するかどうかで、結局 "clear and easily understandable to other speakers of the language you are writing in." を達成するには標準的な一つの方言を使うしかないんですよね。あちこちの英語の方言が混ざると読みにくく、わかりにくくなります。それに英語ネイティブじゃない人に英語の読み書きを強制させることは英語=Standard、英語社会優位のバイアスを強化してしまってることには変わりありません。


PEP 8は本来Pythonの標準ライブラリのためのコーディング規約なので、結局Python開発に参加するときにアメリカの白人英語を使うことに変わりはありません。とはいえPEP 8はPython標準ライブラリに限らず広く使われているので、白人英語に限定した表現をせずに "clear and easily understandable to other speakers of the language you are writing in." という書き方にする変更自体は良いと思います。

とはいえ、このコミッターとGuidoだけで、他のコミッターに説明なくPEP 8が更新されたために、「Whiteっていう人名すらダメなの?」とか「方言使うことを推奨するの?黒人英語だけじゃなくて○○方言は?そもそも英語じゃない言語は?」っていう不要な誤解に基づく論争が生まれてしまいました。

今は git のヒストリを書き換えて、この誤解を招くコミットログをもっと誤解を生まない表現に修正するかどうかが話し合われています。

Python 3.10 の開発(お掃除)に参加しよう

訂正

昔から deprecate されているのにずっと生き残ってるヤツたちはクセモノのぞろいで、全然初心者向けではありませんでした。

代わりに、Docディレクトリを deprecated-removed::grep して、 3.10 で削除する予定になっているものを削除する方がずっと楽なので、そちらに挑戦してみてください。削除する手順は下の記事のままで大丈夫です。


Python 3.9 がベータに入り、masterブランチはPython 3.10の開発に入りました。

はっきりとした区分はないものの、Python 2.7との互換性のために長くdeprecated状態を維持していたメソッドの削除に踏み切るバージョンになりそうです。そこでこんなIssueを作ってみました。

Issue 41165: [Python 3.10] Remove APIs deprecated since Python 3.3 - Python tracker

新しいAPIを提案するよりもずっとハードルが低いはずなので、他の削除プルリクエストを参考にしてお掃除に参加してみませんか?

DeprecatedなAPIを削除する手順は次の通りです。

  1. (初めてプルリクエストを作る場合) CLA にサインする。
  2. ドキュメントでdeprecatedになったタイミングと、DeprecationWarningを出すようになったタイミングが十分(後述)に古いか確認する。
  3. 該当のAPIを削除する。該当のAPIからしか使っていなかったprivateメソッド等も同時に削除すること。
  4. テストも削除する
  5. NEWSエントリとwhat's newエントリを書く
  6. プルリクエストを送る

Pythonの最低deprecation期間は2バージョンですが、消すのを先延ばしにするデメリットが大きくない場合や古くから存在するAPIについては、それよりも長めのdeprecation期間があったほうがいいです。

既に Issue のコメントではPython 3.3時点でdeprecateされたものをリストアップしていますが、もう少し新しいdeprecationを探す場合も3.6までにdeprecationされてるものを選んだ方が「まだ消さないで」と言われるリスクは低いと思います。

PEP 623: Remove wstr from Unicode について

今週新しいPEPを作りました。 www.python.org

背景

Python 3.3からUnicodeの内部表現が変わり、文字列に含まれる最大のコードポイントから1byte(ASCII or latin1), 2byte (UCS2), 4byte (UCS4)を選ぶようになっています。 (PEP 393 Flexible Unicode Representation)

それまでのPython 2 やPython 3.2までは、Unicodeの内部表現はUTF-16UTF-32コンパイル時に決定されていました。(narrow build, wide build と呼ばれていました) この時に一文字を表すC言語の型を Py_UNICODE として、UTF-16なら16bit、UTF-32なら32bitの符号なし整数型が使われていました。

昔の内部表現を Py_UNICODE* 型で取得するAPI (PyUnicode_AsUnicodeなど) や、文字列を作成するときに先に Py_UNICODE の長さを指定してアロケートするAPI (PyUnicode_FromUnicode(NULL, length)) などを動かすために、今の実装は typedef wchar_t Py_UNICODE した上でUniocodeオブジェクトの内部に wchar_t *wstr というメンバーを持っています。

これにより、小さいASCII文字列でも64bit環境では wstr のために8バイトを消費しています。非ASCII文字列ではwchar_t (UTF-16UTF-32)にエンコードしたときの長さのために ssize_t wstr_length というメンバーもあり、合計で16バイトを消費しています。

言うまでもなく文字列(Unicode)オブジェクトはPythonで最も大量にインスタンスが生成される型の一つなので、ほとんどの文字列がASCIIだとしても1インスタンスあたり8バイトの消費はそろそろ削りたいです。

削除に向けて

残念ながらいくつかのAPIがドキュメントではDeprecatedと書かれているものの、CコンパイラーのWarningを出すようにはなっていなかったので、すぐには消せません。 実際のところ、特にWindows関連のライブラリで引数を wchar_t* で受け取るのが便利だったので、標準ライブラリの内部でもまだまだ使われてしまっていました。

コンパイラーのWarningを抑止するマクロを併用することで、これらの内部利用されているけれど消したいAPIにWarningをつけ、これは Python 3.9 にバックポートしました。 ただ、引数を解析する関数 (PyArg_ParseTupleなど) が wchar_t* を使ってる部分は関数自体をWarning対象にはできないので、実行時にPythonのDeprecationWarningを出す必要があり、これは現在ベータになっているPython 3.9にはバックポートできません。 wstr を利用している全てのAPIや動作のDeprecationができるのが3.10になります。

PEP 623では、通常のDeprecationプロセスの最短である Python 3.12でwstrを削除することを提案しています。

影響度

このプランが現実的かどうか調べるために、PyPIのダウンロード数トップ4000のパッケージから、ソースパッケージ (.tar.gzなど) を提供している物を全てダウンロードし、これらのAPIの利用状況を見てみました。

一番多かったのがCythonが生成したコードが空文字列を作るために PyUnicode_FromUnicode(NULL, 0) を使っているもので、これはもう直してもらいました。また同じくCythonがもっとレアな条件で PyUnicode_FromUnicode を使っているのも見つけ、これも報告してあります。次のCython (0.29.21) では直っていると思うので、あとはいろいろなプロジェクトが新しいCythonを使って生成したコードをリリースするのを待てばほとんどが解消されるはずです。

他に多かったのが、(Cythonが生成したコードを含めて)Python 2に対応するために #ifdef で区切ったコードで、Python 3では使われないものです。

Cython生成コード意外で問題になるコードを含むプロジェクトは多分20前後で、ほとんどは簡単なものだったので既にプルリクエストを出したり報告して修正してもらったりしています。なのでこの変更はそれほど大きい breaking change にはならないと考えています。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。