Goにおける画像ストリーム処理について発表しました - JPEG streaming in Go #pixiv_night
ご無沙汰しています、最近リードエンジニアという称号がつきましたはるかさんです。2017/03/14に開催されたpixiv nighit #2でGoにおける画像ストリーム処理の話をしました。そのうちpixiv inside に詳細がのるとおもうので、一旦スライドだけ公開しておきます。
pixivの画像変換はほとんどGoで実装されていますが、実際はcgoの呼び出し箇所が多く、時間的には8割くらいCを書いていることになります。実際cgoの呼び出し箇所では面倒なことも多く、最終的にはすべてがGoの世界になるとうれしいんですが、全ライブラリ書き換えないといけないのでなかなか難しいところです。
ImageFluxはこのあたりのところを頑張ることで、うまいことやっていこうとしているので、ご興味あれば是非声をかけていただければと思います。
nginxのtcp_nodelayディレクティブは設定しなくても良い
「nginx実践入門」お買い上げ頂きありがとうございます。レビュー等読ませて頂いていると、気になるところがあったので補足です。
- 作者: 久保達彦,道井俊介
- 出版社/メーカー: 技術評論社
- 発売日: 2016/01/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
tcp_nodelayは設定しなくても良い
tcp_nodelay
ディレクティブはTCP_NODELAY
オプションを付与するための設定です。これはデフォルトで有効ですし、競合しそうなTCP_NOPUSH
やsendfile
ともうまいこと使えるように実装されています。そのため明示的にoff
にすることは殆どないでしょう。このディレクティブがデフォルトで有効になったのはnginx 0.3.61の事なので、これより古いバージョンを使っている環境はあまり想像できません。
tcp_nodelayは何をするのか
さて本題はもう終わったのですが、tcp_nodelay
ディレクティブは何を設定するディレクティブなのか簡単に説明しておきます。tcp_nodelay
ディレクティブが有効であるとき、ソケットにTCP_NODELAY
オプションが設定されます。TCP_NODELAY
オプションはNagleアルゴリズムを無効にするためのオプションです。
NagleアルゴリズムはRFC896の著者であるJohn Nagle氏の名前から名付けられたアルゴリズムであり、このRFC中で説明されている"small-packet problem"を回避するためのアルゴリズムです。Nagleアルゴリズムでは、小さなパケットの送信を防ぐため、データを一旦バッファリングして大きなパケットにしてから送信しようとします。そのため、小さなパケットを送信しようとしたとき、バッファリングがタイムアウトするまで(調べてないけど200ms?)遅延が発生してしまうことになります。TCP_NODELAY
を有効にすると、このバッファリングを無効にすることで、遅延を最小化することができます。
前述したように、nginxではキープアライブ接続の場合デフォルトでTCP_NODELAY
を設定します。これにより、パケット到着までの遅延を短くすることを実現しています。特にTLSのハンドシェイクなどではかなり効いてくるはずです。
tcp_nopushディレクティブと組み合わせるとどうなるのか
さて、本書ではtcp_nopush
ディレクティブを有効にすることを推奨しています(詳しくは3.4節をごらん下さい)。このオプションはパケットをまとめることで送信するパケット数を最小化するためのオプションです。つまりTCP_NODELAY
と逆の動きをするように見えます。ではtcp_nopush
ディレクティブとtcp_nodelay
ディレクティブの両方を有効にしても大丈夫なのでしょうか?
nginxではTCP_NOPUSH
が有効になるのはsendfile
を利用する場合のみです。ソースコードを見ると、LinuxではTCP_CORK
を有効にするまえにTCP_NODELAY
が有効であれば無効にするように動作します*1。そのため、両方のディレクティブが有効のままでも問題はありません。
設定しないといけないディレクティブは意外と少ない
nginxには数百ものディレクティブがあり、書籍中で取り上げたのはそのうちの一部です。静的ファイル配信に使うのであれば、設定する必要があるディレクティブはそれほど多くありません。自分の好みとしては設定する必要がないディレクティブは少ない方が嬉しいです。調べる必要もなくなるし、余計な手間を減らすことができます。ということで書籍中でもそのようなディレクティブは説明自体を省いています*2。
設定しなければないディレクティブが少ないのはnginxの良いところの1つです。tcp_nodelay
ディレクティブはデフォルトで有効なので、明示的に記述する必要はないでしょう。sendfile
とtcp_nopush
ディレクティブは歴史的な事情と環境依存の問題によってデフォルトoff
にされていることが推測できるのですが、通常の使用においては有効にして問題になることは少ないでしょう。ということで書籍で紹介しているようにsendfile
、tcp_nopush
は明示的に有効にすることをおすすめします。
詳しくはnginx Tech Talksで
このあたりの話は2月の勉強会でやろうと思ってるので、是非参加いただければ幸いです。
書籍「nginx実践入門」のサンプルコードを公開しています
サポートページなどには掲載して頂いているのですが、nginx実践入門のサンプルコードをGithubで公開しています。
本書は紙面の都合上、サンプルコードの説明よりも文章やディレクティブの挙動に重きをおいて説明しているところが多々あり、そのためリストも内容が大きく省略されていたりします。公開しているサンプルコードでは本文中の不足を補うため、基本的にはそのまま動作するコードにすることを心がけて作成しています。実際にコードをそのまま確認したい方はサンプルコードを利用して頂くと手間が省けるかもしれません。
また、本書の設定ファイルが動作するnginxをビルドするためのスクリプトも付属しています。DebianとUbuntuのみの対応となりますが(Travis CIでCIをまわしている関係でデフォルトはUbuntuです)、ビルドする際の参考になるはずです。
本書中ではビルド手順について、特定のディストリビューションに依る書き方を避けています。自分はDebianの場合、以下のようなスクリプトを使い回しています。このスクリプトだと設定ファイルを/etc/nginx/nginx.conf
においたり、/var/log/nginx
にログファイルを出力したりします。参考になるかもしれません。
#!/bin/bash -ex set -o pipefail nginx_version="1.9.5" openssl_version="1.0.2e" pcre_version="8.37" zlib_version="1.2.8" sudo apt-get update sudo apt-get install libgd-dev mkdir src pwd cd src test -d "nginx-${nginx_version}" || wget http://nginx.org/download/nginx-${nginx_version}.tar.gz && tar xvf nginx-${nginx_version}.tar.gz test -d "pcre-${pcre_version}" || wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-${pcre_version}.tar.gz && tar xvf pcre-${pcre_version}.tar.gz test -d "openssl-${openssl_version}" || wget https://www.openssl.org/source/openssl-${openssl_version}.tar.gz && tar xvf openssl-${openssl_version}.tar.gz test -d "zlib-${zlib_version}" || wget http://zlib.net/zlib-${zlib_version}.tar.gz && tar xvf zlib-${zlib_version}.tar.gz cd nginx-${nginx_version} mkdir -p /var/lib/nginx ./configure \ --with-openssl=../openssl-${openssl_version} \ --with-zlib=../zlib-${zlib_version} \ --with-pcre=../pcre-${pcre_version} \ --sbin-path=/usr/sbin/nginx \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/lock/nginx.lock \ --http-log-path=/var/log/nginx/access.log \ --http-client-body-temp-path=/var/lib/nginx/client_body_temp \ --http-proxy-temp-path=/var/lib/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/lib/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/lib/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/lib/nginx/scgi_temp \ --with-debug \ --with-pcre-jit \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_image_filter_module \ --with-http_dav_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_stub_status_module make sudo make install
なお、サンプルコードの内容については2/8(月)開催のnginx Tech Talksでも触れたいと思っています。
nginx実践入門を執筆しました!それから出版記念イベントをやります
1/16(土)発売です。長い間書いていたのでようやくでた、というかんじですが、「nginx実践入門」を元同僚の@cubicdaiyaさんと2人で執筆しました。Amazonにて予約受付中です。電子版(PDF)もあります。
- 作者: 久保達彦,道井俊介
- 出版社/メーカー: 技術評論社
- 発売日: 2016/01/16
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
この本はそもそもWeb+DB Press Vol.72に@cubicdaiyaさん、@semindさんと3名で執筆した「詳解nginx」がベースになっていますが、大幅に加筆した結果、殆どすべて書き直したかんじになりました。 nginx自体もアップデートしており、HTTP/2に対応したnginx 1.9.5を基準に紹介しているので、それほど古い内容にはなっていないのかなーと思います。
自分が担当した部分ですと、
- 第3章 基本設定
- 第4章 静的なWebサイトの構築
- 第5章 安全かつ高速なHTTPSサーバの構築
- 第6章 Webアプリケーションサーバの構築
- 第7章 大規模コンテンツ配信サーバの構築
- 第8章 Webサーバの運用とメトリクスモニタリング
となっておりまして、一般的なHTTPサーバとして使用する場合の基本的な機能は抑えられているかな、と思います。とくに追加した部分ですと、HTTPSサーバとしての設定など、これからのWebでは当たり前となる事柄が含まれています。小規模なサービスから大規模サービスまで幅広く使える内容になっているかと思います。
これだけ長い間書いたにも関わらず、紙面から漏れてしまった便利なディレクティブもたくさんあって書き足したい気持ちでいっぱいですが、ひとまず本という形でお届けできるのは嬉しいです。是非手にとって頂けると幸いです。
「nginx実践入門」出版記念!執筆者らが語る nginx Tech Talks
ということで、2/8(月) 19:00から渋谷dots.さまで出版記念イベントを開催します。書面に書ききれなかった多くを補えればいいかなーと思っているので、本書を予約していただいた方もそうでないかたも是非とも参加をお待ちしております。
GoのSliceは実際どうなっているのか
この記事はGo Advent Calendarのエントリではありません。
Go言語を勉強する人であれば、誰でもがsliceの扱いに躓くであろう。sliceの扱いがちょっとよくわからなかったので、まだよくわかってないけど、ひとまずメモ。
Goのアセンブラに関するドキュメント
sliceをアセンブリから扱うには
結論から書くと、今のところの理解では、sliceを関数に渡すとき、フレームにはslice構造体のアドレス、len
、cap
、そして戻り値のアドレスが積まれている。つまり、次の様なコードを書けば、sliceのアドレスがSI
、len
がAX、cap
がBXに入り、要素の最初の値を返す関数になる。
// func FirstElem(x []uint64) uint64 TEXT ·FirstElem(SB),NOSPLIT,$0 MOVQ $x_base+0(FP), SI MOVQ $x_len+8(FP), AX MOVQ $x_cap+16(FP), BX MOVQ 0(SI), SI MOVQ 0(SI), CX MOVQ CX, ret+24(FP) RET
len
とcap
が何故とれるのかよくわかってないけど、これでだいたいのことはできる。以下本題。
sliceはどうなっているのか
sliceの実装はsrc/runtime/slice.goにある。ということで、こんなブログを読まなくてもコードを読めば良い。
type slice struct { array unsafe.Pointer len int cap int }
array
には要素の配列のポインタ、len
には使用している長さ、cap
は確保済みの長さが入っている。このあたりはA Tour of Goをやっていれば理解できているだろうしブログにも書いてある。
図にすると次の様なかんじだ。
ということで、先ほど示したアセンブリのように、引数をデリファレンスして、もう1回デリファレンスすれば要素の最初の値がとれた。これだけ理解しておけば、アセンブリからsliceを扱うことは比較的容易だ。
growするときにどうしているのか
ここまででだいたい実装はわかったけれど、良く理解できていなかったのでsliceの長さを拡張するときの実装を見てみる。実装はruntime.growsliceにある。エラー処理を省略してコメントを追加しておいた。
// growslice handles slice growth during append. // It is passed the slice type, the old slice, and the desired new minimum capacity, // and it returns a new slice with at least that capacity, with the old data // copied into it. func growslice(t *slicetype, old slice, cap int) slice { et := t.elem // 新しいcap(newcap)を計算する newcap := old.cap if newcap+newcap < cap { newcap = cap } else { for { if old.len < 1024 { newcap += newcap } else { newcap += newcap / 4 } if newcap >= cap { break } } } lenmem := uintptr(old.len) * uintptr(et.size) capmem := roundupsize(uintptr(newcap) * uintptr(et.size)) newcap = int(capmem / uintptr(et.size)) // メモリを確保して内容をコピーする var p unsafe.Pointer p = rawmem(capmem) memmove(p, old.array, lenmem) memclr(add(p, lenmem), capmem-lenmem) return slice{p, old.len, newcap} }
まずはnewcap
の計算から見てみよう。newcap
は拡張後の新しいcap
の値になる。newcap
の計算処理は次の様なロジックだ。
- 現在の
cap
の2倍の値が求められているcap
よりも小さければ、求められているcap
をnewcap
にする len
が1024より短い場合は現在のcap
を2倍にしていき、求められているcap
よりも大きくなったらその値をnewcap
にするlen
が1024よりも長ければ1/4ずつ増やしていく
つまり、1024より短い時は今のcap
の2倍以上、1024よりも長ければ1.25倍以上にするようになっている。これが良く聞くGoのsliceは2倍ずつ増えていく、の実装である。
拡張後のサイズが計算できたら後はその領域を確保し、値をコピーするだけである。
lenmem
とcapmem
はlen
とcap
のメモリ上で実際に必要な長さを求めている。これを求めるには単純に要素型のサイズをかければ良い。
領域の確保はruntime.rawmem
が使われている。この関数の実装はつぎのようになっている。
// rawmem returns a chunk of pointerless memory. It is // not zeroed. func rawmem(size uintptr) unsafe.Pointer { return mallocgc(size, nil, flagNoScan|flagNoZero) }
mallocgc
を呼ぶだけであるが、ひとまずはsize
分mallocしてそのアドレスを返す、と理解しておけば今のところ問題ない。あとは確保したメモリに値をコピーし、すればおしまいである。memmove
、memclr
の実装はアセンブリで書かれているが、ひとまず次の様に理解しておけば良いはずだ。
// frmからtoにlength分移動させる func memmove(to *any, frm *any, length uintptr) // ptrからlength分クリアする func memclr(ptr *byte, length uintptr)
これでcapmem
のサイズだけメモリをコピーし、lenmem
の長さだけ値をコピーして、残りの領域をmemclr
でクリアする、という流れがわかった。おしまい。
まとめ
最近のGoはGoで記述されているので非常に読みやすく、内部構造を理解しやすい。ということでGoで躓いたときはまずGoのコードを読めばよい。ところで、関数呼び出しの際にフレームにbase, len, capで積まれているように見えるんだけど、なぜこうなるのか未だに理解できていない。関数の引数がどういう風にフレームに積まれるのか、どこ読んだらわかるんだろ。
License
// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.
本記事はgolang/goで公開されているGoのソースコードを含んでおり、LICENSEファイルは以下のURLで提供されている。
Gulpでplayのアセットファイルを生成できるプラグインをつくった
最近仕事でplay frameworkを使っているのだけど、playにはアセットファイルをコンパイルしてくれるsbt-webというのがある。これは便利っぽいのだけど、いままでnpmの世界で育った人間にはちょっと困った。だいたい、gulp-なんとか
ってプラグインではなくsbt-なんとか
ってプラグインを探したり、試したり、つくったりしないといけないのはつらい。
playはpublicディレクトリの下にアセットファイルをおけば良いのだが、gzipしたファイルとかmd5ファイルとかを用意しておけば、それを良い感じに使ってくれる。このあたりは下の記事が詳しい。
gulpでのやり方も書いてあったんだけど、面倒なのでプラグインにしておいた。
Gulpfile.jsにはこんなかんじでかけばよい。
var gulp = require('gulp'); var playAssets = require('gulp-play-assets'); var gzip = require('gulp-gzip'); gulp.task('build', function() { gulp.src('assets/**') .pipe(playAssets()) .pipe(gulp.dest('./public')); // output unzipped files .pipe(gzip({ append: true })) .pipe(gulp.dest('./public')); // output zipped files
これでちゃんとgzipしたファイルもできて、playもそれを使ってくれる。あとはsbt起動したときにgulpも起動するようにしておけば便利だと思う(やったことないけど)。
はじめてのGulpプラグインなので皆さんのコントリビュートをお待ちしております。
Web+DB PRESS Vol.88にHHVMの運用について書きました
弊社執筆陣で連載しているPHP大規模開発入門も、もう第9回目となります。今週末8/22(土)に発売されるWeb+DB PRESS Vol.88ではHHVMを取り上げました。HHVMが出てだいぶ経つ気がしますが、PHP 7前のここのタイミングしかもう盛り上がる余地ないかなー、と思ってこのタイミングです。連載が続いてネタがきれてきたとかそういうことではありません。さて、内容ですが基本的にはHHVMの簡単な検証方法とpixivでの運用がメインになります。いまさらHHVMかーと思ってる方も多そうですが、PHP 7以降も継続的に開発されそうなので、HHVMやHackに興味がある方はお手にとってもらっても良い内容かと思います。
- 作者: 佐々木拓郎,高柳怜士,鶴原翔夢,小野侑一,中村俊介,佐藤春旗,長野雅広,佐々木健一,久保達彦,若山史郎,佐藤太一,伊藤直也,道井俊介,佐藤歩,泉水翔吾,坪内佑樹,海野弘成,西尾泰和,中島聡,はまちや2,WEB+DB PRESS編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2015/08/22
- メディア: 大型本
- この商品を含むブログを見る