io.Copy 조사

46342 단어 golangcopytech

개요

  • https://zenn.dev/ichigo_dev/articles/2ff2ae3ce02e2a5f15da
  • 나를 불렀는데, 이렇게 말하면 IO다.Copy 를 제대로 보지 못했습니다
  • 제가 찾아봤어요
  • https://pkg.go.dev/io#Copy
  • 매개 변수로 writer와reader
  • 를 수신합니다
  • 오류가 발생할 때까지 EOF에 복사하거나 write에 복사하기
  • Copy copies from src to dst until either EOF is reached on src or an error occurs. It returns the number of bytes copied and the first error encountered while copying, if any.
  • 마지막 주의 사항
  • src에 WriteTo 인터페이스가 설치되어 있는 경우 사용하지 않음
  • dst에 ReadFrom 인터페이스가 설치된 경우 이 인터페이스가 호출되었다고 적혀 있음
  • 특수 대응이기 때문에 이쪽은 더 빠르고 가볍게 설치할 수 있습니다
  • If src implements the WriterTo interface, the copy is implemented by calling src.WriteTo(dst). Otherwise, if dst implements the ReaderFrom interface, the copy is implemented by calling dst.ReadFrom(src).

    정의


    func Copy(dst Writer, src Reader) (written int64, err error)
    
    type Writer interface {
    	Write(p []byte) (n int, err error)
    }
    
    type Reader interface {
    	Read(p []byte) (n int, err error)
    }
    

    예제

  • 본가의 사이트를 예로 들었다
  • package main
    
    import (
    	"io"
    	"log"
    	"os"
    	"strings"
    )
    
    func main() {
    	r := strings.NewReader("some io.Reader stream to be read\n")
    
    	if _, err := io.Copy(os.Stdout, r); err != nil {
    		log.Fatal(err)
    	}
    
    }
    

    기본 프로세스

  • WriterTo, Read, erFrom, Limited Reader 등을 실현하지 못한 간단한 절차를 쫓는다
  • 32kb의 버퍼를 통해 src에서 dst로 데이터를 쓰기
  • io.Copy

  • 내부 호출 copyBuffer
  • func Copy(dst Writer, src Reader) (written int64, err error) {
    	return copyBuffer(dst, src, nil)
    }
    

    copyBuffer

  • io.왜냐하면 Copy의 buffer는 nil
  • 이기 때문이다.
  • 32kb의 버퍼 생성
  • if buf == nil {
    	size := 32 * 1024
    	buf = make([]byte, size)
    }
    
  • src에서 buffer
  • 읽기
  • 읽기가 있는 경우
  • dst에 buffer
  • 쓰기
  • 없으면 EOF 확인 및 종료
  • for {
    	nr, er := src.Read(buf)
    	if nr > 0 {
    		nw, ew := dst.Write(buf[0:nr])
    		if nw < 0 || nr < nw {
    			nw = 0
    			if ew == nil {
    				ew = errInvalidWrite
    			}
    		}
    		written += int64(nw)
    		if ew != nil {
    			err = ew
    			break
    		}
    		if nr != nw {
    			err = ErrShortWrite
    			break
    		}
    	}
    	if er != nil {
    		if er != EOF {
    			err = er
    		}
    		break
    	}
    }
    

    src에서 WriterTo를 설치할 때

  • 의견https://pkg.go.dev/io#WriteString보자마자
  • allocation과copy를 피할 수 있다고 쓰여있기 때문
  • 부하가 일반 절차보다 낮아야 한다
  • //If the reader has a WriteTo method, use it to do the copy.
    //Avoids an allocation and a copy.
  • dst를 매개 변수로 하고 WriteTo를 직접 실행하고 끝냅니다
  • if wt, ok := src.(WriterTo); ok {
    	return wt.WriteTo(dst)
    }
    

    WriterTo interface


    type WriterTo interface {
    	WriteTo(w Writer) (n int64, err error)
    }
    
  • 이 프로그램을 실제로 실시하는 프로그램 라이브러리는 다음과 같다
  •  ag 'WriteTo implements' -l
    bufio/bufio.go
    net/net.go
    net/iprawsock.go
    net/unixsock.go
    net/udpsock.go
    strings/reader.go
    bytes/reader.go
    

    strings.Reader.WriteTo

  • strings.Reader에 WriteTo가 설치되어 있기 때문에 이것
  • 이라고 부릅니다.
  • 내부에 있습니다.WriteString 쓰기 요청
  • https://pkg.go.dev/io#WriteString
  • func (r *Reader) WriteTo(w io.Writer) (n int64, err error) {
    	r.prevRune = -1
    	if r.i >= int64(len(r.s)) {
    		return 0, nil
    	}
    	s := r.s[r.i:]
    	m, err := io.WriteString(w, s)
    	if m > len(s) {
    		panic("strings.Reader.WriteTo: invalid WriteString count")
    	}
    	r.i += int64(m)
    	n = int64(m)
    	if m != len(s) && err == nil {
    		err = io.ErrShortWrite
    	}
    	return
    }
    

    StringWriter Interface

  • io.WriteString이 Writer 측면에 StringWriter를 설치한 경우
  • 그쪽에서 처리
  • 여기 인도된 Writer는 os입니다.파일입니다.
  • https://pkg.go.dev/[email protected]#File
  • os.File은 String Writer 인터페이스를 충족합니다.
  • https://pkg.go.dev/io#StringWriter
  • WriterString 메소드 사용
  • func WriteString(w Writer, s string) (n int, err error) {
    	if sw, ok := w.(StringWriter); ok {
    		return sw.WriteString(s)
    	}
    	return w.Write([]byte(s))
    }
    

    File.WriteString

  • WriteString은 다음과 같이 설치됩니다
  • .
    bytes 슬라이드 unsafe 선포.Pointer 확보
  • unsafeheader.슬라이스에 역할 할당 중
  • https://pkg.go.dev/golang.org/x/sys/internal/unsafeheader#Slice
  • 수신된 문자열로부터 데이터를 직접 수신
  • 값을 데이터 구조에 전달하고byte
  • 로 전환
    func (f *File) WriteString(s string) (n int, err error) {
    	var b []byte
    	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&b))
    	hdr.Data = (*unsafeheader.String)(unsafe.Pointer(&s)).Data
    	hdr.Cap = len(s)
    	hdr.Len = len(s)
    	return f.Write(b)
    }
    

    dst에서 ReaderFrom을 설치할 때

  • 원시 처리가 이쪽으로 들어오지 않는 절차
  • os.File 제공 또는 Reader 준비 필요
  • 이번에는 독특한 Reader Interface를 충족하는 구조체
  • 를 준비했다.
    package main
    
    import (
    	"io"
    	"log"
    	"os"
    )
    
    type myReader struct {
    }
    
    func (r *myReader) Read(p []byte) (n int, err error) {
    	copy(p, []byte("hello,world\n"))
    	return len(p), io.EOF
    }
    
    func main() {
    	r := &myReader{}
    	if _, err := io.Copy(os.Stdout, r); err != nil {
    		log.Fatal(err)
    	}
    }
    
  • 중간에 *File 판정을 통과한 곳
  • 다른 방법을 추가하면 갈 수 있을 것 같아
  • 귀찮아서 파일을 준비해서 열었어요
  • package main
    
    import (
    	"io"
    	"log"
    	"os"
    )
    
    func main() {
    	r, err := os.Open("hoge.txt")
    	if err != nil {
    		panic(err)
    	}
    
    	if _, err := io.Copy(os.Stdout, r); err != nil {
    		log.Fatal(err)
    	}
    }
    
    hello,world
    

    ReaderFrom interface


    type ReaderFrom interface {
    	ReadFrom(r Reader) (n int64, err error)
    }
    
  • 설치된 모듈은 다음과 같다
  •  ag 'ReadFrom implements' -l    
    bufio/bufio.go
    net/tcpsock.go
    net/iprawsock.go
    net/unixsock.go
    net/udpsock.go
    os/file.go
    

    File.ReadFrom

  • File.ReadFrom에서 readFrom
  • 이라고 합니다.
  • 이 근처에서linux라는 파일로 이동 중
  • // ReadFrom implements io.ReaderFrom.
    func (f *File) ReadFrom(r io.Reader) (n int64, err error) {
    	n, handled, e := f.readFrom(r)
    	return n, f.wrapErr("write", e)
    }
    
  • readFrom은pollCopyFileRange라는 함수
  • 라고도 부른다.
    func (f *File) readFrom(r io.Reader) (written int64, handled bool, err error) {
    	written, handled, err = pollCopyFileRange(&f.pfd, &src.pfd, remain)
    	if lr != nil {
    		lr.N -= written
    	}
    	return written, handled, NewSyscallError("copy_file_range", err)
    }
    
    pollCopyFileRange는 poll입니다.CopyFileRange의 실제 상태
    var pollCopyFileRange = poll.CopyFileRange
    

    CopyFileRange

  • atomic.LoadInt32
  • 가 지원되는지 확인
  • 지원되지 않을 경우 커널 버전 확인
  • atomic.StoreInt32를 사용할 수 있다면 이것
  • 을 사용하세요.
  • 현재 시스템은 Kernel 5입니다.15번이니까 이쪽으로 들어가는 거
  • func CopyFileRange(dst, src *FD, remain int64) (written int64, handled bool, err error) {
    	if supported := atomic.LoadInt32(&copyFileRangeSupported); supported == 0 {
    		return 0, false, nil
    	} else if supported == -1 {
    		major, minor := kernelVersion()
    		if major > 5 || (major == 5 && minor >= 3) {
    			atomic.StoreInt32(&copyFileRangeSupported, 1)
    		}
    	}
    	for remain > 0 {
    		max := remain
    		n, err := copyFileRange(dst, src, int(max))
    	}
    	return written, true, nil
    }
    

    copyFileRange

  • Lock 등을 걸고
  • unix.CopyFileRange
  • 호출 중
    func copyFileRange(dst, src *FD, max int) (written int64, err error) {
    	if err := dst.writeLock(); err != nil {
    		return 0, err
    	}
    	defer dst.writeUnlock()
    	if err := src.readLock(); err != nil {
    		return 0, err
    	}
    	defer src.readUnlock()
    	var n int
    	for {
    		n, err = unix.CopyFileRange(src.Sysfd, nil, dst.Sysfd, nil, max, 0)
    		if err != syscall.EINTR {
    			break
    		}
    	}
    	return int64(n), err
    }
    

    unix.CopyFileRange

  • Syscall6를 직접 호출하여 쓰기
  • copyFileRangeTrap은 326
  • 로 정의됨
    func CopyFileRange(rfd int, roff *int64, wfd int, woff *int64, len int, flags int) (n int, err error) {
    	r1, _, errno := syscall.Syscall6(copyFileRangeTrap,
    		uintptr(rfd),
    		uintptr(unsafe.Pointer(roff)),
    		uintptr(wfd),
    		uintptr(unsafe.Pointer(woff)),
    		uintptr(len),
    		uintptr(flags),
    	)
    	n = int(r1)
    	if errno != 0 {
    		err = errno
    	}
    	return
    }
    

    syscall.Syscall6

  • 집필문집
  • runtime.entersycall이라고 하는 것 같아요.
  • 이곳의 상세한 상황은 다음에 다시 보자
  • 이쪽 링크 참조
  • https://zenn.dev/hsaki/books/golang-concurrency/viewer/internalcase
  • https://ascii.jp/elem/000/001/267/1267477/
  • TEXT ·Syscall6(SB),NOSPLIT,$0-80
    	CALL	runtime·entersyscall(SB)
    	MOVQ	a1+8(FP), DI
    	MOVQ	a2+16(FP), SI
    	MOVQ	a3+24(FP), DX
    	MOVQ	a4+32(FP), R10
    	MOVQ	a5+40(FP), R8
    	MOVQ	a6+48(FP), R9
    	MOVQ	trap+0(FP), AX	// syscall entry
    	SYSCALL
    	CMPQ	AX, $0xfffffffffffff001
    	JLS	ok6
    	MOVQ	$-1, r1+56(FP)
    	MOVQ	$0, r2+64(FP)
    	NEGQ	AX
    	MOVQ	AX, err+72(FP)
    	CALL	runtime·exitsyscall(SB)
    	RET
    ok6:
    	MOVQ	AX, r1+56(FP)
    	MOVQ	DX, r2+64(FP)
    	MOVQ	$0, err+72(FP)
    	CALL	runtime·exitsyscall(SB)
    	RET
    

    참고 자료

  • https://itkq.jp/blog/2017/05/10/linux-file-and-io/
  • https://zenn.dev/nobonobo/articles/297dc5cbc554d6
  • 좋은 웹페이지 즐겨찾기