Заставить GO (GOLANG) разбирать текст построчно - PullRequest
0 голосов
/ 21 апреля 2020

Просто заметьте, я программировал долгое время, но я программировал GO в течение примерно четырех часов относительно этого поста.

Это стало немного академическим c вопросом Теперь все зависит от скорости.

Задача состояла в том, чтобы проанализировать очень большой текстовый файл. Скажем, для тестирования 500 Мб, это не так уж и много, но достаточно, чтобы показать разницу в скорости. Допустим, файл real имеет размер 1 Гб, просто чтобы добавить некоторые параметры к этому замечанию. У меня достаточно памяти для загрузки всего файла в память, если это необходимо.

Вывод ( в данном случае на консоль) каждая строка, которая начинается с определенного IP-адреса c.

Мне было интересно, можно ли это сделать гораздо быстрее, чем grep и awk (et c) написав очень точный c код.

Я решил попробовать это сначала GO, а затем RUST (еще не пробовал).

После некоторой работы с GO I Я весьма удивлен тем, насколько медленнее он делает что-то очень конкретное c. Grep почти на 50% быстрее.

Примечание. Я использовал некоторые базовые c встроенные GO временные характеристики, которые являются точными ( достаточно ) для моих целей.

Я также попытался указать несколько битов источника, которые я не записал, специфицируя c для чтения байтов. Я только 80% понял синтаксис, но они были даже медленнее, чем моя правда кода.

Если вы видите какие-либо несвязанные GO ошибки, не стесняйтесь указывать на них и объяснять, как я говорю, я программирую GO около четырех часов.

Мне нравится кое-что из GO пытается это сделать, поэтому было бы здорово, чтобы это работало даже со скоростью, по крайней мере, равной, в основном, grep и awk et c.

Edit этот тест был на самом деле egrep работает в Ubuntu, так как кажется, что этот код на самом деле быстрее, чем grep и egrep в OSX.

Пока что я достиг поста:

package main

import (
    "bufio"
    "flag"
    "fmt"
    "os"
    "time"
)

var (
    filePath  = flag.String("filepath", "", "The path and filename of the file to parse.")
    ipAddress = flag.String("ipaddress", "", "The ip address to search for.")
)

func main() {

    flag.Parse()

    flag.VisitAll(func(f *flag.Flag) {

        if f.Value.String() == "" {
            fmt.Println()
            fmt.Println("Ip address finder.")
            fmt.Println()
            fmt.Println(`Usage: ipfinder -filepath "..." -ipaddress "..."`)
            fmt.Println()
            os.Exit(1)
        }
    })

    timerStart := time.Now()

    file, err := os.Open(*filePath)

    if err != nil {
        fmt.Printf("Failed to open file: %s", err)
        os.Exit(1)
    }

    defer file.Close()

    fileScanner := bufio.NewScanner(file)

    // Stop re-calculating length.
    // The compiler may handle this??
    var ipAddressStringLength int = len(*ipAddress)
    var fileScannerText string

    for fileScanner.Scan() {

        fileScannerText = fileScanner.Text()

        // I took this from the strings.HasPrefix source code and
        // tried to exclude bits (that may crash some input) but 
        // it didn't make much difference.
        if fileScannerText[:ipAddressStringLength] == *ipAddress {
            fmt.Println(fileScannerText)
        }
    }

    // Not sure about this??
    if err := fileScanner.Err(); err != nil {
        fmt.Println(err)
    }

    elapsed := time.Since(timerStart)

    fmt.Println()
    fmt.Printf("Time Took %s", elapsed)
}

Ответы [ 2 ]

3 голосов
/ 21 апреля 2020

Я только что использовал @ Cerise Limón предложений, скорректировал свой код и добился впечатляющего ускорения (30% на OSX, ~ 75% на Debian). Теперь версия Go (по моим тестовым данным) быстрее, чем egrep на обеих платформах:

package main

import (
    "bufio"
    "bytes"
    "flag"
    "fmt"
    "os"
    "time"
)

var (
    filePath  = flag.String("filepath", "", "The path and filename of the file to parse.")
    ipAddress = flag.String("ipaddress", "", "The ip address to search for.")
)

func main() {
    flag.Parse()
    flag.VisitAll(func(f *flag.Flag) {
        if f.Value.String() == "" {
            fmt.Println()
            fmt.Println("Ip address finder.")
            fmt.Println()
            fmt.Println(`Usage: ipfinder -filepath "..." -ipaddress "..."`)
            fmt.Println()
            os.Exit(1)
        }
    })

    timerStart := time.Now()
    file, err := os.Open(*filePath)

    if err != nil {
        fmt.Printf("Failed to open file: %s", err)
        os.Exit(1)
    }
    defer file.Close()

    fileScanner := bufio.NewScanner(file)
    var scannerBytes []byte
    ipBytes := []byte(*ipAddress)
    w := bufio.NewWriterSize(os.Stdout, 1000000)

    for fileScanner.Scan() {
        scannerBytes = fileScanner.Bytes()

        if bytes.HasPrefix(scannerBytes, ipBytes) {
            fmt.Fprintln(w, string(scannerBytes))
        }
    }
    w.Flush()

    if err := fileScanner.Err(); err != nil {
        fmt.Println(err)
    }

    elapsed := time.Since(timerStart)
    fmt.Println()
    fmt.Printf("Time Took %s", elapsed)
}

Вы можете поэкспериментировать с размером буфера для Stdout, чтобы добиться оптимальной производительности.

1 голос
/ 22 апреля 2020

Вы можете попробовать это, но, честно говоря, я также вижу довольно большие расхождения между Debian на разных машинах.

Простая установка Debian без GUI на аппаратном уровне (как для VM et et c), Grep все еще, кажется, бьет любой GO ответ на этот вопрос, который я пробовал. И бьет это хорошо.

В Debian я отключил таймер в коде и просто использовал команду Linux Time.

Я протестировал это с файлом 800 МБ.

package main

import (
    "bufio"
    "flag"
    "fmt"
    "log"
    "os"
    "strings"
    "sync"
    "sync/atomic"
    "time"
)

var (
    filePath  = flag.String("filepath", "", "The path and filename of the file to parse.")
    ipAddress = flag.String("ipaddress", "", "The ip address to search for.")
)

func main() {

    flag.Parse()
    flag.VisitAll(func(f *flag.Flag) {
        if f.Value.String() == "" {
            fmt.Println()
            fmt.Println("Ip address finder.")
            fmt.Println()
            fmt.Println(`Usage: ipfinder -filepath "..." -ipaddress "..."`)
            fmt.Println()
            os.Exit(1)
        }
    })

    timerStart := time.Now()

    file, err := os.Open(*filePath)
    if err != nil {
        log.Fatal(err)
    }

    defer file.Close()

    scanner := bufio.NewScanner(file)

    linesChunkLen := 64 * 1024

    linesPool := sync.Pool{
        New: func() interface{} {
            lines := make([]string, 0, linesChunkLen)
            return lines
        },
    }

    lines := linesPool.Get().([]string)[:0]

    wg := sync.WaitGroup{}

    scanner.Scan()

    for {

        lines = append(lines, scanner.Text())

        isScanning := scanner.Scan()

        if len(lines) == linesChunkLen || !isScanning {

            linesToProcess := lines

            wg.Add(len(linesToProcess))

            go func() {

                for _, text := range linesToProcess {

                    if strings.HasPrefix(text, *ipAddress) {

                        // Output matching ip address line here.
                        fmt.Println(text)

                    }
                    wg.Add(-1)
                }

                linesPool.Put(linesToProcess)
            }()

            lines = linesPool.Get().([]string)[:0]
        }

        if !isScanning {
            break
        }
    }

    wg.Wait()

    fmt.Printf("Time Took %v\n", time.Since(timerStart))
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...