web-dev-qa-db-ja.com

パイプから読み取ったGolangは大量のデータを読み取ります

ストリーミングされているアーカイブをstdinに読み込もうとしていますが、パイプ内のtarが送信するよりも多くのデータfarを読んでいます。

次のようにコマンドを実行します。

tar -cf - somefolder | ./my-go-binary

ソースコードは次のとおりです:

package main

import (
    "bufio"
    "io"
    "log"
    "os"
)

// Read from standard input
func main() {
    reader := bufio.NewReader(os.Stdin)
    // Read all data from stdin, processing subsequent reads as chunks.
    parts := 0
    for {
        parts++
        data := make([]byte, 4<<20) // Read 4MB at a time
        _, err := reader.Read(data)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Problems reading from input: %s", err)
        }
    }
    log.Printf("Total parts processed: %d\n", parts)
}

100MBのタール付きフォルダーの場合、4MBの1468チャンク(つまり6.15GB)を取得しています!さらに、data []byte配列がどれほど大きいかは問題ではないようです。チャンクサイズを40MBに設定した場合でも、40MBのデータの1400チャンクを取得しますが、これはまったく意味がありません。

Goでos.Stdinからデータを正しく読み取るために必要なことはありますか?

17
atp

あなたのコードは非効率的です。ループ全体でdataを割り当てて初期化しています。

_for {
    data := make([]byte, 4<<20) // Read 4MB at a time
}
_

_io.Reader_としてのreaderのコードが間違っています。たとえば、_, err := reader.Read(data)によって読み取られたバイト数を無視し、errエラーを適切に処理しません。

パッケージio

_import "io" 
_

タイプリーダー

_type Reader interface {
        Read(p []byte) (n int, err error)
}
_

Readerは、基本的なReadメソッドをラップするインターフェースです。

読み取りは、最大len(p)バイトをpに読み取ります。読み取られたバイト数(0 <= n <= len(p))と発生したエラーを返します。 Readがn <len(p)を返す場合でも、呼び出し中にpのすべてをスクラッチスペースとして使用する場合があります。一部のデータは利用可能であるがlen(p)バイトは利用できない場合、Readは通常、それ以上待たずに利用可能なデータを返します。

N> 0バイトを正常に読み取った後、Readがエラーまたはファイルの終わり条件を検出すると、読み取ったバイト数を返します。同じ呼び出しから(非nil)エラーを返すか、後続の呼び出しからエラー(およびn == 0)を返します。この一般的なケースの例は、入力ストリームの最後にゼロ以外のバイト数を返すリーダーがerr == EOFまたはerr == nilのいずれかを返す場合があることです。次の読み取りEOFに関係なく、0を返す必要があります。

呼び出し元は、エラーerrを考慮する前に、返されるn> 0バイトを常に処理する必要があります。そうすることで、いくつかのバイトを読み取った後に発生するI/Oエラーと、許可されているEOF動作の両方を処理します。

Readの実装では、len(p)== 0の場合を除いて、nilエラーでゼロバイトカウントを返すことはお勧めしません。特に、EOFを示すものではありません。

実装はpを保持してはなりません。

以下は、_io.Reader_インターフェースに準拠したモデルファイル読み取りプログラムです。

_package main

import (
    "bufio"
    "io"
    "log"
    "os"
)

func main() {
    nBytes, nChunks := int64(0), int64(0)
    r := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 4*1024)
    for {
        n, err := r.Read(buf[:cap(buf)])
        buf = buf[:n]
        if n == 0 {
            if err == nil {
                continue
            }
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
        nChunks++
        nBytes += int64(len(buf))
        // process buf
        if err != nil && err != io.EOF {
            log.Fatal(err)
        }
    }
    log.Println("Bytes:", nBytes, "Chunks:", nChunks)
}
_

出力:

 2014/11/29 10:00:05バイト:5589891チャンク:1365 
33
peterSO

Readのドキュメントを読みます:

Readはデータをpに読み込みます。 pに読み込まれたバイト数を返します。基礎となるReaderでReadを最大で1回呼び出すため、nはlen(p)よりも小さい場合があります。 EOFでは、カウントはゼロになり、errはio.EOFになります。

一度に4MBを読み取っていません。バッファースペースを提供し、Readが実際に読み取った量を示す整数を破棄しています。バッファスペースが最大ですが、少なくとも私のシステムでは、ほとんどの場合128kが呼び出しごとに読み取られるようです。自分で試してみてください:

// Read from standard input
func main() {
    reader := bufio.NewReader(os.Stdin)
    // Read all data from stdin, passing the data as parts into the channel
    // for processing.
    parts := 0
    for {
        parts++
        data := make([]byte, 4<<20) // Read 4MB at a time
        amount , err := reader.Read(data)
        // WILL NOT BE 4MB!
        log.Printf("Read: %v\n", amount)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Problems reading from input: %s", err)
        }
    }
    log.Printf("Total parts processed: %d\n", parts)
}

さまざまな読み取り量を処理するロジックを実装する必要があります。

5
user918176