はじめに
Pony のチュートリアルを眺めていると、シリアライズ機構が組み込まれているという記述がありました。
オブジェクトのシリアライズができると面白いことができそうな気がしたのですが、よくよく読むと
- 現状では全く同じバイナリで動作しているプログラム同士でしかシリアライズされたオブジェクトを受け渡しできない
-
embed
なフィールドはシリアライズ不可 -
Pointer
フィールドはデシリアライズすると null になる。それが嫌ならシリアライズ機構を自分でカスタムせよ
などと書かれています。
異なるバイナリ間で受け渡ししたり永続化したりできるポータブルな形式にできるのなら使いみちはかなりありそうですが、同一バイナリでしかオブジェクトの受け渡しができないということだと利用可能な状況はかなり限られます。
とはいえ、どのような形式でシリアライズされるのか興味があったのでシリアライズ結果を覗いてみます。
16 進ダンプ
シリアライズされた内容を確認するためにまず、Array[U8] を 16 進ダンプするクラスを作りました。
(オフセット) : (16 進数での羅列) | (キャラクタでの羅列)
の形式で表示します。
use "format"
class HexDump
new create(env: Env, a: Array[U8] box) =>
"""
与えられた Array[U8] の内容を 16 進ダンプする。
"""
var i: USize = 0
var j: USize = 0
var shex: String ref = String(60) // 16 進数として表示する部分
var stxt: String ref = String(16) // キャラクタとして表示する部分
for v in a.values() do
if shex == "" then
shex.append(
Format.int[USize](i where width = 8, fill='0', fmt=FormatHexBare))
shex.append(" :")
end
shex.append(
" " + Format.int[U8](v where width = 2, fill='0', fmt=FormatHexBare))
stxt.push(if (v < ' ') or (v > 'z') then '.' else v end)
i = i + 1
j = j + 1
if j >= 16 then
_print_line(env, shex, stxt)
j = 0
shex.clear()
stxt.clear()
end
end
if shex != "" then
_print_line(env, shex, stxt)
end
fun _print_line(env: Env, shex: String ref, stxt: String ref) =>
while shex.size() < 58 do
shex.push(' ')
end
env.out.print(shex + " | " + stxt)
シリアライズ
シリアライズ対象のクラスをこんなふうに作ってみました。
class Test
let i: U32
let str: String
new create(i': U32, str': String) =>
i = i'
str = str'
まずは、これをシリアライズするとどうなるのかを見てみます。
use "serialise"
actor Main
new create(env: Env) =>
let serialise = SerialiseAuth(env.root)
let output = OutputSerialisedAuth(env.root)
let t: Test = Test(0xdeadbeaf, "test")
try
let s = Serialised(serialise, t)?
let a = s.output(output)
env.out.print("Test(0xdeadbeaf, \"test\")")
HexDump(env, a)
else
env.out.print("failed to serialise")
end
実行結果は以下のようになりました。引数に与えた数値 0xdeadbeaf(リトルエンディアンなので逆順)および文字列 "test" がそのまま見えています。
$ ./test_serialise
Test(0xdeadbeaf, "test")
00000000 : 0F 00 00 00 00 00 00 00 AF BE AD DE 00 00 00 00 | ................
00000010 : 18 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000020 : 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000030 : 38 00 00 00 00 00 00 00 74 65 73 74 00 | 8.......test.
クラスが入れ子になっている場合はどうなるかも試してみます。
class Test2
let test: Test
new create(test': Test) =>
test = test'
というのを作って、Test2(Test(0xdeadbeaf, "test"))
をシリアライズしてみました。
$ ./test_serialise
Test2(Test(0xdeadbeaf, "test"))
00000000 : 11 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 | ................
00000010 : 0F 00 00 00 00 00 00 00 AF BE AD DE 00 00 00 00 | ................
00000020 : 28 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | (...............
00000030 : 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 | ................
00000040 : 48 00 00 00 00 00 00 00 74 65 73 74 00 | H.......test.
微妙に大きくなりました。
シリアライズ手続きのランタイム処理
(ponyc/packages/serialise/serialise.pony)[https://github.com/ponylang/ponyc/blob/main/packages/serialise/serialise.pony] にある Serialised コンストラクタで
@pony_serialise(@pony_ctx(), data, Pointer[None], r, alloc_fn, throw_fn) ?
_data = consume r
のようにフィールド _data が設定されます、output メソッドは _data を返すだけです。
@pony_serialise
は (ponyc/src/libponyrt/gc/serialise.c)[https://github.com/ponylang/ponyc/blob/main/src/libponyrt/gc/serialise.c] にあって、
- コンテキスト(
pony_ctx_t
) にあるハッシュマップserialise
にシリアライズ対象オブジェクトを追加する。 - ハッシュマップ
serialise
にあるオブジェクト全てをシリアライズする。 - ハッシュマップ
serialise
をクリアする。
という感じで動作しているようです。コンテキストに一旦積むのは、対象オブジェクトがシリアライズ中に GC で消えてしまうのを防ぐためかなと思います。
ponyint_serialise_actor の中の処理を見ると abort となっていて、アクターのシリアライズは禁止されているようです。なのでアクターを他プロセスにマイグレーションさせる、といった使い方は少なくとも現状ではできなさそうです。
おまけ
yuml で図を書きながら Pony ランタイムのソースを見ました。自分用メモとして残して置きます。
[actor]<>-q>[messageq]
[actor]-type>[type]
[actor]<>-heap>[heap]
[actor]<>-gc>[gc]
[messageq]-tail>[message]
[messageq]-head>[message]
[messageq]-tail>[message]
[message]0..1-next>[message]
[gc]<>-local>[objectmap]
[gc]<>-foreign>[actormap]-*>[actor]
[gc]-delta>[deltamap]-*>[delta]
[heap]-small_free**>[chunk]
[heap]-small_full**>[chunk]
[heap]-large*>[chunk]
[chunk]-actor>[actor]
[chunk]-next>[chunk]
[objectmap]-*>[object]
[object]-type>[type]
[delta]-actor>[actor]
[ctx]-current>[actor]
[ctx]-stack>[gcstack]
[ctx]<>-acquire>[actormap]
[ctx]<>-serialise>[ponyint_serialise_t(hashmap)]
[ponyint_serialise_t(hashmap)]->[serialise]-t>[type]
[ctx]-scheduler>[scheduler]
[scheduler]<>-sleep_object>[pony_signal_event]
[scheduler]-last_victim>[scheduler]
[scheduler]<>-ctx>[ctx]
[scheduler]<>-mute_mapping>[mutemap]-*>[actor]
[scheduler]<>-q>[mpmcq]
[scheduler]<>-mq>[messageq]
[<<messageq_t>>;messageq]
[<<pony_msg_t>>;message|+index: u32_t;+id: u32_t]
[<<pony_actor_t>>;actor|+muted: atomic(size_t);+flags: atomic(uint8_t);+is_muted: atomic(uint8_t)]
[<<pony_type_t>>;type|+id: uint32_t;+size: uint32_t;+field_count: uint32_t;+field_offset: uint32_t;+instance: void*;+trace: pony_trace_fn;+serialise_trace: pony_trace_fn;+custom_serialise_space: pony_custom_serialise_space_fn;+custom_deserialise: pony_custom_deserialise_fn;+dispatch: pony_dispatch_fn;+final: pony_final_fn;+event_notify: u32_t;+traits: uintptr_t**;+fields: void*;+vtable: void*]
[<<gc_t>>;gc|+mark: uint32_t;+rc_mark: uint32_t;+rc: size_t]
[<<heap_t>>;heap|+used: size_t;+next_gc: size_t]
[<<chunk_t>>;chunk|+m: char*;+size: size_t;+slots: uint32_t;+shallow: uint32_t;+finalisers: uint32_t]
[<<object_t>>;object|+address: void*;+rc: size_t;+mark: uint32_t;+immutable: bool]
[<<delta_t>>;delta|+rc: size_t]
[<<pony_ctx_t>>;ctx|+trace_object: trace_object_fn;+trace_actor: trace_actor_fn;+serialise_buffer: void*;+serialise_size:size_t;+serialise_alloc: serialise_alloc_fn;+serialise_alloc_final: serialise_alloc_fn;+serialise_throw: selialise_throw_fn]
[<<scheduler_t>>;scheduler;+tid: pony_thread_id_t;+index: int32_t;+cpu: u32_t;+node: uint32_t;+terminate: bool;+asio_stoppable: bool;+asio_noisy: bool;+block_count: uint32_t;+ack_token: int32_t;+ack_count: uint32_t]
[<<serialise_t>>;serialise|+key: uintptr_t;+value: uintptr_t;+mutability: int;+block: bool]