为什么以下golang程序会抛出运行时内存不足错误?

Madhav Jha:

该程序应读取一个由整数对组成的文件(每行一对)并删除重复的对。虽然它适用于小型文件,但会对大型文件(例如1.5 GB的文件)引发运行时错误。最初,我认为是导致此问题的是地图数据结构,但是即使在注释掉之后,它仍然会耗尽内存。任何想法为什么会这样?如何纠正呢?这是一个内存不足的数据文件:http : //snap.stanford.edu/data/com-Orkut.html

package main
import (
    "fmt"
    "bufio"
    "os"
    "strings"
    "strconv"
)

func main() {
    file, err := os.Open(os.Args[1])
    if err != nil {
        panic(err.Error())
    }
    defer file.Close()
    type Edge struct {
        u, v int
    }
    //seen := make(map[Edge]bool)
    edges := []Edge{}
    scanner := bufio.NewScanner(file)

    for i, _ := strconv.Atoi(os.Args[2]); i > 0; i-- {
        scanner.Scan()
    }

    for scanner.Scan() {
        str := scanner.Text()
        edge := strings.Split(str, ",")
        u, _ := strconv.Atoi(edge[0])
        v, _ := strconv.Atoi(edge[1])
        var key Edge
        if u < v {
            key = Edge{u,v}
        } else {
            key = Edge{v,u}
        }
        //if seen[key] {
        //  continue
        //}
        //seen[key] = true
        edges = append(edges, key)
    }
    for _, e := range edges {
        s := strconv.Itoa(e.u) + "," + strconv.Itoa(e.v)
        fmt.Println(s)
    }
}

下面给出了示例输入。该程序可以按以下方式运行(最后输入表示要跳过多少行)。去运行undup.go a.txt 1

# 3072441,117185083
1,2
1,3
1,4
1,5
1,6
1,7
1,8
迦勒:

我看了看这个文件:com-orkut.ungraph.txt它包含117185082行。数据的结构方式,即每行至少16个字节。(Edge是两个64bit int)仅此一个就是1.7GB。我过去曾遇到过这个问题,这可能是一个棘手的问题。您是要解决特定用例(问题文件)还是一般用例?

在特定情况下,您可以利用有关数据的一些事项:(1)对键进行排序,并且(2)看起来它将每个连接存储两次,(3)数字似乎并不大。这里有一些想法:

  1. 如果为密钥使用较小的类型,则将使用较少的内存。试试吧uint32

  2. 您可以通过简单地查看第二列是否大于第一列来将密钥流式传输(不使用映射)到另一个文件:

    if u < v {
        // write the key to another file
    } else {
        // skip it because v will eventually show v -> u
    }
    

对于一般情况,您可以使用几种策略:

  1. 如果结果列表的顺序无关紧要:使用磁盘上的哈希表存储映射。有这些一堆:性LevelDB,sqlite的,东京暴君,...一个非常好的一个走的是螺栓

    在for循环中,您只需要检查存储桶中是否包含给定密钥即可。(您可以使用编码/二进制将整数转换为字节片)。如果确实如此,则跳过它并继续。您将需要将第二个for循环处理步骤移到第一个for循环中,这样就不必存储所有键。

  2. 如果结果列表的顺序很重要(并且您不能保证输入顺序正确):您还可以使用磁盘上的哈希表,但需要对其进行排序。螺栓已排序,因此可以正常工作。将所有键添加到其中,然后在第二个循环中遍历。

这是一个示例:(此程序要花一亿时间才能运行一亿条记录)

package main

import (
    "bufio"
    "encoding/binary"
    "fmt"
    "github.com/boltdb/bolt"
    "os"
    "strconv"
    "strings"
)

type Edge struct {
    u, v int
}

func FromKey(bs []byte) Edge {
    return Edge{int(binary.BigEndian.Uint64(bs[:8])), int(binary.BigEndian.Uint64(bs[8:]))}
}

func (e Edge) Key() [16]byte {
    var k [16]byte
    binary.BigEndian.PutUint64(k[:8], uint64(e.u))
    binary.BigEndian.PutUint64(k[8:], uint64(e.v))
    return k
}

func main() {
    file, err := os.Open(os.Args[1])
    if err != nil {
        panic(err.Error())
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)

    for i, _ := strconv.Atoi(os.Args[2]); i > 0; i-- {
        scanner.Scan()
    }

    db, _ := bolt.Open("ex.db", 0777, nil)
    defer db.Close()

    bucketName := []byte("edges")
    db.Update(func(tx *bolt.Tx) error {
        tx.CreateBucketIfNotExists(bucketName)
        return nil
    })

    batchSize := 10000
    total := 0
    batch := make([]Edge, 0, batchSize)
    writeBatch := func() {
        total += len(batch)
        fmt.Println("write batch. total:", total)
        db.Update(func(tx *bolt.Tx) error {
            bucket := tx.Bucket(bucketName)
            for _, edge := range batch {
                key := edge.Key()
                bucket.Put(key[:], nil)
            }
            return nil
        })
    }

    for scanner.Scan() {
        str := scanner.Text()
        edge := strings.Split(str, "\t")
        u, _ := strconv.Atoi(edge[0])
        v, _ := strconv.Atoi(edge[1])
        var key Edge
        if u < v {
            key = Edge{u, v}
        } else {
            key = Edge{v, u}
        }
        batch = append(batch, key)
        if len(batch) == batchSize {
            writeBatch()
            // reset the batch length to 0
            batch = batch[:0]
        }
    }
    // write anything leftover
    writeBatch()

    db.View(func(tx *bolt.Tx) error {
        tx.Bucket(bucketName).ForEach(func(k, v []byte) error {
            edge := FromKey(k)
            fmt.Println(edge)
            return nil
        })
        return nil
    })
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么从未执行过的Swift 3代码会抛出运行时错误?

为什么[[]] [0] ++可以工作,但是[] ++会抛出运行时异常?

新问题-运行时错误-内存不足

运行时错误7:内存不足并加速代码

为什么以下条件运算符“?:”会编译却给出运行时错误

为什么我的代码给出运行时错误?

performSegueWithIdentifier给出运行时错误,为什么?

构建通用应用的发行版时,为什么我的Xamarin PCL会抛出运行时异常?

为什么VB6 FlexGrid抛出运行时错误381'下标超出范围'?

为什么Contextmanager抛出运行时错误“ throw()之后生成器没有停止”?

运行时:内存不足,还有静态内存

Azure应用服务抛出运行时错误:找不到程序集

如何修复 PyTorch 运行时错误:CUDA 错误:内存不足?

opencv linemod抛出运行时错误

编译 index.cshtml 抛出运行时错误

asyncio 抛出运行时错误并忽略异常

Model.fit_generator抛出运行时错误:

什么时候抛出运行时异常?

有什么需要抛出运行时异常

Perl:在运行时构建二维数组时出现内存不足错误

如何解决UPC运行时错误:共享内存不足

PyTorch 运行时错误:CUDA 内存不足。尝试分配 14.12 GiB

Eclipse 中 Java 运行时环境的内存不足

为什么此代码编译并在执行时给出运行时错误

为什么在到达xmx之前64位JVM会抛出内存不足?

为什么以下一段Java代码会引发运行时错误?

为什么用Install4j 6构建的macOS安装程序会在JRE 10中抛出运行时异常?

jboss抛出内存不足:在intellij idea中运行时堆空间,但在eclipse中运行时没有

为什么此代码打印第n个数字会给出运行时错误?