64
Avoiding Developer Taxes (Making development more robust with introspection tools. ) Stephen.Kennedy @ havok.com Principal Engineer github:clang-extract

개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Embed Size (px)

Citation preview

Page 1: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Avoiding Developer Taxes (Making development more robust with introspection tools. )

Stephen.Kennedy @ havok.com Principal Engineer

github:clang-extract

Page 2: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

2

Awesome Coding

Awesome “Other” Collecting Awards

Page 3: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

3

Awesome Coding

Debugging

Meetings

Refactoring

Awards

Page 4: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

4

Page 5: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

The Classic Game Taxes

Versioning

5

Script Binding

Memory Reporting Serialization

Page 6: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Memory Lane

10 Years of taxes

Why we changed

Automate

Correctness

Spread the workload

6

Page 7: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Structure

Serialization

Reflection

Memory Reporting

Script Binding

Versioning

7

Page 8: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Manual serialization

xtream.TokenMustBe("POINT_A"); xtream>>point_A;

bool has_two_bodies = true;

if (xtream.GetVersion() >= 1350)

{

xtream.TokenMustBe("HAS_TWO_BODIES"); xtream>>has_two_bodies;

}

if (has_two_bodies)

{

xtream.TokenMustBe("BODY_B"); xtream>>prot_buffer;

priv_rigid_body_B = prot_subspace->GetRigidBody(prot_buffer);

if (!priv_rigid_body_B) throw_exception("Rigidbody unknown in Spring");

}

xtream.TokenMustBe("POINT_B"); xtream>>point_B;

//…

8 S

Page 9: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Reflection Recap (1)

struct A0 { float x; byte y; }

struct A1 { float z; byte p; byte q };

struct A2 { byte i[3]; }

struct A1023 { };

9 S

Page 10: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

struct A0 { float x; byte y; }

char A0_type[]={ “FB” };

void save_any( void* obj, char* type )

while(*type) { switch( *type++ ) {

case ‘F’:// write(); obj += sizeof(float);

case ‘B’:// write(); obj += sizeof(bool);

case 0: return; }

Reflection Recap (2)

10 S

Page 11: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Serialization with Reflection

Straightforward.

The problem now is …

11

(mostly)

Page 12: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Get Reflected

How to reflect our data?

How to keep it up to date?

Robustness

12

Page 13: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Manual Reflection

class Foo {

public:

enum Flags {…};

int i;

char* pc;

double d;

Flags flags;

protected:

long larr[10];

A* pa;

};

RTTI_DESCRIBE_CLASS( Foo, ( RTTI_FIELD(i, RTTI_FLD_PUBLIC), RTTI_PTR(pc, RTTI_FLD_PUBLIC), RTTI_FIELD(d, RTTI_FLD_PUBLIC), RTTI_FIELD(f, RTTI_FLD_PUBLIC), RTTI_ARRAY(larr, RTTI_FLD_PROTECTED), RTTI_PTR(pa, RTTI_FLD_PROTECTED) ) );

13 R

Page 14: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Parsing Headers

14 R

Dirty Regexes

Foo.h

FooReflect.cpp

class Foo {

public:

int i;

char* pc;

double d;

enum Flags flags;

protected:

long larr[10];

class B* pb;

#ifdef PLATFORM_Y

special* s;

#endif

};

Page 15: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

We Went Full Auto

C++ headers

GCC-XML Master.h DB

Script1 Script2

15 R

Generate Reflection

Page 16: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Clang AST Consumer

class RawDumpASTConsumer : public ASTConsumer

{

virtual void Initialize(ASTContext& Context);

virtual void HandleTopLevelDecl(DeclGroupRef DG)

{

// ...

if( const FieldDecl* fd = dyn_cast<FieldDecl>(declIn) ) // ... else if( const CXXMethodDecl* md = dyn_cast<CXXMethodDecl>(declIn) ) // ... else if( const EnumConstantDecl* ed = dyn_cast<EnumConstantDecl>(declIn) ) // ... }

};

16 R

Page 17: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Clang Custom Output

17

File( id=20270, location='Base/Types/Geometry/hkGeometry.h' )

RecordType( id=20271, name='hkGeometry', polymorphic=False, abstract=False, scopeid=20270 )

Method( id=20317, recordid=20271, typeid=20316, name='getTriangle' )

Field( id=20320, recordid=20271, typeid=9089, name='m_vertices' )

R

Page 18: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Build Integration

Prebuild step runs Clang if necessary

Plugins run on the database

That’s it

18 R

ClangAST DB

Generate Reflection

Reflect.cpp

Static Analysis

Page 19: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Runtime Introspection

float

bool

float

bool short float

bool bool short

float

19 R

struct X { float time; bool used; float pos; bool canmove; short flags; };

Page 20: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

20 R

Page 21: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Reflection Conclusion

LLVM Clang pass

• Robust

• Eliminates out-of-sync errors

DB Consumer pass

• Pre-compile logic checks

• Runtime errors → compile errors

Unit Tests

• Examine reflection data

21

Page 22: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Language Binding

Expose C++ to script

Data: Natural

Callables: Harder

22

Page 23: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Sample Interface

class Timeline

{

/// Adds a label at the given time and

/// returns an id for that label

int addLabel( float time, const char* label );

};

23 B

Page 24: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

What is a Binding?

24

Lua State int addLabel( float time, const char* label) { // ... }

Lua_String “GDC”

Lua_Num 1.4

B

Page 25: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Three Parts of a Binding

25

Lua State

Lua_String “GDC”

Lua_Num 1.4

float time = lua_... char* label = lua_... push time push label call addLabel pop id

lua_set_return( id )

Lua_Int ret = 64

B

Page 26: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

int wrap_timeLine_addLabel(lua_state* s) { TimeLine* tline = lua_checkuserdata(s,1); int arg0 = lua_checkint(s,2); const char* arg1 = lua_checkstring(s,3); int id = tline->addLabel( arg0, arg1 ); lua_pushint(id); return 1; }

Manual Bindings

26 timeLine:addLabel(1,”x”)

B

Page 27: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Normal “Fat” Bindings

1:1 wrapper:native

Manual or Generated

~400b per wrapper

27 B

timeLine:addLabel(1,”x”)

wrap_addLabel()

TimeLine::addLabel()

Page 28: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Slimmer Bindings?

28

wrap_x()

wrap_y()

wrap_z()

wrap_z()

wrap_any()

data_x data_y

data_z data_w

B

Page 29: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

What is a Trampoline?

29

typedef void (*ErrorFunc)(const char* msg, void* context); void set_error_function( ErrorFunc func, void* context );

B

class MyHandler { void on_error(const char* msg) = 0; };

void trampoline(const char* msg, void* context) { ((MyErrorHandler*)context)->on_error(msg); }

MyErrorHandler* handler = ...; set_error_function( &trampoline, handler );

MyHandler* handler = ...; set_error_function( &MyHandler::on_error, handler );

Page 30: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Reflected Function

struct FunctionReflection {

const char* name;

Callable callable;

TypeReflection* argTypes;

int numArgs;

};

30 B

// “function pointer”

// Including return type

Page 31: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Slimmer Binding

31 B

int wrap_anything(lua_state* s) { FunctionReflection* fr = func_get(s); Buffer buf; unpack_args(s, fr->argTypes, buf); (*fr->callable)(buf); return pack_args(s, fr->argTypes, buf); }

Page 32: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Function Trampoline

32

typedef int (*Func_int__int_charp)(int i, char* c); void trampoline(void** buf, Func_int__int_charp funcptr) { int& ret = *(int*)buf[0]; int& a0 = *(int*)buf[1]; char* a1 = *(char**)buf[2]; ret = (*funcptr)(a0,a1); }

B

Page 33: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Trampolines

33

call_in_lua()

lua_bridge()

f0(int x)

trampoline_int()

B

native(int,char*)

trampoline_int_charp()

f1(int x)

Page 34: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Fat vs Slim Memory Cost

34

N * wrap_func()

N * func()

N functions T distinct trampolines (T≤N)

1 lua_bridge() N * Reflection

T * trampoline()

N * func()

B

~400*N ~40*N + ~64*T

Page 35: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Sharing the Trampolines

35

tl:addLabel(1,”x”)

lua_bridge()

trampoline()

the_actual_native_function()

python_bridge()

tl.addLabel(1,’x’)

B

Page 36: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Bindings Conclusion

Generated “Slim” Bindings

Crossplatform & Multilanguage!

Marginally slower

Extra indirection

Considerably smaller

36

Page 37: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Memory Reporting Goals

More than just block sizes

Account for every byte

Low maintenance burden

Customizable output

37 M

Page 38: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Memory Reporting

Manual getMemoryStatistics()

Buggy

Tedious

38 M

void hkpMeshShape::calcContentStatistics ( hkStatisticsCollector* collector ) const { collector->addArray( "SubParts", this->m_subparts ); for( int i = 0; i < this->m_childInfo.getSize(); i++ ) { collector->addReferencedObject( "Child", m_childInfo[i].m_shape ); } hkpShapeCollection::calcContentStatistics(collector); }

Page 39: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Automatic Reports

Aim for 100% automatic coverage

Provide debugging for missing

Leapfrog Technique:

Remember types of (some) allocations

Know offsets of pointers in each type

39 M

Page 40: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Raw Blocks

Raw pairs of (address,size)

40 M

Page 41: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Type Roots

?

Mesh

?

?

BvTree

?

Hooked class operator new/delete

41 M

Page 42: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Reflection Walk

Mesh {

Section [];

};

Section {

Vector4[];

Indices[];

};

?

Mesh

?

Section

BvTree

void

42 M

Page 43: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Reflection Walk

Finds all pointer-to relationships

Debug – Time & Stacktrace

Verify everything reached

vector4[]

Mesh

int[3][]

Section

BvTree

void

43 M

Page 44: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Memory Implementation Details

Custom Type Handlers

slack space – false positives

Obfuscated Pointers

ptr ^= 1;

Untyped Data

void*

44 M

Page 45: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Annotated Memory Dump D 1287169594

M Win32 “demos.exe"

T 0 UNKNOWN

T 1 hkNativeFileSystem

C 13 0x29656826 0x29828312 …

a 0 8

t 0 1

c 0 13

o 1 113 9

o 2 193

L 0x29656826 source\common\base\system\io\filesystem\hknativefilesystem.h(90):'hkNativeFileSystem::operator new'

#L(ocation) <address> <string name>?

#C(allstack) <callstack id> <address>+

#T(ype) <type id> <type name>

#a(llocation) <alloc id> <size>

#t(ype instance) <alloc id> <type id>

#c(allstack instance) <alloc id> <callstack id>

#o(wns) <alloc id> <'owned' alloc id>+

#M(odule information) <platform-dependent>

#D(ate of capture) <timestamp>

45 M

Page 46: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

46 M

Page 47: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Memory Report Conclusion

Label root blocks & grow with reflection

We found offline analysis useful

Low maintenance

Accounts for all reachable memory

Or tells you where it came from

47

Page 48: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Versioning

Change Happens

Hitting a moving target.

Not much in literature.

48 V

Page 49: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Manual Conditionals

Version number

Conditionals

Inter-object changes?

Large refactor?

49 V

xtream.TokenMustBe("POINT_A"); xtream>>point_A;

bool has_two_bodies = true;

if (xtream.GetVersion()>=1350)

{

xtream.TokenMustBe("HAS_TWO_BODIES");

xtream>>has_two_bodies;

} if (has_two_bodies)

{

xtream.TokenMustBe("BODY_B");

xtream>>prot_buffer;

priv_rigid_body_B = s->GetRigidBody(prot_buffer);

if (!priv_rigid_body_B)

throw_exception("Rigidbody unknown in Spring");

}

xtream.TokenMustBe("POINT_B"); xtream>>point_B;

//…

Page 50: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Ideal Versioning System

Don’t slow me down

Don’t constrain my changes

Help me fix it up

Don’t make me think

Don’t make me revisit

50 V

Page 51: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Snapshot Versioning

Compare all previous vs current metadata

Function updates changed members

Check up-to-date with CRC of reflection

51 V

Page 52: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Snapshot example

A { int x; int y; }

B { A* a; }

52 V

A { int y; }

B { A* a; int x;}

{ “A”, 0x1234, 0x8989, NULL }

{ “B”, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) {

// newB->x = oldB->a->x; }

Page 53: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Version Function

53 V

void Update_B( Object& oldB, Object& newB )

{

newB[“x”] = oldB[“a”].asObject()[“x”].asInt();

}

A { int x; int y; }

B { A* a; }

A { int y; }

B { A* a; int x;}

Page 54: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Chaining Snapshot Updates

54

A { int x; int y; } B { A* a; }

A { int y; } B { A* a; int x;}

{ “A”, 0x1234, 0x8989, NULL } { “B”, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) { // newB->x = oldB->a->x; }

V1 → V2 A { int y; } B { A* a; int x; }

A { int y; int z; } B { A* a; int x; }

{ “A”, 0x8989, 0x52c1, Update_A } { “B”, 0xfed4, 0xf115, NULL }

void Update_A( void* oldA, void* newA ) { // newA->z = 1000; }

V2 → Current

V

Current

Page 55: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Snapshots

Manual

v0 v1 v2 Now

What Have We Gained?

55

if version<1 if version<2

if version<1

if version<3

if version<2

if version<1

v0 v1 v2 Now

A { int x; int y; }

B { A* a; }

A { int y; }

B { A* a; int x;}

{ “A”, 0x1234, 0x8989, NULL }

{ “B”, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) {

// newB->x = oldB->a->x; }

A { int x; int y; }

B { A* a; }

A { int y; }

B { A* a; int x;}

{ “A”, 0x1234, 0x8989, NULL }

{ “B”, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) {

// newB->x = oldB->a->x; }

A { int x; int y; }

B { A* a; }

A { int y; }

B { A* a; int x;}

{ “A”, 0x1234, 0x8989, NULL }

{ “B”, 0xabcd, 0xfed4, Update_B }

void Update_B( void* oldB, void* newB ) {

// newB->x = oldB->a->x; }

V

Page 56: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Finer Grained Changes

We hired artists

Full snapshots too heavy

New products, lots of branches

• No global timeline

56 V

Page 57: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Patching (1)

B0→B1

B.add(int,“y”)

57

A0 { int x; }

B1 { A* a; int y; }

A1 { int x; int y; }

B2 { A* a; }

A0 { int x; }

B0 { A* a; }

V

Page 58: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Patching (2)

B0→B1

B.add(int,“y”)

58

A0 { int x; }

B1 { A* a; int y; }

A1 { int x; int y; }

B2 { A* a; }

A0 { int x; }

B0 { A* a; }

A0→A1

B1→B2

A.add(int,“y”)

A.y = B.y

B.rem(“y”)

V

Page 59: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Sample Patch

// This block is generated by the metadata comparison.

// It is pasted into the source and expands out to a few constant structures.

PATCH_BEGIN("hkbLookAtModifier", 2, "hkbLookAtModifier", 3)

// Require hkbEventBase version 3 before running this patch

PATCH_DEPENDS("hkbEventBase", 3)

PATCH_MEMBER_ADDED_VEC_4("neckForwardLS", 0.0f,1.0f,0.0f,0.0f)

// user edit: add & remove changed to rename

PATCH_MEMBER_RENAMED("headForwardHS", "headForwardLS")

// user edit: call a C function here

PATCH_FUNCTION( hkbLookAtModifier_2_to_3 )

PATCH_END()

59 V

Page 60: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Verification

60 V

Previous Reflection

Apply Patches

Patched Reflection

Compare against current

reflection

PatchesOK

PatchesWrong/Missing

Page 61: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Versioning Workflow

Make your changes

Run a reflection diff utility

Copy & paste the suggested patch

Edit (add&remove → rename)

Write version function if necessary

Done

61 V

Page 62: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Versioning Conclusion

Massively reduced tax

Quick to add versioning

Cheap to test

Non-prescriptive workflow

62

Page 63: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Data, Not Functions

Generating code?

Stop, generate data

Functions:

big and single purpose.

Data:

small and multi-purpose

63

Page 64: 개발 과정 최적화 하기 내부툴로 더욱 강력한 개발하기 Stephen kennedy _(11시40분_103호)

Conclusion

64

Serialization Memory Reflection

Binding Versioning

github:clang-extract