Maison >développement back-end >Golang >Ignorer les lignes contenant un motif dans un fichier texte long dans Go

Ignorer les lignes contenant un motif dans un fichier texte long dans Go

WBOY
WBOYavant
2024-02-13 13:57:191010parcourir

在 Go 中忽略长文本文件中包含模式的行

php Editor Apple Dans le langage Go, nous avons souvent besoin de traiter des fichiers texte volumineux. Parfois, nous ne nous intéressons qu'aux lignes contenant un modèle spécifique et ignorons les autres lignes. Heureusement, dans Go, nous pouvons utiliser des expressions régulières et bufio.Scanner pour atteindre cet objectif. En utilisant des expressions régulières pour faire correspondre les lignes et en exécutant le fichier via un scanner ligne par ligne, nous pouvons facilement filtrer les lignes qui ne nous intéressent pas. Cette astuce améliore non seulement l'efficacité, mais rend également notre code plus concis et plus lisible. Voyons ensuite comment ignorer les lignes contenant des motifs dans les fichiers texte longs dans Go.

Contenu de la question

J'essaie d'implémenter une fonction pour ignorer les lignes contenant des motifs dans des fichiers texte longs (ascii garanti) en go

Je le suis withoutignorewithignore 下面的函数都接受文件名参数输入并返回 *byte.buffer,随后可用于写入 io.writer.

withignore 函数采用附加参数 pattern 从文件中排除包含模式的行。该函数可以工作,但通过基准测试,发现它比 慢 5 倍而不忽略 . Y a-t-il un moyen de l'améliorer ?

package main

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

func withoutignore(f string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }

    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    inputbuffer := make([]byte, 1048576)
    var bytesread int

    var bs []byte
    opbuffer := bytes.newbuffer(bs)

    for {
        bytesread, err = rfd.read(inputbuffer)

        if err == io.eof {
            return opbuffer, nil
        }

        if err != nil {
            return nil, nil
        }

        _, err = opbuffer.write(inputbuffer[:bytesread])
        if err != nil {
            return nil, err
        }
    }
    return opbuffer, nil
}

func withignore(f, pattern string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }

    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    scanner := bufio.newscanner(rfd)
    var bs []byte
    buffer := bytes.newbuffer(bs)
    for scanner.scan() {
        if !bytes.contains(scanner.bytes(), []byte(pattern)) {
            _, err := buffer.writestring(scanner.text() + "\n")
            if err != nil {
                return nil, nil
            }
        }
    }

    return buffer, nil
}

func main() {
    // buff, err := withoutignore("base64dump.log")
    buff, err := withignore("base64dump.log", "audit")
    if err != nil {
        log.fatal(err)
    }

    _, err = buff.writeto(os.stdout)
    if err != nil {
        log.fatal(err)
    }
}

Benchmarks

package main

import "testing"

func benchmarktestwithoutignore(b *testing.b) {
    for i := 0; i < b.n; i++ {
        _, err := withoutignore("base64dump.log")
        if err != nil {
            b.fatal(err)
        }
    }
}

func benchmarktestwithignore(b *testing.b) {
    for i := 0; i < b.n; i++ {
        _, err := withignore("base64dump.log", "audit")
        if err != nil {
            b.fatal(err)
        }
    }
}

Et peut être utilisé depuis la ligne de commande“base64dump.log”生成

base64 /dev/urandom | head -c 10000000 > base64dump.log

Solution

Puisque ascii est garanti, il peut fonctionner directement au niveau byte.

Cependant, si vous vérifiez chaque octet pour un caractère de nouvelle ligne lors de la lecture de l'entrée, puis recherchez à nouveau le modèle dans la ligne, l'opération sera appliquée à chaque octet.

D'un autre côté, si vous lisez un bloc d'entrée et effectuez une recherche optimisée de modèles dans le texte, sans même vérifier chaque octet d'entrée, vous pouvez minimiser les opérations par octet d'entrée.

Par exemple, l'algorithme de recherche de chaîne Boyer-Moore. La fonction bytes.index intégrée de Go a également été optimisée. La vitesse atteinte dépendra bien entendu des données d'entrée et du mode réel. Pour l'entrée spécifiée dans la question, les performances de `bytes.index s'améliorent considérablement lorsqu'elles sont mesurées.

Programme

  • En lisant un bloc où la taille du bloc doit être nettement plus longue que la longueur maximale de la ligne, une valeur >= 64 Ko devrait probablement être bonne, lors des tests, j'ai utilisé le 1 Mo dans la question.
  • Un bloc ne se termine généralement pas par une nouvelle ligne, alors recherchez de la fin du bloc jusqu'à la nouvelle ligne suivante, limitez la recherche à cette tranche et mémorisez les données restantes pour le prochain passage
  • Le dernier bloc ne se termine pas nécessairement par un caractère de nouvelle ligne
  • À l'aide de fonctions Go hautes performances bytes.index, vous pouvez trouver où dans le bloc le motif se produit
  • Recherche des nouvelles lignes précédentes et suivantes à partir de la position trouvée
  • Le bloc est ensuite affiché au début de la ligne correspondante
  • Et continuez la recherche à partir de la fin de la ligne où le motif apparaît
  • Si la recherche ne trouve pas d'autres emplacements, affichez les emplacements restants
  • Lisez le morceau suivant et appliquez à nouveau les étapes décrites jusqu'à ce que vous atteigniez la fin du fichier

Remarquable

Une opération de lecture peut renvoyer moins de données que la taille du bloc, il est donc logique de répéter l'opération de lecture jusqu'à ce que la taille du bloc de données soit lue.

Benchmark

Le code optimisé est généralement beaucoup plus complexe, mais il est également nettement plus performant, comme nous le verrons plus tard.

benchmarktestwithoutignore-8         270       4137267 ns/op
benchmarktestwithignore-8             54      22403931 ns/op
benchmarktestfilter-8                150       7947454 ns/op

Ici, la méthode de code optimisé benchmarktestfilter-8只比没有过滤的操作慢1.9倍左右,而benchmarktestwithignore-8 est 5,4 fois plus lente que la valeur de comparaison non filtrée.

Regardez-le sous un autre angle : le code optimisé est 2,8 fois plus rapide que le code non optimisé.

code

Bien sûr, voici le code pour vos propres tests :

func filterfile(f, pattern string) (*bytes.buffer, error) {
    rfd, err := os.open(f)
    if err != nil {
        log.fatal(err)
    }
    defer func() {
        if err := rfd.close(); err != nil {
            log.fatal(err)
        }
    }()

    reader := bufio.newreader(rfd)
    return filter(reader, []byte(pattern), 1024*1024)
}

// chunksize must be larger than the longest line
// a reasonable size is probably >= 64k
func filter(reader io.reader, pattern []byte, chunksize int) (*bytes.buffer, error) {
    var bs []byte
    buffer := bytes.newbuffer(bs)

    chunk := make([]byte, chunksize)

    var remaining []byte
    for lastchunk := false; !lastchunk; {
        n, err := readchunk(reader, chunk, remaining, chunksize)
        if err != nil {
            if err == io.eof {
                lastchunk = true
            } else {
                return nil, err
            }
        }

        remaining = remaining[:0]
        if !lastchunk {
            for i := n - 1; i > 0; i-- {
                if chunk[i] == '\n' {
                    remaining = append(remaining, chunk[i+1:n]...)
                    n = i + 1
                    break
                }
            }
        }

        s := 0
        for s < n {
            hit := bytes.index(chunk[s:n], pattern)
            if hit < 0 {
                break
            }
            hit += s
            startofline := hit
            for ; startofline > 0; startofline-- {
                if chunk[startofline] == '\n' {
                    startofline++
                    break
                }
            }
            endofline := hit + len(pattern)
            for ; endofline < n; endofline++ {
                if chunk[endofline] == '\n' {
                    break
                }
            }
            endofline++

            _, err = buffer.write(chunk[s:startofline])
            if err != nil {
                return nil, err
            }
            s = endofline
        }

        if s < n {
            _, err = buffer.write(chunk[s:n])
            if err != nil {
                return nil, err
            }
        }
    }

    return buffer, nil
}

func readchunk(reader io.reader, chunk, remaining []byte, chunksize int) (int, error) {
    copy(chunk, remaining)
    r := len(remaining)
    for r < chunksize {
        n, err := reader.read(chunk[r:])
        r += n
        if err != nil {
            return r, err
        }
    }
    return r, nil
}

La section benchmark pourrait ressembler à ceci :

func BenchmarkTestFilter(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _, err := filterFile("base64dump.log", "AUDIT")
        if err != nil {
            b.Fatal(err)
        }
    }
}

La fonction de filtre est divisée et le travail réel est effectué en func filter(reader io.reader,pattern []byte, chunksize int) (*bytes.buffer, error).

La création de tests unitaires a été préparée ou prise en compte en injectant le reader et le chunksize, qui manquent ici mais sont définitivement recommandés lorsqu'il s'agit d'index.

Cependant, le but ici est de trouver un moyen d’améliorer considérablement les performances.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer