GoのChannelをコピーしてコピー元のChannelをクローズするとコピー先のChannelもcloseされる

Goのプログラムを書いてたらはまってたのでメモしておく。

package main

func main() {
    ch1 := make(chan struct{})
    ch2 := ch1
    close(ch1)
    close(ch2)
}

Result

panic: close of closed channel

goroutine 1 [running]:
main.main()
    /tmp/sandbox237471277/prog.go:7 +0x80

Program exited: status 2.

Playgroundのリンク play.golang.org

proxygen の QUICのサンプルを試してみた。

facebookのHTTP Library proxygen がQUICとHTTP/3に対応しているらしいので動かしてみました。

github.com

Note that currently this project has been tested on Ubuntu 18.04 and Mac OSX although it likely works on many other platforms.

ubuntu16.04 でやると、ビルドスクリプト内部でapt-get してくるライブラリのバージョンに問題があるのでやめたほうがいいです。

ビルド方法

cd proxygen
./build.sh --with-quic

サーバ側サンプル実行方法

_build/proxygen/httpserver/hq --qlogger_path /tmp/logs/

クライアント側サンプル実行方法

_build/proxygen/httpserver/hq --mode=client --path=/

/tmp/logs を見ると、qlogが出力されてました。

{
  "description": "Converted from file",
  "qlog_version": "draft-00",
  "summary": {
    "max_duration": 8190,
    "max_outgoing_loss_rate": "",
    "total_event_count": 35,
    "trace_count": 1
  },
  "title": "mvfst qlog",
  "traces": [
    {
      "common_fields": {
        "dcid": "0aa4cd34408216f7",
        "protocol_type": "QUIC_HTTP3",
        "reference_time": "0",
        "scid": "4000009602439820"
      },
      "configuration": {
        "time_offset": 0,
        "time_units": "us"
      },
      "description": "Generated qlog from connection",
      "event_fields": [
        "relative_time",
        "CATEGORY",
        "EVENT_TYPE",
        "TRIGGER",
        "DATA"
      ],
      "events": [
        [
          "152",
          "TRANSPORT",
          "PACKET_RECEIVED",
          "DEFAULT",
          {
            "frames": [
              {
                "frame_type": "CRYPTO_FRAME",
                "len": 334,
                "offset": 0
              },
              {
                "frame_type": "PADDING",
                "num_frames": 854
              }
            ],
            "header": {
              "packet_number": 0,
              "packet_size": 1235
            },
            "packet_type": "INITIAL"
          }
        ],
 以下略

wiresharkでパケット見る方法はわからなかったです。

Bolina: a new end-to-end protocol specially designed for wireless communication

下の記事で、BoliaというUDPベースのプロトコルとQUIC、TCPの比較が行われていました。

blog.codavel.com

Boliaでは、QUIC(やTCPなど)で用いているのACKの仕組みをnetwork codingで代替しているようです。

無線などを使ったときに発生するランダムなロスが輻輳状態だと判断されてしまうことがあるのが課題で、それを解決するためにnetwork codingを使っているらしいです。

network codingについて

別な記事でシーケンスの説明がありました。

blog.codavel.com

下の図では、P1、P2、P3、P4というパケットの代わりに、P1+P2、P2+P3、P3+P4、P2+P4、P1+P3+P4 という(おそらく)XORしたパケットを送っています。この場合はP3+P4のパケットが落ちてしまっても届いたパケットを使えばすべてのパケットを復号できるらしいです。

f:id:neko--suki:20191022111024p:plain

これによって、次に送るデータを決めるときにACKへの依存が必要なくなる点と、パケットリカバリーが自由にできるようになる点にメリットがあるようです。

また、個別のパケットが落ちたかどうかによるロスの検出ではなく、どのくらいのデータが通ったかによるロスの検出が行えるようになるらしいです。具体的には、送信側のスループットと受信側のグッドプットが想定の範囲内なら通常起こりうる転送エラーの範囲だと判断します。もし、差が大きいなら輻輳が発生したと判断するようです。

性能の比較

QUICの実装は、https://github.com/ngtcp2/ngtcp2https://github.com/private-octopus/picoquichttps://github.com/facebookincubator/mvfsthttps://github.com/facebook/proxygenを使用したようです。TCPの実装は、boost.asioと書かれているのでhttps://boostjp.github.io/tips/network/tcp.html っぽいです。

比較のために25回の連続したGETリクエストを行っています。固定されたリクエストのサイズ、遅延、パケットロスを設定してスループットを計測しています。

まずは、リクエストサイズが250KBytesで、ロス率がそれぞれ0.1%と0.2%の時の比較です。遅延5msの時のスループットは、QUIC > Bolia > TCPとなっています。遅延を大きくすると、Boliaのスループットが一番大きくなります。

f:id:neko--suki:20191022105552p:plain
ロス率0.1 リクエストサイズ250K bytes

f:id:neko--suki:20191022105620p:plain
ロス率0.2%、リクエストサイズ250KBytes

リクエストサイズ17MBytesの場合は、ロス率0.2% 遅延5msの場合を除いて、Boliaのスループットが一番高くなっています。

f:id:neko--suki:20191022105711p:plain
ロス率0.1%、リクエストサイズ17MBytes

f:id:neko--suki:20191022105729p:plain
ロス率0.2%、リクエストサイズ17MBytes

著者の考察によると、リクエストサイズが小さい場合はslow startフェーズから抜けるまえに処理が終わってしまう、リクエストサイズが大きい時は輻輳制御とロス検出・リカバリーの仕組みによる差が出ている、とのことです。

あと、下の一文が読んでいて気になりました。

All QUIC implementations had 0-RTT enabled (the first request for each latency/packet loss point was 1-RTT and the remaining 0-RTT)

ある遅延・パケットロスの設定に対して最初のリクエストは1-RTTでそれ以降は0-RTTと書かれている。なので、コネクションを張りなおしているように読めました。

参考リンク

www.codavel.com

blog.codavel.com

blog.codavel.com

www.codavel.com

https://www.codavel.com/wp-content/uploads/2019/04/Draft_ArchitectureOverview.pdf

WebAssembly: Web Workers を読んだ

https://dzone.com/articles/webassembly-web-workers

WebAssemblyのコードを、fetch→ArrayBuffer→コンパイル→postMessage という風に使う。

ブラウザには、UI用のスレッド ('window' オブジェクト) とは別に、Web Workderというのを使ってスレッドを立てることができる。

これによって、UIの処理を止めることなく別な処理を走らせることが可能になる。(Javascriptは非同期処理ではない。)

WebAssemblyのコードは、UI側、WebWorkerのどちらでも実行ができる。

そのやり取りに、postMessage()というメソッドを使う。

New Kid on the Web: A Study on the Prevalence of WebAssembly in the Wild を読んだ

https://www.sec.cs.tu-bs.de/pubs/2019a-dimva.pdf

WebAssemblyの普及状況を調べた論文

alexa top 1milioon の947,704 sitesに対して、topページも使われていない可能性を加味してランダムで3つリンクを踏んだ分のあわせて、3,465,320 pagesを訪問した その中で、1639サイトで1950のwasmファイルが使われていた。ユニークなファイル数で数えると、150ファイル存在していた。 同じものが、346のサイトで使われていた (おおむね1/600に近い値にはなる)

そのうち、55%のウェブサイトがマイニングに使われていた。javascriptのライブラリの一部としての提供が38.8%、Testが14.9% と続いている。

Optimizing HTTP/2 prioritization with BBR and tcp_notsent_lowat

https://blog.cloudflare.com/http-2-prioritization-with-nginx/

 

BBRとtcp_notsetn_Iowatを使って、HTTP/2の優先度制御を最適化する話。

 

サーバ側のバッファサイズの課題

優先度が低いリソースのリクエストの後に、優先度が高いリソースのリクエストが来ることがある。バッファサイズが大きいと、優先度が低いリソースがリクエスト順にすべてカーネルに渡された後に、優先度が高いリソースが渡される。その結果、優先度が高いリソースが届くのが遅くなる。それを、tcp_notsent_lowatを設定することで調整する

 

inflightのバッファサイズ

従来のTCP輻輳制御の方法だと、ロスが起きてからウィンドウサイズを減らしている。それに対してBBRはそういうウィンドウサイズの減らし方はしない。その結果優先度が低いリソースがたくさんネットワーク上にいる状態を避けて、優先度が高いリソースのリクエストが来たらそれを先に送信できるようにする。

 

 

ウェブページのロードはなぜ早くないといけないのか

qiitaように書いてたけど論文の内容には含まれないので切り出しました。

 

昔から、ウェブページのロード時間が長くなると利益が減るため、ウェブページのロードは速いほうが良いと考えられています。ソース元はリンク切れしていますが、10年ほど前にAmazonは100msecページのロード時間が遅くなると利益が1%減るという情報を公開していました。また、[Amazon Found Every 100ms of Latency Cost them 1% in Sales](https://www.gigaspaces.com/blog/amazon-found-every-100ms-of-latency-cost-them-1-in-sales/)という記事では、GoogleAkamaiなどの事例も公開されています。他にも探すと多くの事例があるはずです。