[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.Stdout는 os 가방에 성명된 변수입니다.
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의 반환값으로 인해 Println과 Fprintln의 반환값은 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 객체를 풀로 반환
  • Put와 Get는 골프장 안전
  • 이다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/

    좋은 웹페이지 즐겨찾기