web-dev-qa-db-ja.com

Goでの効率的なCSVの読み取りと書き込み

以下のGoコードは、10,000レコードのCSV(タイムスタンプtimesおよびfloat values)を読み取り、データに対していくつかの操作を実行してから、元の値を追加の列とともに別のCSVに書き込みますscoreの場合。しかし、それはひどく遅い(つまり、数時間ですが、そのほとんどはcalculateStuff())であり、CSVの読み取り/書き込みに対処できる非効率性があるかどうか知りたいです。

package main

import (
  "encoding/csv"
  "log"
  "os"
  "strconv"
)

func ReadCSV(filepath string) ([][]string, error) {
  csvfile, err := os.Open(filepath)

  if err != nil {
    return nil, err
  }
  defer csvfile.Close()

  reader := csv.NewReader(csvfile)
  fields, err := reader.ReadAll()

  return fields, nil
}

func main() {
  // load data csv
  records, err := ReadCSV("./path/to/datafile.csv")
  if err != nil {
    log.Fatal(err)
  }

  // write results to a new csv
  outfile, err := os.Create("./where/to/write/resultsfile.csv"))
  if err != nil {
    log.Fatal("Unable to open output")
  }
  defer outfile.Close()
  writer := csv.NewWriter(outfile)

  for i, record := range records {
    time := record[0]
    value := record[1]

    // skip header row
    if i == 0 {
      writer.Write([]string{time, value, "score"})
      continue
    }

    // get float values
    floatValue, err := strconv.ParseFloat(value, 64)
    if err != nil {
      log.Fatal("Record: %v, Error: %v", floatValue, err)
    }

    // calculate scores; THIS EXTERNAL METHOD CANNOT BE CHANGED
    score := calculateStuff(floatValue)

    valueString := strconv.FormatFloat(floatValue, 'f', 8, 64)
    scoreString := strconv.FormatFloat(prob, 'f', 8, 64)
    //fmt.Printf("Result: %v\n", []string{time, valueString, scoreString})

    writer.Write([]string{time, valueString, scoreString})
  }

  writer.Flush()
}

このCSV読み取り/書き込みテンプレートコードを可能な限り高速にするためのヘルプを探しています。この質問の範囲では、calculateStuffメソッドについて心配する必要はありません。

9
BoltzmannBrain

最初にファイルをメモリにロードしてから処理しますが、大きなファイルでは遅くなる可能性があります。

ループして.Readを呼び出し、一度に1行ずつ処理する必要があります。

func processCSV(rc io.Reader) (ch chan []string) {
    ch = make(chan []string, 10)
    go func() {
        r := csv.NewReader(rc)
        if _, err := r.Read(); err != nil { //read header
            log.Fatal(err)
        }
        defer close(ch)
        for {
            rec, err := r.Read()
            if err != nil {
                if err == io.EOF {
                    break
                }
                log.Fatal(err)

            }
            ch <- rec
        }
    }()
    return
}

playground

// DaveCのコメントに大まかに基づいていることに注意してください。

15
OneOfOne

これは本質的にコメントセクションからの Dave C の答えです:

_package main

import (
  "encoding/csv"
  "log"
  "os"
  "strconv"
)

func main() {
  // setup reader
  csvIn, err := os.Open("./path/to/datafile.csv")
  if err != nil {
    log.Fatal(err)
  }
  r := csv.NewReader(csvIn)

  // setup writer
  csvOut, err := os.Create("./where/to/write/resultsfile.csv"))
  if err != nil {
    log.Fatal("Unable to open output")
  }
  w := csv.NewWriter(csvOut)
  defer csvOut.Close()

  // handle header
  rec, err := r.Read()
  if err != nil {
    log.Fatal(err)
  }
  rec = append(rec, "score")
  if err = w.Write(rec); err != nil {
    log.Fatal(err)
  }

  for {
    rec, err = r.Read()
    if err != nil {
      if err == io.EOF {
        break
      }
      log.Fatal(err)
    }

    // get float value
    value := rec[1]
    floatValue, err := strconv.ParseFloat(value, 64)
    if err != nil {
      log.Fatal("Record, error: %v, %v", value, err)
    }

    // calculate scores; THIS EXTERNAL METHOD CANNOT BE CHANGED
    score := calculateStuff(floatValue)

    scoreString := strconv.FormatFloat(score, 'f', 8, 64)
    rec = append(rec, scoreString)

    if err = w.Write(rec); err != nil {
      log.Fatal(err)
    }
  w.Flush()
  }
}
_

もちろん、ロジックはすべてmain()に詰め込まれていることに注意してください。複数の関数に分割する方が良いでしょうが、それはこの質問の範囲を超えています。

5
BoltzmannBrain

encoding/csvは、多くの割り当てを実行するため、大きなファイルでは実際に非常に遅くなります。あなたのフォーマットはとてもシンプルなので、代わりにstrings.Splitを使用することをお勧めします。

それでも十分に速くない場合は、アセンブリに実装されているstrings.IndexByteを使用して、自分で解析を実装することを検討できます。 http://golang.org/src/strings/strings_decl.go?s=274: 310#L1

そうは言っても、ファイルがメモリよりも大きい場合は、ReadAllの使用も再検討する必要があります。

1
Nick Keets