Go 안의 Go

고퍼 페스티벌

2015년 5월 26일

롭 파이크

구글

키보드 오른쪽, 혹은 마우스로 우측 가장자리 클릭^^

Go in Go

Gopherfest

26 May 2015

Rob Pike

Google

시작하며

음.. 몇일 후에 GO 1.5 에 대해 이야기하는 자리에 가게 되어서..

개발끈이 짧다보니.. 뭐라도 준비해갈까..해서 그냥 눈에보이는 걸

급하게 번역해봤습니다. 구글번역기 정도의 번역입니다 ;;


화면 우측 상단에 번역 버튼을 올리오니, 헷갈리실때는 영어를 직접 보시고

잘못된 것이 있으면 연락주시면 바로 고쳐보겠습니다. 감사합니다.

시작하며

음.. 몇일 후에 GO 1.5 에 대해 이야기하는 자리에 가게 되어서..

개발끈이 짧다보니.. 뭐라도 준비해갈까..해서 그냥 눈에보이는 걸

급하게 번역해봤습니다. 구글번역기 정도의 번역입니다 ;;


화면 우측 상단에 번역 버튼을 올리오니, 헷갈리실때는 영어를 직접 보시고

잘못된 것이 있으면 연락주시면 바로 고쳐보겠습니다. 감사합니다.

Go 안의 Go

GO의 1.5 버젼을 맞이하여 , 이제 전체적 시스템은 Go로 쓰여졌습니다.
(그리고 조금의 어셈블러 ^^)

C 는 가버렸습니다.

Side note: gccgo 는 여전히 존재하며
이 talk는 기본 컴파일러, gc에 대해 이야기합니다.

Go in Go

As of the 1.5 release of Go, the entire system is now written in Go.
(And a little assembler.)

C is gone.

Side note: gccgo is still going strong.
This talk is about the original compiler, gc.

왜 C로 쓰여졌었냐구요?

Bootstrapping을 위해서죠. (컴퓨터 용어라.. 뭐.. )

(또한 Go는 처음에는 컴파일러 구현 언어로 의도되지 않았습니다. )

Why was it in C?

Bootstrapping.

(Also Go was not intended primarily as a compiler implementation language.)

왜 Go컴파일러로 갔을까요?

입증(validation)을 위한 게 아닌 좀더 실용적인 이유였습니다 :

  • GO는 (정확히) C보다 쓰기 쉽습니다.
  • (디버거의 부재에도 불구하고) C보다 디버깅하기 쉽습니다.
  • Go 는 당신이 알아야할 유일한 언어입니다. 더 많은 지원(contributions)을 받기 위해
  • Go 의 더 나은 모듈성, 도구들, 테스트, 프로파일링, ...
  • Go 는 병렬 프로그래밍 실행을 쉽게 합니다.

다음과 같은 잇점을 봤을 때 , 아직 이르긴 합니다.

디자인 문서: golang.org/s/go13compiler

Why move the compiler to Go?

Not for validation; we have more pragmatic motives:

  • Go is easier to write (correctly) than C.
  • Go is easier to debug than C (even absent a debugger).
  • Go is the only language you'd need to know; encourages contributions.
  • Go has better modularity, tooling, testing, profiling, ...
  • Go makes parallel execution trivial.

Already seeing benefits, and it's early yet.

Design document: golang.org/s/go13compiler

왜 런타임이 Go로 갔나요?(?)

우리는 단지 런타임 컴파일을 하는 우리의 C컴파일러를 가지고 있었습니다.
우리는 Go로된 단편스택같은, 같은 방식의 ABI를 가진 컴파일러를 원했었습니다. (헷갈림)

Go로의 전환은 C컴파일러를 제거한다는 것을 의미합니다.
이것은 컴파일러를 GO로 전환하는 것보다 더 중요합니다 .

( 컴파일러 전환의 모든 이유가 runtime에도 적용이 되었습니다.)

오직 하나의 언어가 runtime에 있고, 통합과 스택관리가 쉬워졌습니다.

언제나, 단순성이 가장 고려되었습니다.

Why move the runtime to Go?

We had our own C compiler just to compile the runtime.
We needed a compiler with the same ABI as Go, such as segmented stacks.

Switching it to Go means we can get rid of the C compiler.
That's more important than converting the compiler to Go.

(All the reasons for moving the compiler apply to the runtime as well.)

Now only one language in the runtime; easier integration, stack management, etc.

As always, simplicity is the overriding consideration.

역사

왜 우리는 우리의 도구를 가지고 있을까요?
우리들의 ABI?
우리들의 파일형식?

역사, 친숙함, 나아가기 쉬움. 그리고 속도

GCC, LLVM과 함께 많은 Go의 큰 변화는 더 힘이 들 것입니다.

History

Why do we have our own tool chain at all?
Our own ABI?
Our own file formats?

History, familiarity, and ease of moving forward. And speed.

Many of Go's big changes would be much harder with GCC or LLVM.

큰 변화들

도구를 가지고, Go로 가서 쉬워진 모든 것들 :

  • 링커 리아키텍쳐
  • 새로운 가비지 콜렉터
  • 스택 맵들
  • 지속적인 스택
  • write barriers (쓰기배리어??)

마지막 세가지들은 C에서 불가능한 것입니다. :

  • C는 타입 세이프하지 않습니다. 무엇이 포인터인지 언제나 알지 않습니다.
  • 최적화에 의해 스택 슬롯이 재명명될 수 있습니다.

(Gccgo 는 아직 단편된 스택, 부정확한(스택) 콜렉션을 한동안 가집니다.)

Big changes

All made easier by owning the tools and/or moving to Go:

  • linker rearchitecture
  • new garbage collector
  • stack maps
  • contiguous stacks
  • write barriers

The last three are all but impossible in C:

  • C is not type safe; don't always know what's a pointer
  • aliasing of stack slots caused by optimization

(Gccgo will have segmented stacks and imprecise (stack) collection for a while yet.)

고루틴 스택

  • 1.2 까지: 스택들이 단편화되었습니다.
  • 1.3: 스택들이 근접합니다. C코드를 런타임에서 실행하지 않는한..
  • 1.4: 시스템 스택에 대한 제한된 C로 인해 스택들이 근접되었습니다.
  • 1.5: C를 제거함으로써 스택들이 근접됨

이것들은 각각 큰 발걸음이었고, 빠르게 만들어져갔습니다.(led by khr@).

Goroutine stacks

  • Until 1.2: Stacks were segmented.
  • 1.3: Stacks were contiguous unless executing C code (runtime).
  • 1.4: Stacks made contiguous by restricting C to system stack.
  • 1.5: Stacks made contiguous by eliminating C.

These were each huge steps, made quickly (led by khr@).

런타임 전환

기계의 도움과 수작업으로 많은 것들이 이뤄졌습니다.

안전한 언어에서 런타임을 구현하는 것은 도전이었습니다.
예를 들어서 GC에서 포인터들을 raw bits로 다루기 위해 unsafe 가 사용된 경우가 있긴 합니다.
그러나 당신이 생각하는 것만큼은 많지 않습니다.

다음 세션의 어떤 번역자(여기선 C에서 Go로의 전환을 도와준 사람을 일컫는 듯?:역주) 가 translation(전환)을 많이 도와줬습니다.

Converting the runtime

Mostly done by hand with machine assistance.

Challenge to implement the runtime in a safe language.
Some use of unsafe to deal with pointers as raw bits in the GC, for instance.
But less than you might think.

The translator (next sections) helped for some of the translation.

컴파일러 전환하기

왜 처음부터 시작하지않고, 전환을 했냐구요? 정확함과 테스트를 위해서였습니다.


단계:

  • C에서 Go로의 커스텀 번역기를 쓰세요
  • translator를 돌리고, 성공할때까지 반복(iterate)하세요
  • bit-동일한 결과로 성공을 측정하세요
  • 수작업과 기계작업으로 코드를 정리(clean up)하세요
  • C안의 GO속에서 자연스러운 Go로 옮겨갑니다. (아직도 하는 중)

Converting the compiler

Why translate it, not write it from scratch? Correctness, testing.

Steps:

  • Write a custom translator from C to Go.
  • Run the translator, iterate until success.
  • Measure success by bit-identical output.
  • Clean up the code by hand and by machine.
  • Turn it from C-in-Go to idiomatic Go (still happening).

변환기

첫번째 출력은 (안좋게) GO로 변환되는 한라인씩의 C 였습니다.
이것을 위한 도구는 rsc@로 쓰여졌습니다. (GopherCon 2014 에서 얘기했었죠)
이 일을 위해서 일반적인 C-go 변환기가 아니라 커스터마이징해서 썻습니다.

단계:

  • 새로운 간단한 C 파서를 사용해서 C코드를 파싱하였고 (yacc)
  • 표현식으로*p++ 같은 C 비슷한 것들은 지우거나 다시 쓰여졌습니다.
  • C 파싱 트리를 돌면서(Walk), C코드를 Go문법으로 출력합니다.
  • 출력을 컴파일합니다.
  • 실행하고 생성된 코드를 비교합니다.
  • 반복합니다.

Yacc 문법은 sam-powered hands으로 변환되었습니다.

Translator

First output was C line-by-line translated to (bad!) Go.
Tool to do this written by rsc@ (talked about at GopherCon 2014).
Custom written for this job, not a general C-to-Go translator.

Steps:

  • Parse C code using new simple C parser (yacc)
  • Remove or rewrite C-isms such as *p++ as an expression
  • Walk the C parse tree, print the C code in Go syntax
  • Compile the output
  • Run, compare generated code
  • Repeat

The Yacc grammar was translated by sam-powered hands.

변환설정

손으로 다시쓰여진 다음과 같은 룰에 의하여 도움을 받습니다. :

  • 이 필드는 bool 이다.
  • 이 함수는 bool 을 반환한다.

표준 라이브러리를 사용하는 것과 같은 것들을 위해 diff같은 것들이 다시 쓰여졌습니다:

diff {
-    g.Rpo = obj.Calloc(g.Num*sizeof(g.Rpo[0]), 1).([]*Flow)
-    idom = obj.Calloc(g.Num*sizeof(idom[0]), 1).([]int32)
-    if g.Rpo == nil || idom == nil {
-        Fatal("out of memory")
-    }
+    g.Rpo = make([]*Flow, g.Num)
+    idom = make([]int32, g.Num)
}

Translator configuration

Aided by hand-written rewrite rules, such as:

  • this field is a bool
  • this function returns a bool

Also diff-like rewrites for things such as using the standard library:

diff {
-    g.Rpo = obj.Calloc(g.Num*sizeof(g.Rpo[0]), 1).([]*Flow)
-    idom = obj.Calloc(g.Num*sizeof(idom[0]), 1).([]int32)
-    if g.Rpo == nil || idom == nil {
-        Fatal("out of memory")
-    }
+    g.Rpo = make([]*Flow, g.Num)
+    idom = make([]int32, g.Num)
}

다른 예제

언어간의 다른 시멘틱한 차이때문에 발생합니다.

diff {
-    if nreg == 64 {
-        mask = ^0 // can't rely on C to shift by 64
-    } else {
-        mask = (1 << uint(nreg)) - 1
-    }
+    mask = (1 << uint(nreg)) - 1
}

Another example

This one due to semantic difference between the languages.

diff {
-    if nreg == 64 {
-        mask = ^0 // can't rely on C to shift by 64
-    } else {
-        mask = (1 << uint(nreg)) - 1
-    }
+    mask = (1 << uint(nreg)) - 1
}

grind

Go 에서는, 새로운 도구 grind 가 개발되었습니다. (by rsc@):

  • Go를 파싱하고, 타입체크하고
  • 실행하려고 수정한 것들의 리스트를 기록하고, 이 위치에 텍스트를 넣고
  • 끝에서는 수정한 것들을 소스에 적용하고요. (AST를 수정하는 것은 어려웠습니다)

프로파일링과 다른 분석들에 의한 변화들:

  • 죽은 코드 지우기
  • gotos 지우기
  • 사용하지 않는 라벨들 지우고, 필요하지 않는것들 등등 지우기
  • var 선언을 처음 사용 근처로 두기

Grind

Once in Go, new tool grind deployed (by rsc@):

  • parses Go, type checks
  • records a list of edits to perform: "insert this text at this position"
  • at end, applies edits to source (hard to edit AST).

Changes guided by profiling and other analysis:

  • removes dead code
  • removes gotos
  • removes unused labels, needless indirections, etc.
  • moves var declarations nearer to first use

성능 문제들

변환기에 의한 출력은 보잘것없는 Go 였습니다. 그리고 10배 느리게 동작했엇습니다.
많은 속도지연이 복구되었습니다.

C 에서 GO로 가면서 나왔던 문제들 :

  • C패턴이 안 좋은 GO코드가 될 수 있었음. 복잡한 for 반복문들
  • C 스택변수가 escape하질 않음. Go 컴파일러도 확신하지 못함.
  • fmt.Stringer vs C의 varargs
  • Go 에서 unions는 없고 structs 로 대신함 :부풀어짐
  • 안 좋은 위치에있는 변수 선언들

씨 컴파일러는 많은 메모리를 자유롭게 하지 않지만(해제?), Go는 GC를 가집니다.
CPU와 메모리에 오버헤드를 증가시킵니다.

Performance problems

Output from translator was poor Go, and ran about 10X slower.
Most of that slowdown has been recovered.

Problems with C to Go:

  • C patterns can be poor Go; e.g.: complex for loops
  • C stack variables never escape; Go compiler isn't as sure
  • interfaces such as fmt.Stringer vs. C's varargs
  • no unions in Go, so use structs instead: bloat
  • variable declarations in wrong place

C compiler didn't free much memory, but Go has a GC.
Adds CPU and memory overhead.

성능 fix

프로파일! (이전엔 안했음!)

  • vars 를 처음 사용에 가깝게 함
  • vars 를 복합적으로 쪼갬(split)
  • 컴파일러의 코드들을 라이브러리의 코드들로 대체함. 예를 들자면 math/big
  • struct필드들을 결합하기 위한 인터페이스나 다른 비법들
  • 더 나은 escape 분석 (drchase@).
  • 손으로 튜닝한 코드와 데이터 레이아웃

대부분의 성능fix같은 문제들을 위해 grind, gofmt -r and eg와 같은 툴을 이용하세요.

디버깅 프린트 라이브러리에서 인터페이스 아규먼트를 지우는 것은 전반적으로 15% 를 얻습니다! !

더 많이 해야할 것이 많이 남았습니다.

Performance fixes

Profile! (Never done before!)

  • move vars closer to first use
  • split vars into multiple
  • replace code in the compiler with code in the library: e.g. math/big
  • use interface or other tricks to combine struct fields
  • better escape analysis (drchase@).
  • hand tuning code and data layout

Use tools like grind, gofmt -r and eg for much of this.

Removing interface argument from a debugging print library got 15% overall!

More remains to be done.

기술적 잇점

변환의 다른 잇점들:

GC는 더 이상 늘어진?, 매달린(dangling) 포인터가 생기는 것에 대해 걱정할 필요가 없다는 것을 말합니다.

백엔드부분이 정리될 기회입니다.

툴 체인 도처에 통합된 386 and amd64 아키텍쳐들

새로운 아키텍쳐가 추가되기 쉬워졌습니다.

통합된 도구들 : 이제는 하나의 컴파일러, 하나의 어셈블러, 하나의 링커.

Technical benefits

Other benefits of the conversion:

Garbage collection means no more worry about introducing a dangling pointer.

Chance to clean up the back ends.

Unified 386 and amd64 architectures throughout the tool chain.

New architectures are easier to add.

Unified the tools: now one compiler, one assembler, one linker.

컴파일러

GOOS=YYY GOARCH=XXX go tool compile

하나의 컴파일러; 더이상은 6g, 8g 등등 없습니다 .

5만줄의 포터블한 코드에 관해서.
registerizer가 지금 포터블하지만, 아키텍쳐들은 잘 특징지어졌습니다.
포터블하지 않은 것들 : Peepholing, 사양서에 묶여진 레지스터들.
일반적으로 포터블한 LOC 의 10% 정도..

Compiler

GOOS=YYY GOARCH=XXX go tool compile

One compiler; no more 6g, 8g etc.

About 50K lines of portable code.
Even the registerizer is portable now; architectures well characterized.
Non-portable: Peepholing, details like registers bound to instructions.
Typically around 10% of the portable LOC.

어셈블러

GOOS=YYY GOARCH=XXX go tool asm

새로운 어셈블러, Go에 모두 있고, 처음부터 r@로 쓰여짐 .
깨끗하고 자연스러운 GO 코드

4000라인 이하, 10%의 기계의존

이전의 yacc과 C 어셈블러와 완벽하게 호환가능

어떻게 이게 가능할까요?

  • Plan 9 어셈블러에서 비롯된 공유된 문법.
  • 통합된 백엔드 로직 (오래된 liblink, 지금은 internal/obj)

Assembler

GOOS=YYY GOARCH=XXX go tool asm

New assembler, all in Go, written from scratch by r@.
Clean, idiomatic Go code.

Less than 4000 lines, <10% machine-dependent.

Almost completely compatible with previous yacc and C assemblers.

How is this possible?

  • shared syntax originating in the Plan 9 assemblers
  • unified back-end logic (old liblink, now internal/obj)

링커

GOOS=YYY GOARCH=XXX go tool link

대부분 C코드에서 수작업-변환, 머신- 변환

오리지널 링커의 일부인 새로운 라이브러리, internal/obj는 기계에 관한 세부사항을 캡쳐하고 오브젝트 파일을 씁니다.

4개의 아키텍쳐들에서 2만 7천 라인 , 대부분 테이블 (과 약간의 안 좋은 것들 )

  • arm: 4000
  • arm64: 6000
  • ppc64: 5000
  • x86: 7500 (386 and amd64)

Example benefit: one print routine to print any instruction for any architecture.

Linker

GOOS=YYY GOARCH=XXX go tool link

Mostly hand- and machine- translated from C code.

New library, internal/obj, part of original linker, captures details about machines, writes object files.

27000 lines summed across 4 architectures, mostly tables (plus some ugliness).

  • arm: 4000
  • arm64: 6000
  • ppc64: 5000
  • x86: 7500 (386 and amd64)

Example benefit: one print routine to print any instruction for any architecture.

부트스트랩

C 컴파일러가 아닌, 부트스트래핑하는데 GO 컴파일러가 필요

그러므로 소스로부터 1.5를 빌드하기 위해서는 작동하는 GO를 다운로드하거나 빌드하세요. (build 가 두번이나 들어가서..대충 번역합니다;)

우리는 Go 1.4 버젼을 Go 1.5+툴체인을 빌드하기 위한 베이스로 여깁니다. (그 이상버젼도 좋습니다. )

세부사항: golang.org/s/go15bootstrap

Bootstrap

With no C compiler, bootstrapping requires a Go compiler.

Therefore need to build or download a working Go installation to build 1.5 from source.

We use Go 1.4+ as the base to build the 1.5+ tool chain. (Newer is OK too.)

Details: golang.org/s/go15bootstrap

미래에는

많은 작업을 여전히 하고 있는 중이구요. 1.5 는 거의 완료되었습니다.

미래의 작업:

더 나은 escape 분석
SSA를 사용하는 새로운 컴파일러 백엔드 (C보다 훨씬 쉬운 Go쪽으로)
더 나은 최적화를 가능케 할 것입니다.

PDF(나 xml)로 부터 기계 설명을 생성해낼 것입니다.
순수히 기계-생성된 명령 정의를 가질 것입니다 : :
"PDF를 읽고 어셈블러 설정을 써낼 것입니다. ".
이미 역어셈블러들을 위해 deploy되었습니다.

Future

Much work still to do, but 1.5 is mostly set.

Future work:

Better escape analysis.
New compiler back end using SSA (much easier in Go than C).
Will allow much more optimization.

Generate machine descriptions from PDFs (or maybe XML).
Will have a purely machine-generated instruction definition:
"Read in PDF, write out an assembler configuration".
Already deployed for the disassemblers.

결론

프로젝트에서 C를 제거하는 것은 큰 진척사항입니다.
코드가 좀더 깔끔해지고 테스트가능해지고 프로파일가능해지고 작업하기 쉬워졌습니다.

새로운 통합된 툴체인이 코드의 양을 줄여주고, 유지보수성을 증가시켜줍니다.

유연한 툴체인, 휴대성은 여전히 중요하게 여겨집니다 .

Conclusions

Getting rid of C was a huge advance for the project.
Code is cleaner, testable, profilable, easier to work on.

New unified tool chain reduces code size, increases maintainability.

Flexible tool chain, portability still paramount.

Thank you

Rob Pike

Google


번역자 : 아라한사 fb.com/me.adunhansa