0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pony 試乗記(6) シリアライズ

Posted at

はじめに

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] にあって、

  1. コンテキスト(pony_ctx_t) にあるハッシュマップ serialise にシリアライズ対象オブジェクトを追加する。
  2. ハッシュマップ serialise にあるオブジェクト全てをシリアライズする。
  3. ハッシュマップ serialise をクリアする。

という感じで動作しているようです。コンテキストに一旦積むのは、対象オブジェクトがシリアライズ中に GC で消えてしまうのを防ぐためかなと思います。

ponyint_serialise_actor の中の処理を見ると abort となっていて、アクターのシリアライズは禁止されているようです。なのでアクターを他プロセスにマイグレーションさせる、といった使い方は少なくとも現状ではできなさそうです。

おまけ

yuml で図を書きながら Pony ランタイムのソースを見ました。自分用メモとして残して置きます。

pony.png

[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]
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?