ばいなり、こんこん、こんぱいる!をC87で出します
タイトルはきつねさんシリーズではないですが、相変わらずLLVMネタで同人誌を出します。 タイトルは「ばいなり、こんこん、こんぱいる!」です。 今回は何かをもじってネタにしようということでこの名前になりました。自分が考えた「ggr!きつねさん」は没になりました。
前回はClangネタでしたが、今回からまたLLVMネタに戻りました。 今まではテーマを決めて二人がそれに沿ってネタを作るというスタイルでやっていましたが、 今回は特にテーマを統一せずに好きなことをやるという感じになっています。 自分は3章担当で、LLVMのJIT(OldJIT)とMCJITの違いについてソースコードを追いかけながら簡単に解説するという内容になっています。 4章は餅さん(@sui_moti)が担当で、LLVMのGCについて解説しながら実際に実装を行っています。 表紙や挿絵(!)は矢上さん(@re_sonanz, web)にお願いしています。
今回はなかなかやる気がでず筆が乗らず、ページ数が今までよりも少なくなりましたが内容としては十分面白いのではないかなと思ってたりします。
前回200部刷って少しだけあまったのと、今回あまり書けなかったのもあって70部になります。
値段が前回とあまり変わってないのはすみません!!
以下、頒布場所の詳細になります。部数制限等は特にありません。
- コミックマーケット87
- 場所:東京ビッグサイト
- サークル名:MotiPizza
- 日程:3日目 12月30日(火)
- スペース番号:西く-16a
- 値段: 1部 700円
- ページ数: 54ページ
- 部数制限: 無し
- 合計部数: 70部
以下、章構成。
Fluentdのin_tailプラグインで複数行のログをよむ方法
Fluentd v0.10.45からin_tailプラグインで複数行のログを読み込めるようになりました。 これは今まで外部プラグイン(fluent-plugin-tail-multiline)として提供されていた機能をマージしたものになります。v0.10.50で複数行ログの読み込みに関するバグ修正が入っているので、この機能を使う場合はv0.10.50以上を使いましょう。
基本的な使い方
基本的にはドキュメントに書かれている通りですが、in_tailのformat
オプションにmultiline
を指定することで複数行モードになります。実際のパースのためのルールはformatN(Nは1から20)という名前のオプションで記述します。
以下は公式ドキュメントから引用したサンプルでRailsのログのパースする設定となります。
format multiline format_firstline /^Started/ format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/ format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/ format3 /( Parameters: (?<parameters>[^ ]+)\n)?/ format4 / Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/ format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/
複数行モードの設定にはいくつか注意しないといけないことがあります。
- formatNは必ず1から初めて連続していなければいけません。
- 各formatには必ず1つは名前付きキャプチャが含まれてなければいけません。
- 同じ数値で複数回書いた場合は後に書かれたルールが有効になり、前のものは単純に無視されます(はまりポイントその1)。
- 正規表現のマッチにはRegexpのRegexp::MULTILINEが使われるため、.(ドット)は改行にもマッチするようになります(はまりポイントその2)。
- 正規表現に渡される文字列(ログ)は最後の改行が削除されているので完全にマッチさせる場合には最後に改行を含んではいけません(はまりポイントその3)。
サンプルの2行目にあるformat_firstline
オプションは正規表現のマッチを始める行を指定するもので、指定しないこともできます。このオプションについてはもう少し詳しく説明します。
format_firstlineの使い分け
format_firstline
を指定した場合、この正規表現にマッチした行からログをバッファに貯めはじめ、次にformat_firstline
にマッチしたときにそれまでのバッファをformatN
でパースしてメッセージを生成します。
逆に指定しなかった場合は、常に各行をバッファに貯めはじめ、formatN
に完全にマッチした段階でメッセージを生成します。
どちらもメッセージを生成した段階でバッファをクリアします。
自分はこの仕様を見た時にformat_firstline
は必要ないんじゃないかと思っていたのですが、よく考えると必要な場合もありました。
典型的にはパースしたいログの終端が予測できない場合にformat_firstline
が必要です。
例えば、メッセージの先頭にタイムプスタンプをつけてログに残すけどメッセージに改行が含まれることを許す場合です。
Jun 28 19:45:00 message1 foo! Jun 28 19:45:05 message2 foo! bar! baz! Jun 28 19:45:10 message3 foo!
上記のようなログの場合は各メッセージの終端が予測できないため、 先頭にタイムスタンプが来るまで というルールになります。
この場合にはformat_firstline
に^[A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2}
とでもいれておけば良いでしょう。
逆にこうなるとformat_firstline
オプションは必須でこれだけで良いでは?という気持ちになりますが、実はそうではなくメリット・デメリットがあります。
format_firstlineのメリット・デメリット
format_firstline
オプションを使った場合は、その仕様上 次のメッセージが来るまで今のメッセージの終端がわからない ということになります。
つまり、メッセージがログに出力されてもすぐにそのログがFluentdのメッセージとして送信されるわけではなく、次のログが出てくるまで遅延することになります。ちなみに、次のログが来る前にFluentdを落としたら「今バッファに溜まっているメッセージはロストしないのか」というのが気になりますが、終了前にformatN
の判定が行われるためロストすることはありません。
一方でformat_firstline
を使わない場合はログの終端が予測できるため、formatNにマッチした段階でメッセージを送信が可能になり、遅延することはありません。逆にこちらのデメリットとしては、 間違った正規表現(formatN)を書いても気づけない という問題があります。formatNにマッチするまで「まだログが不完全」と判断されるため、永遠にバッファにたまり続けます。
また、どちらの場合も(複数行として出されるものの)各行が一度にログに出力されない場合は、途中までしかFluentdのin_tailが読み込んでいない可能性があり、終了時のパースで失敗していまうのでそのログはロストします。これは結構注意しなければいけないことで、複数行ログの場合は一度にログを出力できなければロストする可能性があることを念頭に置きましょう。
まとめ
Fluentdのin_tailプラグインで複数行ログの読む場合にはformat_firstline
を使う方法と使わない方法があり、それぞれにメリット・デメリットがあります。
どちらのケースが適しているかはよく考えて選びましょう。ちなみに個人的にはformat_firstline
を使わない方法をよく使っています。
記事に間違いなどがあれば随時修正します。
Ruby FFIを使ったエクステンションの作り方
Ruby FFIについて
ここ最近libclangのRuby Bindingのffi-clangに機能を追加していたのですが、その過程でFFI(Foreign function interface)というものを知りました。FFIとはようするに多言語を呼び出すためのインターフェイスのことのようで、RubyにおけるFFIはlibffiを利用しているようです。
Rubyでは(C言語で)エクステンションを書けば共有ライブラリを利用するようなライブラリ(Gem)を作ることができますが、FFIを使えばRubyで書くことができますしポータブルな実装になります。本家曰く:
- コンパイルが必要無い
- マルチプラットフォーム(変更無しでJRubyやRubiniousなどで動作する)
- 記述がRubyなので読み書きしやすい
- Rubyの実装の変更に影響されない
実際FFIを使ってみると、単純なものなら一瞬でバインディングを作ることができるので非常に便利でした。慣れるまでは「このパターンだとどう記述すれば良いのだろう」というので悩んでしまいますが慣れ次第です。日本語で解説しているブログなどはほとんど見つからないですが、使い方は公式のドキュメントが割とよくできているので英語さえ読めればなんとかなります。このエントリでは公式チュートリアルにないようなちょっとだけ複雑なケースを含む、簡単なFFIの書き方を紹介したいと思います。
FFI導入部分
Ruby FFIはffiを使うのでまず最初にインストールしておきます。基本的にはバインディングを割り当てるモジュールの中でFFI::Library
を有効にしてffi_lib
で共有ライブラリを読み込むことです。例としては以下のような感じになります。
require 'ffi' module FooLib extend FFI::Library ffi_lib "libfoo.so" ## ここに実装を書く end
この後はattach_function
を使って共有ライブラリの各関数を割り当てていくことになります。また、必要に応じて構造体やenumの定義を行います。
サンプル
まず簡単な例として以下のfoo.c
とfoo.h
からlibfoo
を作成して、そのバインディングを作成する場合を紹介します。
// foo.c static const char *foobar = "FOOBAR"; double func_a(int a, float b, unsigned long c, char *name) { printf("libfoo: func_a: %d %f %lx %s\n", a, b, c, name); return 0.1; } struct Foo* create_foo() { struct Foo *foo = (struct Foo *)malloc(sizeof(struct Foo)); foo->name = (char *)foobar; foo->ptr = (int *)malloc(sizeof(int)); *foo->ptr = 100; printf("libfoo: create_foo: %p\n", foo); return foo; } void use_foo(struct Foo *foo, int a) { printf("libfoo: use_foo: %p %d\n", foo, a); return; } void free_foo(struct Foo *foo) { printf("libfoo: free_foo: %p\n", foo); if (foo != NULL) { free(foo->ptr); free(foo); } return; }
// foo.h struct Foo { char *name; int *ptr; }; double func_a(int a, float b, unsigned long c, char *name); struct Foo* create_foo(); void use_foo(struct Foo *foo, int a); void free_foo(struct Foo *foo);
func_a
はいろんな型の引数を受け取ってdoubleを返すシンプルな関数です。create_foo
とuse_foo
とfree_foo
はFoo構造体を操作するAPI群となります。
これをRuby FFIを使って割り当てたプログラムは以下のようなものになります。
module FooLib extend FFI::Library ffi_lib "libfoo.so" class Foo < FFI::Struct layout( :name, :string, :ptr, :pointer, ) end attach_function :func_a, [:int, :float, :ulong, :string], :double attach_function :create_foo, [], :pointer attach_function :use_foo, [:pointer, :int], :void attach_function :free_foo, [:pointer], :void end
attach_function
は共有ライブラリの関数をRubyのメソッドとして割り当てるメソッドです。第一引数に関数名、第二引数に関数の引数の型を配列で指定、第三引数で戻り値の型を指定します。
構造体を定義する場合はFFI::Struct
を継承してFFI::Struct#layout
でメンバーを定義します。メンバーの定義方法は他にもいくつかあるようですが、自分はいつもlayoutを使って定義しています。layoutは,名前、型の順でメンバーの個数分並べていきます。
以上で関数の割り当てができたので、このモジュールを使ってみます。プログラムは以下の通りです。
p FooLib.func_a(10, 1.0, 1 << 60, "foobar") puts foo_ptr = FooLib.create_foo p foo_ptr.class p foo_ptr.address.to_s(16) puts foo = FooLib::Foo.new foo_ptr p foo.class p foo[:name].class p foo[:name] p foo[:ptr].class p foo[:ptr].read_int puts FooLib.use_foo(foo, 10); FooLib.free_foo(foo);
このプログラムを実行した結果は以下の通りです。
libfoo: func_a: 10 1.000000 1000000000000000 foobar 0.1 libfoo: create_foo: 0x7f7926dcf220 FFI::Pointer "7f7926dcf220" FooLib::Foo String "FOOBAR" FFI::Pointer 100 libfoo: use_foo: 0x7f7926dcf220 10 libfoo: free_foo: 0x7f7926dcf220
func_a
では引数と戻り値が正しい値になっていることがわかります。create_foo
では戻り値の型を:pointer
としたため、FFI::Pointer
クラスが返ってきています。FFI::Struct
はinitialize
でFFI::Pointer
を渡すことでインスタンスを作成できます。構造体の中身のデータを取得したい場合には一度変換する必要があります。use_foo
とfree_foo
の引数にはcreate_foo
の戻り値をそのまま渡すこともできますが、FooLib::Foo
を渡すこともできます。それぞれ渡されたポインタのアドレスが正しい値になっていることがわかります。
オブジェクトの自動的な解放
先ほどの例ではfree_foo
を明示的に呼び出す必要があるため、メモリリークする可能性がありますし、何よりRubyっぽくないです。FFIにはオブジェクトが解放(GC)されるときに自動的にメソッドを呼び出してくれる機能があります。方法はいくつかありますが、自分はManagedStruct
もしくはAutoPointer
を使った実装を使っています。
ManagedStructを使った解放
ManagedStruct
はStruct
にリリース機能を付けただけで基本的にStruct
と変わりません。上記のLibFooモジュールの中でStruct
の代わりにManagedStruct
を使って以下のように書き換えます。
class ManagedFoo < FFI::ManagedStruct layout( :name, :string, :ptr, :pointer, ) def self.release(ptr) puts "release 0x#{ptr.address.to_s(16)}" FooLib::free_foo(ptr) end end
self.release
はGC時に自動的に呼び出されるメソッドで、クラスメソッドなのが特徴です。これをcreate_foo
の戻り値からnewしてインスタンスを作成すると、そのインスタンスがGCされるときに自動的にfree_foo
が呼ばれるようになります。
スコープから外れてすぐGCされるわけではないので以下のようにループしておけばいずれGCされて確認できると思います。
loop do foo = FooLib::ManagedFoo.new FooLib.create_foo p foo.class FooLib.use_foo foo.pointer, 1000 end
AutoPointerを使った解放
AutoPointer
はPointer
にリリース機能を付けたもので、Struct
におけるManagedStruct
のようなものです。
AutoPointer
もself.release
でポインタが渡されるのでそれを使って解放するだけですが、いろいろとメソッドを生やして、オブジェクトを操作する専用クラスっぽくすることもできます。実際には同じことをManagedStructを使って行うこともできます。
class FooPointer < FFI::AutoPointer def self.release(ptr) puts "release 0x#{ptr.address.to_s(16)}" FooLib::free_foo(ptr) end def initialize ptr = FooLib.create_foo super ptr @foo = FooLib::Foo.new ptr end def use FooLib::use_foo @foo.pointer end def name @foo[:name] end def ptr @foo[:ptr] end end
FooPointer
はinitialize
の中でcreate_foo
を呼び出すようにしています。このインスタンスがGCされる時にはself.release
で自動的にfree_foo
が呼び出されます。またその他に構造体の中身へのアクセッサを生やしたりしてカプセル化したりもできます。
型の可読性をあげる
attach_function
の引数や戻り値の型で:pointer
とした場合に何のポインタかがわからないので、ここだけ見ても仕様がわからないという問題があります。
attach_function :create_foo, [], :pointer attach_function :use_foo, [:pointer, :int], :void attach_function :free_foo, [:pointer], :void
今回このポインタの型はstruct Foo
なのでそれに対応するFooLib::Foo
にひもづけることができます。
attach_function :create_foo, [], Foo.ptr attach_function :use_foo, [Foo.ptr, :int], :void attach_function :free_foo, [Foo.ptr], :void
:pointer
の部分をFoo.ptr
と置き換えることでFoo
のポインタとして受け渡しを行うという意味になります。
この状態でcreate_foo
を実行すると戻り値の型はFFI::Pointer
ではなく、最初からFooLib::Foo
で返ってことになります。
foo = FooLib.create_foo p foo.class # => FooLib::Foo
また、ポインタではなく値で受け渡しをしたい場合は#by_value
を使うことで値渡しを指定できます。
例えば以下のようにstruct Foo
を値で受け取るuse_foo_value
関数の場合は
void use_foo_value(struct Foo foo);
Foo.by_value
で値渡しを指定することができます。
attach_function :use_foo_value, [Foo.by_value, :int], :void
使用するときはFooLib::Foo
のインスタンスをそのまま引数に渡すだけで良いです。
foo = FooLib.create_foo FooLib.use_foo_value foo
ポインタ渡しと配列
関数の使用として引数でポインタを渡して関数内で値を詰めて返すというものや、配列とそのサイズを渡して関数無いで指定サイズ分詰めて返すもの、そして配列のポインタを返すものがあると思います。
Cのコードとしては以下のような3つのパターンです。
void foo_int(int *a); void foo_int_array(int *a, int num); void foo_allocate_array(int **a, int *num); void foo_int(int *a) { *a = 10; } void foo_int_array(int *a, int num) { for (int i = 0; i < num; i++) a[i] = 10; } void foo_allocate_array(int **a, int *num) { int n = 3; *a = (int *)malloc(sizeof(int) * n); for (int i = 0; i < n; i++) (*a)[i] = 10 + i; *num = n; printf("libfoo: foo_allocate_array: %p %d\n", *a, n); }
これらの割り当ては以下のようになります。ポインタのポインタも:pointer
として割り当てます。
module FooLib extend FFI::Library ffi_lib "libfoo.so" attach_function :foo_int, [:pointer], :void attach_function :foo_int_array, [:pointer, :int], :void attach_function :foo_allocate_array, [:pointer, :pointer], :void end
このモジュールを利用したプログラムは以下のようになります。
a1 = FFI::MemoryPointer.new :int # int *a = malloc(sizeof(int)) FooLib.foo_int(a1) # r1 = a1.read_int # ((int *)a)[0] p r1 # 10 a2 = FFI::MemoryPointer.new(:int, 5) # int *a = malloc(sizeof(int) * 5) FooLib.foo_int_array(a2, 5) # r2 = 5.times.map { |i| # a2.get_int(i * 4) # ((int *)a)[i] } # p r2 # => [10, 11, 12, 13, 14] arr_ptr = FFI::MemoryPointer.new :pointer # int **a = malloc(sizeof(int *)) num_ptr = FFI::MemoryPointer.new :int # int *num = malloc(sizeof(int)) FooLib.foo_allocate_array(arr_ptr, num_ptr) # => libfoo: foo_allocate_array: 0x7f0d2052bd60 3 arr = arr_ptr.read_pointer # int *a = *(int **)a num = num_ptr.read_int # int num = *(int *)num p arr.address.to_s(16) # => "7f0d2052bd60" p num # => 3 r3 = num.times.map { |i| # arr.get_int(i * 4) # ((int *)a)[i] } # p r3 # => [10, 11, 12]
FFI::MemoryPointer
はcaalloc
のようなもので、第一引数に型、第二引数に個数を指定します。割り当てたメモリはインスタンスがGCされる時に自動的に解放されます。
read_int
のようにread_hoge
はhogeでキャストしてからポインタを参照するといったような動作になります。
最初のパターンではread_int
するだけで値がとれます。
二個目のパターンではint型の配列を作ってから渡します。get_int
はread_int
と似ていますが、引数で指定したオフセットからポインタを参照することになります。
三個目のパターンではint配列のポインタとintのポインタを渡しますが、int配列のポインタの方はただのポインタのポインタとして渡します。arr_ptr.read_pointer
で1段ポインタを参照し、arr_get_int
で実際の値を取得します。
三個目のパターンでは共有ライブラリ側でメモリを割り当てているので、解放関数(例えばfoo_deallocate_array
)を使う必要があるケースがあります。自分はこの場合には、foo_allocate_array
の戻り値を管理するクラスをFFI::AutoPointer
で作成してrelease
でfoo_deacllocate_array
を呼び出すようにしています。このクラスでEnumerable
かArray
を継承すれば見た目上はArrayと同様の動作をして自動的に解放もしてくれるクラスになります。
class FooArray < FFI::AutoPointer class FooPointer < FFI::Pointer attr_reader :ptr attr_reader :num def initialize(ptr, num) super ptr @ptr = ptr @num = num end end include Enumerable def initialize(ptr, num) super FooPointer.new(ptr, num) @arr = ptr @num = num end def self.release(ptr) FooLib::foo_deallocate_array(ptr.ptr, ptr.num) end def each(&block) @num.times { |i| block.call(@arr.get_int(i * 4)) } end end arr_ptr = FFI::MemoryPointer.new :pointer # int **a num_ptr = FFI::MemoryPointer.new :int # int *num FooLib.foo_allocate_array(arr_ptr, num_ptr) arr = arr_ptr.read_pointer # int *a num = num_ptr.read_int # int num foo = FooArray.new(arr, num) # foo_allocate_arrayの戻り値を管理するクラス p foo.class # => FooArray p foo.map{|e| e + 100} # => [110, 111, 112] exit # => libfoo: foo_deallocate_array
最後に
Ruby FFIの紹介と実装する上での簡単なテクニック集的なものを紹介しました。実際にはまだEnum周りでいろいろとあるのですが長くなってきたのでここで止めます。FFIには更にGeneratorだとかConverterだとかが存在して、これらが何者なのかイマイチわかっていないですが、使いこなせるともっと簡単にかけるのかもしれません。
参考リンク
LLVM 3.4 リリースノートの訳
半年に一回がんばる日が来ました。例によってLLVMのリリースノートの訳です。 直訳がほとんどなので気になる人は原文を読みましょう。
12月31日時点でLLVMのダウンロードページやドキュメントページへのリンクがありませんが、直接それっぽいURLにアクセスすると既に存在するようです。ただしClangはまだ無いようです。
イントロダクション(Instroduction)
このドキュメントはLLVMコンパイラ・インフラストラクチャのリリース3.4に関するリリースノートです。前回のリリースからの大きな改善点, サブプロジェクトの改善点, コードの利用者などを含むLLVMの状況を記しています。LLVMリリースの全てはLLVMリリースサイト からダウンロードできます。
LLVMについての最新情報などの詳細はLLVMメインサイト を参照して下さい。疑問や感想などがありましたらLLVM開発者メーリングリスト に投稿して下さい。
SubversionチェックアウトもしくはメインLLVMウェブページからこのファイルを読んでいるなら、このドキュメントには現在のリリースではなく次のリリースが適用されることに注意して下さい。特定のリリースのリリースノートをみたい場合、リリースページ を見て下さい。
本リリースの包括的では無い変更点リスト(Non-comprehensive list of changes in this release)
- これがC++98ツールチェインを使用してコンパイルするLLVMの最後のリリースなると期待されています。私たちはこのリリース後からC++11の機能をLLVMと他のサブプロジェクトで使い始めることを期待しています。とはいえ、全てのプラットフォーム上のホストコンパイラとして現代的なC++ツールチェインの合理的なセットをサポートすることを約束します。これには最低でもWindowsのVisual Studio 2012とMacおよびLinuxでのClang 3.1とGCC 4.7.xを含んでいます。最終的なコンパイラセットとそれらがサポートするC++11機能は確定してはいない。しかし、LLVMの利用者が次のリリースでホストツールチェインの要求における大きな変化を追従してくれることを望んでいます。
- パイプライン上でのいかなるコマンドの失敗によっても回帰テストが失敗するようになります。あるディレクトリで無効にするには
config.pipefail = False
をlit.local.cfg
に追加して下さい。詳細はLitを参照して下さい。 - 古いJITから例外ハンドリングのサポートが削除されました。EH(例外ハンドリング)のサポートが必要であればMCJITを利用して下さい。
- R600バックエンドが実験的とはされなくなり、デフォルトでビルドされるようになりました。
APFloat::isNormal()
はAPFloat::isFiniteNonZero()
に、APFloat::isIEEENormal()
はAPFloat::isNormal()
にリネームされました。これはAPFloat::isNormal()
がIEEE-754R-2008に準拠することを保証します。- ライブラリ呼び出し簡易化パス(library call simplification pass)が削除されました。その機能は命令結合器(instruction combiner)と関数属性マークパス(function attribute marking pass)に統合されました。
- Visual Studio 2008を使用するビルドのサポートが廃止されました。VS2010かそれ以降を使用して下さい。詳細な情報はGetting Started using Visual Studioを参照して下さい。
- 以前
-O3
で有効になっていたループベクトル化が-Os
と-O2
で有効化されるようになりました。 - SLPベクトル化がデフォルトで有効化されるようになりました。
llvm-ar
が新しいオブジェクトライブラリを使用してGNUフォーマットでアーカイブとシンボルテーブルを生成するようになりました。- FileCheckが複数回の
-check-prefix
の指定を許容するようになりました。これにより複数のRUN行を使うときに重複チェックを減らせるようになります。 - bitcast命令が異なるアドレス空間を持つポインタ間のキャストをもはや許容しなくなりました。これを行う場合は、addrspacecast命令を使用して下さい。
- 異なるアドレス空間のための異なるサイズのポインタが多くの場合動作するようになりました。これは主にGPUターゲットで役に立ちます。
- OCamlバインディングがほとんど全てのLLVMライブラリをカバーするように拡張されました。
Mips Target
MIPS SIMDアーキテクチャ(MSA)のサポートが追加されました。MSAはインラインアセンブラ、__builtin_msa
のプリフィックスを持つをintrinsicsおよび通常のコード生成をサポートしています
(命令セットなどのドキュメントを含む)MSAに関する情報は、MIPS SIMD page at Imagination Technologiesを参照して下さい。
PowerPC Target
PowerPCバックエンドには以下の変更点を含みます:
- fast-iselサポート(より高速な
-O0
のコード生成) - 組み込みアセンブラの多くの改善
- アラインされていない(Altivec)ベクトル読み込みの生成のサポート
- fcpsgn命令の生成のサポート
round()
(nearbyint()
やrint()
)ではなく、fast-mathモード内でのみ行われる)のためのfrin
の生成- (A2のような)組み込みコアのための命令スケジューリングの改善
- (特に32bitモードにおける)prologue/epilogue生成の改善
- 動的なスタックアラインメント(と大きなアラインメントを持つ動的なスタック割り当て)のサポート
- counter-register-basedループの生成の改善
- バグ修正
SPARC Target
SPARCバックエンドは多くの改善があります。それは、
SystemZ/s390x Backend
LLVMとclangはzEnterprise z196とzEnterprise EC12ターゲットを最適化できるようになりました。clangではそれぞれ-march=z196
と-march=zEC12
を使用することでこれらのターゲットが選択されます。
LLVM3.4を利用した外部オープンソースプロジェクト(External Open Source Projects Using LLVM 3.4)
LLVMの興味深い点は多くの他の言語やツールプロジェクトのためのイネーブル技術として使われることです。このセクションではLLVM3.4で動作するよう更新されたいくつかのプロジェクトをリストアップします。
DXR
DXRはFixfoxのような大きなプロジェクトを理解するためのMozillaのコード検索とナビゲーションツールです。"この関数の全ての呼び出し元を探し出せ"といった構造化クエリと同様な全文検索と正規表現検索をサポートしています。浦ではtrigram indexやre2ライブラリそしてclangコンパイラプラグインによって収集された構造化データを使用しています。
LDC - the LLVM-based D compiler
D はCライクの構文と静的型付きを持つ言語です。これは効率、制御、そしてモデリング力を安全性とプログラマの生産性に実際的に結びつける。D言語はコンパイル時関数実行(CTFE)とテンプレートメタプログラミングのような強力なコンセプトをサポートし、そして並列性への革新的なアプローチと多くの古典的なパラダイムを提供する。
効率的なネイティブコードを生成するために、LLVMと結合したリファレンスコンパイラをバックエンドとして、[LDC](http://wiki.dlang.org/LDC] はそのフロントエンドを使用する。LDCはLinux、OS X、そしてWindowsのようなx86/x86_64システムとLinux/PPC64をターゲットとする。ARMやAArch64のような他のアーテキテクチャへのポートは進行中です。
LibBeauty
LibBeautyデコンパイラとリバースエンジニアリングツールは現在LLVM disassemblerとLLVM IR Builderを利用しています。このプロジェクトの現在の目標はx86_64バイナリ.o
ファイルを入力として受け取り、同様のLLVM IR.bc
もしくは.ll
ファイルを出力として生成することである。ARMバイナリ.o
ファイルの入力のサポートは後に追加されるでしょう。
Likely
Likelyは画像認識のためのオープンソースの領域特価型言語です。アルゴリズムはLLVMのMVJIT基盤を使用してjust-in-timeコンパイルされ、GPUが有効になったOpenCL SPIRやCUDAとシングルもしくはマルチスレッドCPU上で実行されます。Likelyは画像処理や統計的な学習カーネルが一般的に行列データ型を扱うように書く必要があり、実行時にはそれらが同じ型上で処理を繰り返す傾向があるという知見を活かしている。
Portable Computing Language (pocl)
容易にポータブルなオープンソースOpenCL実装を生成することに加えて、pocl のもう一つの主な目的はコンパイラの最適化を利用したOpenCLプログラムの性能ポータビリティを改善することとターゲット依存の手動最適化の必要性を減らすことです。poclの重要な部分は、ワークグループバリアの存在の中でさえカーネルコンパイラを用いて静的に多数のワークアイテムを並列化するLLVMパスのセットであることです。これによって複数の方法でワークグループの静的な細流度並列化が可能になる。
Portable Native Client (PNaCl)
Portable Native Client (PNaCl)はアプリケーションのポータビリティやセキュリティの恩恵を犠牲にすることなく、ネイティブコードの性能と低レベル制御を現代のウェブブラウザにもたらすためのChrome主導のプロジェクトです。PNaClはネイティブのCとC++コードをLLVM clangコンパイラを使用して中間表現にコンパイルすることで動作する。この中間表現はポータブルな実行形式へとラップされたLLVMバイトコードのサブセットである。これは他のウェブサイト資源と同様にウェブサーバ上でホストされることができる。サイトがアクセスされると、Chromeがそのポータブルな実行形式を取得して下層デバイスに最適化されたアーキテクチャ依存のマシンコードへの翻訳する。PNaClは開発者に彼らのコードをどんなハードウェアプラットフォームでも動作するコンパイルさせ、下層のCPUやGPUの力を活用しながらそのPNaClアプリケーションをウェブサイト上に組み込ませる。
TTA-based Co-design Environment (TCE)
TCE はTransport triggered architecture(TTA)基にした新しいプロセッサを設計するためのツールセットです。これはC/C++プログラムから合成可能なVHDL/Verilogそして並列プログラムバイナリまでの完全な協調設計フローを提供する。プロセッサの設定可能な項目としてはレジスタファイル、機能ユニット、サポート操作、そして相互接続ネットワークが含まれる。
TCEはC/C++/OpenCL Cの言語サポート、ターゲット非依存の最適化、そして一部のコード生成のためにClangとLLVMを利用する。これはコンパイラツールチェインの大部分をターゲット毎に再コンパイルすることを避けるために、デザインされたTTAプロセッサ向けの新しいLLVMベースのコード生成器を"オンザフライ"で生成し、それらを実行時ライブラリとしてコンパイラバックエンドにロードする。
WebCL Validator
WebCL ValidatorはOpenCL ES 1.1のサブセットであるWebCL C言語のための検証を実装している。バリデータはWebCL Cの正確さを確認し、source-2-source変換としてメモリ保護を実装する。その変換によりWebCLはメモリ保護されたOpenCLへと変換される。保護されたOpenCLは割り当てられていないメモリ範囲へのアクセスができず、他のプログラムへ情報がリークしないようにそのメモリは常に初期化される。
Additional Information
LLVMウェブページ 、特にドキュメントでは様々な追加情報が利用できます。ウェブページにはSubversionにあるソースコードにあわせて更新されているAPIドキュメントもあります。LLVMツリーの "llvm/docs" ディレクトリに行くと特定のリリースに対するドキュメントが取得可能です。
LLVMに関する質問やコメント等がありましたらメーリングリスト を通してお気軽にご連絡下さい。
きつねさんとおぼえる!Clang おかわりをC85で出します
前回に引き続きClang本を出します。
タイトルは「きつねさんとおぼえる!Clang おかわり」です。
タイトル案を適当にネタで出したらそのまま通って驚愕しているところでございます。
前回はプラットフォームとしてのClangの機能を駆け足で説明しましたが、
今回もテーマとしては同じでLibClangとLibToolingについて重点的に説明しています。
前回購入した方でも十分新しい内容が入っていますので、ぜひ立ち寄って頂ければと思います。
3,4章のLibClangについては風薬が担当し、5章のLibToolingと全体の構成を柏木餅子(id:motipizza, @sui_moti)が担当しています。
表紙や挿絵(!)は矢上さん(@re_sonanz, web)にお願いしています。
LibToolingを使った代表的なツールであるclang-formatやclang-modernizeの紹介から始まり、
4章ではLibClangの中でも抽象構文木にフォーカスしてRubyバインディングを使って詳しく解説し、
5章でLibToolingを使ってclang-degraderというものを作成しながら解説するという内容になってます。
以下、頒布場所の詳細になります。部数制限等は特にありません。
- コミックマーケット85
- 場所:東京ビッグサイト
- サークル名:MotiPizza
- 日程:3日目 12月31日(火)
- スペース番号:西す-05b
- 値段: 1部 800円
- ページ数: 110ページ
- 部数制限: 無し
- 合計部数: 200部
毎回売り切れになってしまって申し訳なかったので今回は余らせる勢いで200部刷ってあります。
前回の「きつねさんとおぼえる!Clang」も50部刷りますので買い逃した方はぜひ両方ともご購入下さい。
- はしがき
- はじめに
- 本書の構成
- 本書の目的
- 本書内の記載について
- お断り
- 環境構築とClang の使い方
- 本章の概要と構成
- 環境
- インストール
- ビルドに必要なパッケージのインストール
- LLVM/Clang のビルド/インストール
- パスの設定
- ツールの種類と確認
- clang
- clang-check
- clang-format
- clang-modernize
- LibClang AST の概要
- LibClang チュートリアル
- LibTooling おかわり!
- あとがき
- 著者/関係者一覧
Fluentdの知られていない6つのこと
Fluentdの知られていない6つのこと
本当に知られていないかはわからないです。 公式にはあまり説明されていなかったり調べてもなかなか見つからないことが多いと個人的に思ったものを集めました。 機能や言葉の細かい説明は省いているのである程度使っている人が対象です。
out_copyはshallow copy
Fluentdで最初に使うであろうビルドインされているout_copyプラグインですが、実はデフォルトではメッセージをdeep copyしないため意図しない結果になることがあります。
<match test> type copy <store> type record_modifier tag test.aa foo bar </store> <store> type retag tag test.bb </store> </match> <match test.{aa,bb}> type stdout </match>
例えばこのような設定の場合、test.aa
のみに foo=bar
が追加されることが期待されます。
しかし実際には test.bb
にも foo=bar
が追加されてしまいます。
これはout_copyプラグインがshallow copyになっているためです。
この問題を解決するためのトリッキーな方法としてメッセージを加工するプラグインは最後に持ってくるなどの方法があったりしますが、処理する順番が今後も記述順のままである保証もないので微妙なところです。
現最新版の v0.10.39 ではout_copyプラグインでdeep copyを行う deep_copy
が追加されました。
デフォルトではoffになっているので、必要に応じて on にしましょう。
TCP Heartbeat
Fluentdの肝であるout_forwardは接続先の生存確認(heartbeat)のために定期的にパケットを送信しています。 このパケットの送信はUDPで行われているため、環境によってはTCPは開いてるけどUDPは閉じてるということがあり、別の環境では動いたのにこの環境では動かない理由がわからない…などということがあったりします。
実はこの生存確認の方法も heartbeat_type
オプションで指定することが可能で、:tcp
とすることでTCPによる確認が可能になります。
自分は最近まで知らなかったですが、結構前から実装されているので気軽に使っても良さそうです。
secondaryは実はなんでも使える
公式のドキュメントでは out_forwardプラグイン のオプションとして説明されている secondary
オプションですが、細かい説明もないのでデフォルトのままなんとなく使っているということも多いと思います。しかし、 この secondary オプションは、使いこなせると更に強固なシステムを構築することもできるので、ぜひ知っておいた方が良いです。
まず、 secondary
オプションは out_forwardプラグイン以外にも使うことができます。
もともとこのオプションの目的が"BufferedOutputプラグインでチャンクに入りきらなくなったときにどうするか"というもののため、BufferedOutputプラグインであれば全てのプラグインで使うことができます。
out_forwardに限らず、詰まる可能性があるところでは使っておいた方が良いでしょう。
次に、 secondary
オプションで指定できるtypeは file
だけではありません。
secondaryで指定する設定はOutputプラグインの設定と全く同じなので、実はどんなプラグインでも書くことはできます。つまりtypeにfileを指定した場合は out_fileプラグインの全てのオプションを使うことができます。
ただし、内部の実装を完全に理解している人以外はout_file以外を使うべきではありません。
その理由はBufferedOutputのバッファの扱い方がプラグインにより異なるため、組み合わせによってはうまく動作しません。というよりほとんどの場合そのまま使えるプラグインはないでしょう。
このあたりはFluentd本体を改善することでなんとかなりそうですがまだ先は長そうです。
out_fileのログに対してシンボリックリンクを生成する
out_fileプラグインを使って最初に驚くことがログファイル名です。
オプションでフォーマットを指定できるので、例えば time_slice_format %Y%m%d
のように指定したら foo.20130928.log
とかになるんだろうなーと思ったら、実際には foo.20130928.b4e76e103eebf8c2f
とか謎のsuffixがついて困るということがあります。
大抵のログローテーション付きログの仕組みにあるように、Fluentdにも最新のログファイルに対するシンボリックリンクを貼る機能があります。
path
オプションと同様に symlink_path
オプションでシンボリックリンクのパスを指定すると常に最新のファイルを差し続けるくれます。
ただし、実装の仕様上一時的にシンボリックリンク先がなくなることがことがあります。その場合も新しいログができると自動的にリンク先を変えてくれます。
シンボリックリンクを参照するときには tail -F
で見るとよいでしょう。
こちらも最新版の v0.10.39 から利用することができます。
num_threadsで並列処理数を指定できる
BufferedOutputを並列に行うためのオプションとしてnum_threads
があります。
ただし、このオプションは誰も使っていない可能性すらあります。
BufferedOutputプラグインで num_threads
を指定することで、writeの処理を並列に行うことができます。実装としてはキューにたまった処理を並列に取り出して各スレッドで処理するというようになっていて、見た目上は問題無さそうです。今のところ自分ではまだ試していないです。
ソースコードを読んでいて少し気になったところとしては、try_flushでキューが空じゃないとエンキューしないので、chunk_limitを頻繁に超えるようなものじゃないと複数スレッドあってもデキューできないような気がしました。
BufferedOutputをバルク処理以外に使うのは微妙
BufferedOutputの特徴としてリクエストのバッファリングと再送機能によってリクエストの送信を保証できるということが挙げられます。 よって、失敗する可能性のある処理は全てBufferedOutputで行いたいと思います。
BufferedOutputは溜まったリクエストを一括で処理することを想定している(ように思える)ので、個別のリクエストを処理するようなプラグインを書くのは注意が必要です。 その理由は再送処理の粒度がチャンク単位となっているからです。 5つのリクエストがバッファに溜まっている時に5回のリクエストに分けて送信するとします。この時に、2つ目目までは成功したけど3つ目で失敗して再送処理をFluentdに任せると、失敗した3つめからではなく最初のリクエストからやりなおしになります。 よって、リクエストを個別に行う場合は冪等性が保証されている必要があります。
この問題を解決しようと汎用的なOutputクラスを作ろうとしてましたが、中の仕組みを保ったまま作るのが意外と難しいので止まってます。 誰か作ってください!
さいごに
Fluentdは使うのが簡単で機能も十分にありますが、やはり使っていく上でプラグインを書いたり安全性を確認したりすると内部の実装を知る必要が出てきます。 実装はそれほど複雑ではなく行数も短いためすぐに読めると思います。 読んでみると改善すべき点も意外といっぱいあったりするので、使っている人は読んで貢献していきましょう。
記事に間違い等ありましたらご連絡下さい。
きつねさんとおぼえる!ClangをC84で出します
きつねさんシリーズ第三弾としてClang本を出します。
タイトルは「きつねさんとおぼえる!Clang」です。
今までLLVMを扱っていましたが、今回からClangについて紹介していく予定です。
今回はあまり時間が取れなかったため応用的な内容までは紹介することはできていませんが、
Clangがどのような機能を提供しているかというのを理解できると思います。
Sanitizer、LibClang、LibTooling、ClangPluginを大きなテーマとして紹介しています。
自分がSanitizerとLibClangを担当し、残り2つを餅さんが担当しています。
表紙はいつも通り矢上さん(web)に書いて頂きました。
表紙を見て頂ければわかる通り、新しいキャラクタとしてアンが登場しています。
更に今回は表紙に追加して、今まで登場したキャラクタの設定資料集がついています。
これだけでも十分買って良かった!と思えるものになってます。
以下、頒布場所の詳細になります。部数制限等は特にありません。
コミックマーケット84
場所:東京ビッグサイト
サークル名:MotiPizza
日程:3日目 8月12日(月)
スペース番号:東ペ-04a
値段: 1部 700円
ページ数: 102ページ
部数制限: 無し
合計部数: 150部
以下、章構成。
- はしがき
- はじめに
- 本書の構成
- 本書内の記載について
- お断り
- 環境構築とClang の使い方
- 本章の概要と構成
- 環境
- インストール
- ツールの種類と確認
- はじめてのSanitizer
- Sanitizer とは
- Sanitizer の種類
- LibClang
- LibClang でできること
- 翻訳単位について
- トークンの取得
- 抽象構文木の走査
- 診断情報の取得
- コード補完情報の取得
- コンパイルデータベース
- LibTooling
- ビルド環境の構築
- Clang AST の概要
- LibTooling で簡単なツールを作る
- RecursiveASTVisitor でAST の情報を出力する
- LibAstMatchers で特定パターンのAST を捕捉する
- RefactoringTool でコードを書き換えてみる
- ClangPlugin
- ClangPlugin で簡単なプラグインを作成する
- あとがき
- 著者/関係者一覧