FlatBuffer
FlatBuffer
FlatBuffer란 메세지 송/수신에 사용되는 플랫폼 종속성 없이 사용가능한 “직/역직렬화 라이브러리”이다.
FlatBuffer사용 이유
- 데이터 송/수신 시 Parsing/Unpacking이 필요 없다.
- 메모리 효율성이 높고, 빠른 속도를 보장한다.
- 사용하는 데이터타입에 대한 유연성이 존재한다.
- 적은 양의 코드로 작성 가능하다.
- 사용하기 편리하다.
- 여러 플랫폼에서 사용가능하다.
위와 같은 장점이 존재하는 FlatBuffer는 직/역직렬화 라이브러리라는 점에서 Google Protocol Buffer 와 같은 역활을 한다.
아래 사진을 참고하면 FlatBuffer를 사용하는 이유를 알 수 있다.
Google Protocol Buffer 와 FlatBuffer의 가장 큰 차이점은 FlatBuffer의 사용 이유의 1번의 내용처럼 데이터 송/수신 시 Parsing/Unpacking이 필요 없다는 점 이다.(Zero-copy)
이로 인하여 시간의 측면에서 매우 큰 장점을 나타내는 것을 보인다.
아래 사진또한 FlatBuffer를 사용하는 이유를 알 수 있다.
Google Protocol Buffer에 비해 FlatBuffer의 가장 뚜렷한 장점 2가지는 zero-copy와 Random-access-reads이다.
1) Zero Copy
Zero Copy는 Network에서 Read/Write할때 걸리는 불필요한 Copy과정을 최소화 하자는 것이다.
아래 그림을 살펴보게 되면 기존의 데이터 복사 과정이다.
Application buffer를 거쳐가므로 4번의 과정이 필요하다는 것을 알 수 있다.
아래 그림은 Zero Copy의 과정이다.
Application buffer를 거쳐지 않으므로 2번의 과정이라는 것을 알 수 있다.
2) Random-access-reads
FileBuffers는 각 레코드에 모든 필드 위치에 대한 오프셋 테이블을 저장하고 개체간 포인터를 사용하여 임의 액세스를 허용
구조 및 사용 방법
Google Protocol Buffer 와 마찬가지로 과정이 진행된다.
IDL(Interface Description Language)작성 -> flatc로서 Compile -> 사용
IDL(Interface Description Language) 작성
아래와 같은 예시를 살펴보고 이것에 대한 내용을 정리한다.
// Example IDL file for our monster's schema.
namespace MyGame.Sample;
enum Color:byte { Red = 0, Green, Blue = 2 }
union Equipment { Weapon } // Optionally add more tables.
struct Vec3 {
x:float;
y:float;
z:float;
}
table Monster {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte];
color:Color = Blue;
weapons:[Weapon];
equipped:Equipment;
path:[Vec3];
}
table Weapon {
name:string;
damage:short;
}
root_type Monster;
1) Table: Field의 집합
Field 추가 가능
deprecated로서 사용하지 않는 Field정의 가능
Field이름과 Table이름 바꾸기 가능
값을 직접 대입한 뒤, Value값을 주지 않으면 Default Value로서 자동으로 들어가게 된다.
2) Struct: Field의 집합
Field가 추가 되거나 deprecated할 수 없다.
Default Value가 없기때문에 값을 반드시 대입하여야 한다.
Table에 비해 메모리를 적게 차지하고 Access가 빠르다.
3) Type
Scalar
- 8 bit: byte (int8), ubyte (uint8), bool
- 16 bit: short (int16), ushort (uint16)
- 32 bit: int (int32), uint (uint32), float (float32)
- 64 bit: long (int64), ulong (uint64), double (float64)
Non Scalar
- Vector: [type]
- String: [byte] or [ubyte]
- tables, structs, enums, unions
Filed 변경시 같은 크기의 Type으로만 변경 가능
4) (Default) Values
숫자로 구성된 값
Scalar만 (Default)Values를 가질 수 있고, Non Scalar는 불가능
5) Enums
주어진 값을 가지거나 이전 값에서 하나씩 증가하는 값을 가진 sequence
6) Unions
여러 Message유형을 보낼 때 사용
Table의 이름으로서 구성
7) Namespaces
식별자가 고유하도록 ㄱ보장하는 코드 영역 정의
8) Root type
직렬화 된 데이터의 Root Table
참조: IDL 작성 자세한 내용
flatc
.proto file을 해당 언어에 맞는 클래스로 Compile하는 과정
1) flatbuffers 설치: Download the Package
2)Building with Cmake: Building
3) flatc 실행:
flatc [ GENERATOR OPTIONS ] [ -o PATH ] [ -I PATH ] [ -S ] FILES...
[ -- FILES...]
GENERATOR OPTIONS을 활용하여 해당 언어어 맞는 클래스로서 Compile가능
참조: Detail of Compiler Options
실제 동작 Code
flatc --python -o ./ ./monster.fbs
실행 결과(monster.py)
Name Space에 지정한 MyGame/Sample 폴더 안에 .py파일 생성
Writing a Message
1) FileBuffer생성
2) String 존재한다면 CreateString으로 문자열 생성하고 offset을 저장
3) Start(builder)
4) Add(builder, value)
5) result = End(builder)
6) builder.Finish(result)
7) builder.Output()
아래 Code는 FlatBuffer Class를 사용하여서 Monster를 등록하는 과정이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import sys
import flatbuffers
import MyGame.Sample.Color
import MyGame.Sample.Equipment
import MyGame.Sample.Monster
import MyGame.Sample.Vec3
import MyGame.Sample.Weapon
def main():
builder = flatbuffers.Builder(0)
#Add_weapon
input_weapons = []
output_weapons = []
while True:
weapon = raw_input("Enter a weapon name,dmage (or leave blank to finish): ")
if weapon == "":
break
name, damage = weapon.split(',')
imsi = [name, damage]
input_weapons.append(imsi)
for w in input_weapons:
weapon_imsi = builder.CreateString(w[0])
MyGame.Sample.Weapon.WeaponStart(builder)
MyGame.Sample.Weapon.WeaponAddName(builder, weapon_imsi)
MyGame.Sample.Weapon.WeaponAddDamage(builder, int(w[1]))
output_weapons.append(MyGame.Sample.Weapon.WeaponEnd(builder))
MyGame.Sample.Monster.MonsterStartWeaponsVector(builder, len(output_weapons))
for o in output_weapons:
builder.PrependUOffsetTRelative(o)
# Note: Since we prepend the data, prepend the weapons in reverse order.
weapons = builder.EndVector(len(input_weapons))
# Add_name
input_name = raw_input("Enter monster name: ")
name = builder.CreateString(name)
# Add-Inventory
input_inventory = int(raw_input("Enter a inventory: "))
MyGame.Sample.Monster.MonsterStartInventoryVector(builder, input_inventory)
# Note: Since we prepend the bytes, this loop iterates in reverse order.
for i in reversed(range(0, 10)):
builder.PrependByte(i)
inv = builder.EndVector(10)
# Add-pos
input_pos = raw_input("Enter monster pos(x,y,z): ")
x,y,z = input_pos.split(',')
pos = MyGame.Sample.Vec3.CreateVec3(builder, float(x), float(y), float(z))
#Add-Hp
input_hp = int(raw_input("Enter a hp: "))
#Add-Color
input_color = raw_input("Is this Monster'color Red, Green, or Blue? ")
# Add-Mana
input_mana = int(raw_input("Enter a Mana: "))
MyGame.Sample.Monster.MonsterStart(builder)
MyGame.Sample.Monster.MonsterAddPos(builder, pos)
MyGame.Sample.Monster.MonsterAddMana(builder, input_mana)
MyGame.Sample.Monster.MonsterAddHp(builder, input_hp)
MyGame.Sample.Monster.MonsterAddName(builder, name)
MyGame.Sample.Monster.MonsterAddWeapons(builder, weapons)
MyGame.Sample.Monster.MonsterAddInventory(builder, inv)
if input_color == "Red":
MyGame.Sample.Monster.MonsterAddColor(builder,MyGame.Sample.Color.Color().Red)
elif input_color == "Green":
MyGame.Sample.Monster.MonsterAddColor(builder,MyGame.Sample.Color.Color().Green)
elif input_color == "Blue":
MyGame.Sample.Monster.MonsterAddColor(builder,MyGame.Sample.Color.Color().Blue)
else:
print("Unknown Color type; leaving as default value.(Red)")
MyGame.Sample.Monster.MonsterAddColor(builder,MyGame.Sample.Color.Color().Red)
MyGame.Sample.Monster.MonsterAddEquippedType(
builder, MyGame.Sample.Equipment.Equipment().Weapon)
print("Which weapon",input_name," equipped?")
count = 0
for w in input_weapons:
print("Number: ",count,'Weapon_name: ',w[0])
count = count+1
select_weapon = int(raw_input("Choose Select: "))
MyGame.Sample.Monster.MonsterAddEquipped(builder, output_weapons[select_weapon])
orc = MyGame.Sample.Monster.MonsterEnd(builder)
builder.Finish(orc)
buf = builder.Output()
with open(sys.argv[1], "wb") as f:
f.write(buf)
print 'The FlatBuffer was successfully created and verified!'
if __name__ == '__main__':
main()
실제 Input값
Variable | 실제값 |
name | monster1 |
mana | 10 |
hp | 20 |
Color | Red |
Pos | 10, 20, 30 |
weapons |
|
inventory | 10 |
Equipped | name: w1, damage: 10 |
실행 결과
실행 결과 Binary File로서 저장되는 것을 알 수 있다.
Reading a Message
Non-deprecated Scalar 변수에 대해서는 바로 접근 가능하다.
1
2
3
hp = monster.Hp()
mana = monster.Mana()
name = monster.Name()
Sub-Object의 경우 Sub-Object에 접근 한 뒤 Sub-Object 의 Field에 접근 가능
1
2
3
4
pos = monster.Pos()
x = pos.X()
y = pos.Y()
z = pos.Z()
Vector의 경우 Length를 통하여 길이를 알아낸 뒤 반복문으로 접근 가능
1
2
inv_len = monster.InventoryLength()
third_item = monster.Inventory(2)
Union의 경우 해당 위치와 크기를 통하여 접근 가능하다.
1
2
3
4
5
6
7
8
union_type = monster.EquippedType()
if union_type == MyGame.Sample.Equipment.Equipment().Weapon:
# `monster.Equipped()` returns a `flatbuffers.Table`, which can be used to
# initialize a `MyGame.Sample.Weapon.Weapon()`.
union_weapon = MyGame.Sample.Weapon.Weapon()
union_weapon.Init(monster.Equipped().Bytes, monster.Equipped().Pos)
weapon_name = union_weapon.Name()
weapon_damage = union_weapon.Damage()
위의 Union의 접근을 통해서 아래와 같은 그림을 예상할 수 있다.
즉, FileBuffers는 offset이 어디서부터 시작되고 끝나는지를 명시함으로써 Random-acess-read가 가능하다는 것을 알 수 있다.
아래 코드는 만들어진 Binary File을 읽는 과정이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python
# coding: utf-8
import monster_pb2
import sys
def ListMonster(address_monster):
for monster in address_monster.monster:
print("Monster pos: ",monster.pos.x, monster.pos.y, monster.pos.z)
print("Monster mana:", monster.mana)
print("Monster hp:", monster.hp)
print("Monster name:", monster.name)
print("Monster friendly:", monster.friendly)
print("Monster inventory: ")
for monster_inventory in monster.inventory:
print(monster_inventory)
print("Monster color: ",monster.color)
print("Monster weapon: ")
for w in monster.weapons:
print("Name: ",w.name)
print("Damage: ",w.damage)
print("Monster equipped")
for e in monster.equipped.weapon:
print(e)
print("Monster path:")
for p in monster.path:
print(p)
if len(sys.argv) != 2:
print("Usage:", sys.argv[0], "Save_FILE")
sys.exit(-1)
address_monster = monster_pb2.AddressMonster()
with open(sys.argv[1], "rb") as f:
address_monster.ParseFromString(f.read())
ListMonster(address_monster)
실행 결과
Monster Weapon_list
Weapon Name: b'w2'
Weapon Damage: 20
Weapon Name: b'w1'
Weapon Damage: 10
Monster Name b'w2'
Monster Inventory
0 1 2 3 4 5 6 7 8 9
Monster Pos
Pos.x: 10.0
Pos.y: 20.0
Pos.z: 30.0
Monster Hp 20
Monter Color: Red
Monster Mana 10
Monster Equipped List
Weapon name: b'w1'
Weapon damage: 10
참조:Writing & Reading A Message 자세한 내용
참조: 원본코드
참조: 곰팡이 먼지연구소
참조: capnproto
참조: skydays 블로그
문제가 있거나 궁금한 점이 있으면 wjddyd66@naver.com으로 Mail을 남겨주세요.
Leave a comment