Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/go/7.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/visual-studio-code/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
有效附加到字符串可变长度容器(Golang)_Go_Containers_Slice - Fatal编程技术网

有效附加到字符串可变长度容器(Golang)

有效附加到字符串可变长度容器(Golang),go,containers,slice,Go,Containers,Slice,问题是: 我需要对一个大日志文件的每一行应用多个正则表达式(比如几GB长),收集非空匹配并将它们全部放入一个数组中(用于序列化并通过网络发送) 如果答案成立,则切片没有多大帮助: 如果片没有足够的容量,append将需要分配新内存并复制旧内存。对于1024个元素的切片,它将增加系数1.25 因为可以有几十万个正则表达式匹配,所以我无法真正预测一个片段的长度/容量。我也不能让它太大,“以防万一”bc这会浪费内存(或者会吗?如果内存分配器足够聪明,不会分配太多未写入的内存,也许我可以使用一个巨大的片

问题是:

我需要对一个大日志文件的每一行应用多个正则表达式(比如几GB长),收集非空匹配并将它们全部放入一个数组中(用于序列化并通过网络发送)

如果答案成立,则切片没有多大帮助:

如果片没有足够的容量,append将需要分配新内存并复制旧内存。对于1024个元素的切片,它将增加系数1.25

因为可以有几十万个正则表达式匹配,所以我无法真正预测一个片段的长度/容量。我也不能让它太大,“以防万一”bc这会浪费内存(或者会吗?如果内存分配器足够聪明,不会分配太多未写入的内存,也许我可以使用一个巨大的片容量而不会造成太大伤害?)

因此,我正在考虑以下备选方案:

  • 具有匹配项的双链接列表()
  • 计算其长度(将
    len()
    work?)
  • 预先分配此容量的一部分
  • 将字符串指针复制到此切片
  • 在Go中是否有一种不那么费力的方法来实现这个目标(使用~O(1)append complexity进行append)

    (当然是golang新手)

    append()
    的平均(摊销)成本已经是O(1),因为它每次都以一定的百分比增长数组。随着阵列变得越来越大,它的增长成本也越来越高,但比例上也越来越少。10M项目片的增长成本将是1M项目片的10倍,但由于我们分配的额外容量与大小成正比,因此在下次增长之前,追加(切片,项目)调用的数量也将是10x。增加的成本和减少的再分配频率相互抵消,使平均成本保持不变,即O(1)

    同样的想法也适用于其他语言的动态大小数组:例如,微软的
    std::vector
    实现显然使数组每次增长50%。摊销O(1)并不意味着您不为分配支付任何费用,只意味着您在阵列变大时继续以相同的平均费率支付

    在我的笔记本电脑上,我可以在77毫秒内运行一百万个
    slice=append(slice,someStaticString)
    s。siritinga在下面指出,它之所以快速的一个原因是“复制”字符串以放大数组实际上只是复制字符串头(指针/长度对),而不是复制内容。100000个字符串头的复制容量仍然不足2MB,与您正在处理的其他数据量相比,这不是什么大问题

    container/list
    对我来说,在一个微型方舟里慢了约3倍;当然,链表附加也是常数时间,但我认为
    append
    的常数较低,因为它通常只能写入几个单词的内存,而不能分配列表项等。计时代码在操场上不起作用,但您可以在本地复制它并运行它来查看您自己:

    有时,您可以有效地预分配空间以避免重新分配/复制(在本例中,使用
    make([]string,0,1000000)
    将运行时间从~77ms缩短到~10ms),但是,当然,通常情况下,您没有足够的关于预期数据大小的信息,等等,以弥补有价值的收益,你最好把它留给内置的算法


    但是您在这里提出了一个关于类
    grep
    的应用程序的更具体的问题(感谢您在上下文中提出了一个详细的问题)。对于这一点,最基本的建议是,如果您正在搜索GIG的日志,那么最好避免在RAM中缓冲整个输出

    您可以编写一些东西将结果作为单个函数进行流式处理:
    logparser.Grep(in-io.Reader,out-io.Writer,patterns[]regexp.regexp)
    ;如果您不希望发送结果的代码与grep代码过于纠缠,您也可以将
    out
    a
    chan[]byte
    func(match[]byte)(err error)

    (关于
    []字节
    字符串
    的对比:一个
    []字节
    似乎可以在这里完成这项工作,并且在您执行I/O时可以避免
    []字节
    字符串转换,所以我更喜欢这样。不过,我不知道您在做什么,如果您需要
    字符串
    就可以了。)


    如果您将整个匹配列表保留在RAM中,请注意,保留对大字符串或字节片的部分引用可以防止整个源字符串/片被垃圾收集。因此,如果你走这条路,那么你可能会反直觉地想要复制匹配项,以避免将所有源日志数据保留在RAM中。

    我试图将你的问题提炼成一个非常简单的例子

    由于可以有“数十万个正则表达式匹配”,我为
    匹配
    切片容量分配了1m(1024*1024)的大量初始条目。切片是一种引用类型。在64位操作系统上,片头“struct”的长度、容量和指针总计为24(3*8)字节。因此,1M条目的片的初始分配仅为24(24*1)MB。如果有超过1M个条目,将分配一个容量为1.25M(1+1/4)条目的新切片,并将现有的1M切片头条目(24MB)复制到其中

    总之,通过最初过度分配片容量,您可以避免许多
    append
    s的开销。更大的内存问题是为每个匹配保存和引用的所有数据。更大的CPU时间问题是执行
    regexp.FindAll
    所需的时间

    package main
    
    import (
        "bufio"
        "fmt"
        "os"
        "regexp"
    )
    
    var searches = []*regexp.Regexp{
        regexp.MustCompile("configure"),
        regexp.MustCompile("unknown"),
        regexp.MustCompile("PATH"),
    }
    
    var matches = make([][]byte, 0, 1024*1024)
    
    func main() {
        logName := "config.log"
        log, err := os.Open(logName)
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            os.Exit(1)
        }
        defer log.Close()
        scanner := bufio.NewScanner(log)
        for scanner.Scan() {
            line := scanner.Bytes()
            for _, s := range searches {
                for _, m := range s.FindAll(line, -1) {
                    matches = append(matches, append([]byte(nil), m...))
                }
            }
        }
        if err := scanner.Err(); err != nil {
            fmt.Fprintln(os.Stderr, err)
        }
        // Output matches
        fmt.Println(len(matches))
        for i, m := range matches {
            fmt.Println(string(m))
            if i >= 16 {
                break
            }
        }
    }
    

    考虑到处理的数据量tb,我必须限制一次运行中要扫描的行数(原因应该很明显:平均I/O负载、CPU负载和驻留集大小,即防止出现大负载峰值),因此我不必真正进行流式处理。但对我来说更重要的是,我并不真正理解你的意思