如何同时搜索大片地图[string]字符串

时间:2017-01-21 18:52:56

标签: go concurrency parallel-processing

我需要搜索slice maps[string]string//Search for a giving term //This function gets the data passed which will need to be search //and the search term and it will return the matched maps // the data is pretty simply the map contains { key: andSomeText } func Search(data []map[string]string, term string) []map[string]string { set := []map[string]string{} for _, v := range data { if v["key"] == term { set = append(set, v) } } return set } 。我的想法是,这是一个很好的机会去通道和去常规。

计划是将切片分成几部分并发送并行搜索。 但我有点震惊的是,我的并行版本超时了,而整个切片的搜索都成功了。

我不确定我做错了什么。下面是我用来测试这个概念的代码。真正的代码将涉及更多的复杂性

// All searches all records concurrently
// Has the same function signature as the the search function
// but the main task is to fan out the slice in 5 parts and search
// in parallel
func All(data []map[string]string, term string) []map[string]string {
    countOfSlices := 5

    part := len(data) / countOfSlices

    fmt.Printf("Size of the data:%v\n", len(data))
    fmt.Printf("Fragemnt Size:%v\n", part)

    timeout := time.After(60000 * time.Millisecond)

    c := make(chan []map[string]string)

    for i := 0; i < countOfSlices; i++ {
        // Fragments of the array passed on to the search method
        go func() { c <- Search(data[(part*i):(part*(i+1))], term) }()

    }

    result := []map[string]string{}

    for i := 0; i < part-1; i++ {
        select {
        case records := <-c:
            result = append(result, records...)
        case <-timeout:
            fmt.Println("timed out!")
            return result
        }
    }
    return result
}

因此,搜索给定SearchTerm的地图片段非常有效。

现在我想如果我的切片有20K条目,我想并行搜索

func GenerateTestData(search string) ([]map[string]string, int) {
    rand.Seed(time.Now().UTC().UnixNano())
    strin := []string{"String One", "This", "String Two", "String Three", "String Four", "String Five"}
    var matchCount int
    numOfRecords := 20000
    set := []map[string]string{}
    for i := 0; i < numOfRecords; i++ {
        p := rand.Intn(len(strin))
        s := strin[p]
        if s == search {
            matchCount++
        }
        set = append(set, map[string]string{"key": s})
    }
    return set, matchCount
}

以下是我的测试:

我有一个生成测试数据和2个测试的函数。

func TestSearchItem(t *testing.T) {

    tests := []struct {
        InSearchTerm string
        Fn           func(data []map[string]string, term string) []map[string]string
    }{
        {
            InSearchTerm: "This",
            Fn:           Search,
        },
        {InSearchTerm: "This",
            Fn: All,
        },
    }

    for i, test := range tests {

        startTime := time.Now()
        data, expectedMatchCount := GenerateTestData(test.InSearchTerm)
        result := test.Fn(data, test.InSearchTerm)

        fmt.Printf("Test: [%v]:\nTime: %v \n\n", i+1, time.Since(startTime))
        assert.Equal(t, len(result), expectedMatchCount, "expected: %v to be: %v", len(result), expectedMatchCount)

    }
}

2个测试:第一个只是遍历切片而第二个是并行搜索

@Override
public void onCreate(SQLiteDatabase db) {   
  db.execSQL("create table "+TABLE_NAME+" (ID INTEGER PRIMARY KEY AUTOINCREMENT, TITLE TEXT, STORY TEXT, AUTHOR TEXT, DATE STRING)");
  db.execSQL("create table "+TABLE_NAME1+" (ID INTEGER PRIMARY KEY AUTOINCREMENT, Table1Field TEXT)");
}

@Override
public void onUpgrade(SQLiteDatabase db, int i, int i1) {
    db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME);
    db.execSQL("DROP TABLE IF EXISTS "+TABLE_NAME1);
    onCreate(db);
}

如果有人能解释为什么我的并行代码如此之慢,那会很棒吗?代码有什么问题,我在这里缺少什么,以及在内存50K +中搜索大片的推荐方法。

3 个答案:

答案 0 :(得分:4)

这看起来只是一个简单的错字。问题是你将原来的大切片分成5块(countOfSlices),并正确启动5个goroutines来搜索每个部分:

for i := 0; i < countOfSlices; i++ {
    // Fragments of the array passed on to the search method
    go func() { c <- Search(data[(part*i):(part*(i+1))], term) }()

}

这意味着您应该获得 5 结果,但不是。你期望4000-1的结果:

for i := 0; i < part-1; i++ {
    select {
    case records := <-c:
        result = append(result, records...)
    case <-timeout:
        fmt.Println("timed out!")
        return result
    }
}

显然,如果你只推出了5个goroutine,每个都提供1个单一结果,你只能期望尽可能多(5)。而且由于你的循环等待了很多(永远不会来),它会超出预期。

将条件更改为:

for i := 0; i < countOfSlices; i++ {
    // ...
}

答案 1 :(得分:1)

并发不是并行性。 Go是大规模并发语言,而不是并行。即使使用多核机器,在计算线程中访问共享切片时,也会为CPU之间的数据交换付费。例如,您可以利用第一次匹配时的并发搜索。或者对结果做一些事情(比如打印它们,或写一些Writer,或者排序),而继续搜索。

func Search(data []map[string]string, term string, ch chan map[string]string) {
    for _, v := range data {
        if v["key"] == term {
            ch <- v
        }
    }
}
func main(){
...
    go search(datapart1, term, ch)
    go search(datapart2, term, ch)
    go search(datapart3, term, ch)
...
    for vv := range ch{
        fmt.Println(vv) //do something with match concurrently
    }
...
}

搜索大切片的推荐方法是保持排序,或制作二叉树。没有魔力。

答案 2 :(得分:0)

有两个问题 - 因为icza注意到你从未完成选择,因为你需要使用countOfSlices,然后你的搜索调用将无法获得你想要的数据,因为你需要在调用go func()之前分配它,所以在外面分配切片并传入。

您可能会发现,与这样简单的数据并行执行此特定工作仍然不是更快(可能在具有大量内核的机器上使用更复杂的数据)这是值得的吗?

确保在测试时尝试交换测试运行的顺序 - 您可能会对结果感到惊讶!也许可以尝试测试包中提供的基准测试工具,它可以为您运行代码很多次并平均结果,这可以帮助您更好地了解扇出是否加快了速度。