2050年再エネ100%はどれくらい難しいか(1) - 日本の消費電力量について

今年に入ってから環境問題、主に再エネについて少しずつ本を読んだりして調べています。

10月以降、菅政権が2050年カーボンニュートラルを打ち出してから、一気にこの分野の話題が過熱してきました。河野太郎大臣などが再エネの最大限導入のためにあらゆる障害を取り除く努力をされていて、リップサービスではなく本気で動き始めたことを喜んでいます。

一方で、経済産業省が2050年のエネルギー基本計画のたたき台になる目安として、2050年の電力のうち再生エネルギーが50~60%という数字が出てきて、若干失望もしました。今後菅政権の圧力で上方修正されることを期待しています。

www.jiji.com

とはいえ、「60%」「80%」「いーや、100%だっ」って、大きい数字を言った人がえらい訳ではありません。パーセントの数字だけを見ていても実感が湧かなくなってしまうので、2050年再エネ100%がどれくらい大変なことなのか、もうちょっと具体的にイメージできる形にしていきたいと思います。

今回は日本の電力需要を確認してみます。2019年度の電力調査統計を見てみると、総需要(電力会社以外が発電した分を含む)は約1222TWh、電力会社の発電量が約8600TWhあります。一方発電の内訳は、水力が84TWh、新エネ(風力、太陽光など)が42TWh (4.9%) しかありません。

この統計には太陽光の自家消費分などが入っていないので、実際にはもう少し多いと思われます。ISEPの試算では太陽光が7.6%となっています。

www.isep.or.jp

ざっくり年間で1200TWhくらいの電力を現在は使っている訳だけれども、2050年カーボンニュートラルを実現するためには、全ての化石燃料の需要(車のEV化はもちろん、産業では製鉄やセメントなど、家庭では暖房と給湯など)を可能な限り電気に、不可能な分は水素や水素から合成した燃料にしないといけません。その水素や合成燃料も化石燃料ではなくて再エネ由来にしなければなりません。(CCSは大きな部分は占めない。これはまた機会があれば。)

化石燃料の電化や再エネ由来燃料への置き換えに必要な電力量は、2050年までの省エネ努力や人口減で減る電力量よりも大幅に大きいと見込まれます。上の経済産業省の目安でも1300~1500TWhと計算されていました。この程度の増加で済んでいるのは、国内の水素や燃料需要のほとんどを輸入に頼る想定だからだと思います。

参考までに、1GWの原発や大型火力発電所の設備利用率が80%だった場合の年間発電量は、 24 * 365 * 0.8 ≒ 7008 なので約7TWhです。よく言う「原発n基分」で表現すると、1300 / 7 ≒ 186 なので「原発186基分」の電力をカーボンフリーで発電する必要があるのです。

次回はこの電力を100%国内の再エネで発電する場合の規模感を計算してみたいと思います。

go-sql-driver/mysql のバッファサイズが4KiBなのは小さすぎる??

qiita.com

go-sql-driver/mysql のバッファサイズは4KiBで、大きいクエリを送るなり大きいパケットを受信すれば自動的に増えますが、小さいrowを大量に受信する場合は4KiBのバッファを使って何度もReadをしてしまいます。これを大きくすればReadの回数が減って高速化できそうな気がします。

ということで、話題のリポジトリをforkして、DBからselectしてCSVを書き出す部分だけを100回ループしてみました。CSVの書き出しがネックにならないようにbufio.NewWriterSize()を使ってバッファリングしてあります。

profiling · methane/bench-docker@c333bd0 · GitHub

テスト環境は手元の MacBook Pro 13 (Retina, 13-inch, Mid 2014) で、あまり厳密なベンチではありません。

まずはバッファサイズ4KiBの場合のプロファイル結果。

f:id:methane:20201217170259p:plain
flamegraph (バッファサイズ 4KiB)

rows.Next が 3.01s で、呼び出し元の work は 3.33s です。

次にバッファサイズ16KiBの場合のプロファイル結果。

f:id:methane:20201217170529p:plain
flamegraph (バッファサイズ 16KiB)

rows.Next が 2.83s に減りましたが、期待したほどは減ってません。そして呼び出し元の work は 3.36s で、微妙に増えてしまいました。CSVを書き出すための fmt.Fprintf や Time.Format() が若干遅くなってしまってrows.Next の高速化分を打ち消してしまっています。

システムコールのオーバーヘッドの大きさにも依存しますが、むやみにバッファサイズを大きくしてもキャッシュに与える悪影響でシステムコールの回数を減らす効果が打ち消されるのかもしれません。

@kazuho さんさすがです。

Matebook 14 (2020)

amzn.to

税込み11万円弱で、AMD Ryzen 7 4800H、RAM 16GB、SSD 512GB、sRGB 100%の3:2液晶はすごいコスパ

Windowsはサブだけどある程度の開発生産性は欲しいという場面にいいと思って購入した。バタフライキーボードになる前のMBP 13 (2015)と比べて、

  • 日本語キー配列はいたって普通。キーボードのストローク、タイプ感も十分で、キートップを触ると若干の凹みも感じられて、十分にタイプしやすい。
  • 液晶解像度はMBPには劣るが、横解像度もFullHDよりも若干高めで、個人的には十分。
  • 低反射コーティングの質はMBPより悪い気がする。輝度を少し高めて使ってるけど、人によってはノングレアのフィルムを貼ったほうが良いかもしれない。
  • 重さは同じはず (1.49kg)
  • バッテリー持ちは(比較対象が古すぎるけど)圧倒的にいい。電源モードを「高パフォーマンス」(最高の1つ下)にして、一日リビングで充電なしで作業できる。
  • 液晶を開けただけではスタンバイから復帰せず、指紋センサー兼電源ボタンを押すことがある。でも復帰後にロック解除のために改めて指紋センサーを押す必要はないので使い勝手は十分にいい。
  • 液晶を開けるときに最初の1センチはいいんだけど、そこから開く力が重くなって下側が浮いてしまうので反対の手で下側を支えないといけないところに、細かいけれどもツメの甘さを感じる。
  • USB Type-C ポートが1つしかなくて、しかも充電がここからしかできないのがネック。

amzn.to

関数アノテーションを軽量化しました

この記事は KLab 2020 Advent Calendar の 12/2 分になります。 qiita.com

最近の Python に対する改善を紹介します。私が設計、コードレビューまでしましたが、実装は他のコントリビューターにしていただきました。 (プルリクエストはこちら)

背景として、Python 3.10 からは from __future__ import annotations がデフォルト化され、アノテーション部分は実行時に評価されずにただの文字列になります。( PEP 563 を参照してください。)

>>> def add(a: int, b: int) -> int:
...     return a+b
...
>>> add.__annotations__
{'a': 'int', 'b': 'int', 'return': 'int'}

アノテーションが実行時に評価されないということは、コンパイル時にアノテーションがすべて計算可能ということです。そこで Python 3.10 からさらなる最適化を導入しました。

まずは Python 3.9 で関数アノテーションがどう実装されていたかをおさらいします。

# a.py
from __future__ import annotations

def add(a: int, b: int) -> int:
    return a + b
$ python3 -m dis a.py
(snip)
  3          12 LOAD_CONST               2 ('int')
             14 LOAD_CONST               2 ('int')
             16 LOAD_CONST               2 ('int')
             18 LOAD_CONST               3 (('a', 'b', 'return'))
             20 BUILD_CONST_KEY_MAP      3
             22 LOAD_CONST               4 (<code object add at 0x7f7c9efac870, file "a.py", line 3>)
             24 LOAD_CONST               5 ('add')
             26 MAKE_FUNCTION            4 (annotations)
             28 STORE_NAME               2 (add)

4つのLOAD_CONST命令と1つのBUILD_CONST_KEY_MAP命令でdictを作って、それをMAKE_FUNCTIONに渡しています。この dict は function オブジェクトに格納されます。

これは関数の定義時に実行される命令なのでプログラムの実行速度への影響は軽微ですが、ループの中で inner function を定義している場合には毎回実行されますし、そうでなくても pyc ファイルを import する時間に多少影響を与える可能性があります。

これが Python 3.10 ではこうなります。

  3          12 LOAD_CONST               9 (('a', 'int', 'b', 'int', 'return', 'int'))
             14 LOAD_CONST               6 (<code object add at 0x7f9e932a7be0, file "a.py", line 3>)
             16 LOAD_CONST               7 ('add')
             18 MAKE_FUNCTION            4 (annotations)
             20 STORE_NAME               2 (add)

アノテーションが1つのタプルにまとめられ、このタプルが dict に変換されずそのまま MAKE_FUNCTION に渡され、関数オブジェクトに保存されるようになりました。これでアノテーション付きの関数を作る速度が数倍速くなります。 https://bugs.python.org/issue42202#msg381320 からマイクロベンチマーク結果を1つだけピックアップします。

def f(a: int, /, b: int, *, c: int, **d: int) -> None: pass

Python 3.9.0
5000000 loops, best of 5: 326 nsec per loop

Python 3.10.0a2+ (from __future__ import annotations がデフォルト化された状態)
5000000 loops, best of 5: 264 nsec per loop

Python 3.10.0a2+ with compact representation
5000000 loops, best of 5: 87.1 nsec per loop

さらにメモリ使用量にも影響があります。このタプルは func.__annotations__ が最初にアクセスされた時に dict に変換されるのですが、静的型チェックやコード補完機能、ドキュメントの目的で付けられた関数アノテーションは実行時には利用されないことが多いので、アクセスされないままならずっとタプルのままです。これでメモリ使用量は4割以下に減らせます。

>>> sys.getsizeof({"a":"int","b":"int","return":"int"})
232
>>> sys.getsizeof(("a","int","b","int","return","int"))
88

おまけに、同じソースファイルの中にある LOAD_CONST で読まれるタプルは、中身が同じであれば1つのインスタンスを使いまわします。 (Python 3.7までは1つのcodeオブジェクト(1つの関数など)内でしか使い回さなかったのですが、これも私が改良していました。 https://github.com/python/cpython/commit/c2e1607a51d7a17f143b5a34e8cff7c6fc58a091)

コードジェネレータに生成されたファイルなどで数千の同じシグネチャの関数がある場合は、今までは関数の数だけ dict オブジェクトを作っていたのが、 Python 3.10 ではたった1つのタプルで済むようになります。これでより気軽に関数アノテーションを使えるようになります。

将来の野望としては、関数アノテーションだけでなく docstring も、実際に利用されてからロードする遅延ロードが実装できたら良いなと思っています。しかしこれには pyc ファイルのフォーマットを完全に刷新する必要があり、しかもモジュールをロードしたあとに pyc ファイルが上書きされたらどうするのかという問題に対する解決策をまだ思いついていないので、 近い将来に実現するのは難しいです。いいアイデアを思いついたら教えて下さい。

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あるいはアプリでのクエリキャッシュを使うとか)を先に検討しようと思います。

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

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