[Go] Go력 1년의 현재, fmt.Prin을 들여다보기 ()

17928 단어 Gotech
이 기사는 Go Advent Calendar 2021 4일차 기사입니다.
https://qiita.com/advent-calendar/2021/go

개시하다


고 쓰기 시작한 지 1년이 지났다.
나는 처음 쓴 날fmt.Println("Hello World")이 그립다.Hello World와 프린트할 일은 거의 없지만 fmt.Println 항상 당신의 보살핌을 받았습니다.
모처럼의 기회인 만큼 이 기회를 틈타 천천히 fmt.Println 설치를 살펴보고 싶다.
fmt.Println("Hello World")

Println


func Println(a ...interface{}) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=273
매개 변수 형식은 임의의 인터페이스 {}이고, 반환 값의 형식은 int와 error입니다.
잘 되돌아오는 값이 있겠지.현재 반환 값을 변수에 대입하지 않았습니다.
함수의 내용은 한 줄만 있고 호출 중입니다 Fprintln.
읽기Println는 읽기Fprintln와 같다는 것이다.Fprintln의 첫 번째 매개 변수에 전달os.Stdout.os.Stdoutos 가방에 성명된 변수입니다.
var (
	Stdin  = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
	Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
	Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/os/file.go;l=65 Println의 시작은 os.Stdout에 대입된 *os.File 유형의 값이 가지고 있는 Write 방법에 따라 진행되었다.

Fprintln


func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
	p := newPrinter()
	p.doPrintln(a)
	n, err = w.Write(p.buf)
	p.free()
	return
}
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=262
어디 보자Fprintln.*pp 프린터 상태를 관리하는 구조체를 초기화하거나 호출 방법을 사용하거나 버퍼 메모리를 작성합니다.Write의 반환값으로 인해 PrintlnFprintln의 반환값은 Write의 반환값이다.
솔직히 여기까지는 만족하지만, 좀 더 깊이 들어가고 싶어요.

newPrinter


func newPrinter() *pp {
	p := ppFree.Get().(*pp)
	p.panicking = false
	p.erroring = false
	p.wrapErrs = false
	p.fmt.init(&p.buf)
	return p
}
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=136
초기화 함수newPrinter를 살펴보자.ppFree.Get().(*pp)에서 제작*pp형의 값을 만들고 초기 값을 제작 값의 필드에 삽입한다.
필드의 대입은 물론이고 신경을 많이 썼다ppFree.Get().(*pp).
익숙하지 않은 함수라고 하고 반환값을 *pp형으로 나눈다.
이 보기 싫은 함수는 도대체 누구일까.

ppFree


var ppFree = sync.Pool{
	New: func() interface{} { return new(pp) },
}
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=131
눈에 거슬리는 함수Get는 구조체의 방법이다.
필드sync.Pool에 초기화 후New가 구조로 돌아가는 함수를 넣었기 때문에 *pp 구조sync.Pool 방법에서Get라고 할 수 있다.New뭐야.

sync.Pool


type Pool struct {
	noCopy noCopy
	local     unsafe.Pointer // local fixed-size per-P pool, actual type is [P]poolLocal
	localSize uintptr        // size of the local array
	victim     unsafe.Pointer // local from previous cycle
	victimSize uintptr        // size of victims array
	// New optionally specifies a function to generate
	// a value when Get would otherwise return nil.
	// It may not be changed concurrently with calls to Get.
	New func() interface{}
}
https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/sync/pool.go;l=44
https://pkg.go.dev/sync#Pool
댓글이 많아서 열심히 읽었는데 잘 모르겠어요.
나는 축적된 오라리의 책을 읽고 기술 보도도 읽었지만 부드러웠다.
어쨌든 현재로는...
  • 대상(유형은sync.Pool이기 때문에 구조나 절편 등을 효과적으로 처리하는 연못
  • interface{}에서 수영장에서 대상을 얻다
  • 풀에 대기 중인 객체가 있으면 반환하고 그렇지 않으면 반환Get
  • New 객체를 풀로 반환
  • PutGet는 골프장 안전
  • 이다Put 구조체를 반복해서 사용할 수 있습니다!재활용으로 확보한 pp입니다!그런가요?[]byte의 일은 좀 더 잘 조사해야 하지만 분위기만 이해하면 계속 읽을 수 있다.
    한마디로 수영장 취득 대상sync.Pool에서 발견Get된 호칭이다.

    free


    func (p *pp) free() {
    	// Proper usage of a sync.Pool requires each entry to have approximately
    	// the same memory cost. To obtain this property when the stored type
    	// contains a variably-sized buffer, we add a hard limit on the maximum buffer
    	// to place back in the pool.
    	//
    	// See https://golang.org/issue/23199
    	if cap(p.buf) > 64<<10 {
    		return
    	}
    	p.buf = p.buf[:0]
    	p.arg = nil
    	p.value = reflect.Value{}
    	p.wrappedErr = nil
    	ppFree.Put(p)
    }
    
    https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=146
    수영장New으로 반환되며Put구조체의 방법pp내에서 불린다.
    큰 거free면 수영장에 안 갈 것 같아.

    doPrintln


    func (p *pp) doPrintln(a []interface{}) {
    	for argNum, arg := range a {
    		if argNum > 0 {
    			p.buf.writeByte(' ')
    		}
    		p.printArg(arg, 'v')
    	}
    	p.buf.writeByte('\n')
    }
    
    https://cs.opensource.google/go/go/+/refs/tags/go1.17.3:src/fmt/print.go;l=1164
    그럼 마지막으로 보시죠[]byte.
    구조체의 방법doPrintln을 호출하여 pp의 매개 변수로 임의의 값을 주고 버퍼에 순서대로 기록합니다.
    형식 지정 하위 항목은 항상 지정됩니다printArg.
    또한 ln이기 때문에 매개 변수의 마지막 요소를 쓴 후에 쓴다Println.

    총결산

    %v\n에서 처리된 내용을 간략하게 요약하면
    수영장fmt.Println에서 구조체를 얻다(수영장에 대기 중fmt.Fprintln이 없으면 새로 만든 구조체로 돌아간다)

    출력할 값을 버퍼에 쓰기

    쓰기 버퍼 값을 출력 위치에 쓰기

    패브릭을 풀로 반환pp이런 흐름.
    특별히 해결하고 싶은 목적은 없고, 표준 포장의 원본 코드를 읽는 것은 오락이다.
    설치가 잘 안 돼서 읽을 때 디테일한 부분을 쉽게 읽을 수 있지만 이 코드들을 따라잡았으면 좋겠어요.
    또 알다pp가 첫 수확이었다.
    표준 포장의 코드링은 기술과 흥미를 높이는 동시에 계속하고 싶습니다.
    읽어주셔서 감사합니다.

    참고 자료


    https://www.oreilly.co.jp/books/9784873118468/
    https://dokupe.hatenablog.com/entry/20190501/1556686106
    https://tanksuzuki.com/entries/golang-sync-pool/

    좋은 웹페이지 즐겨찾기