多次执行例程读取pcap文件不能提高性能吗?

问题描述

我需要阅读约600个pcap文件,每个文件约为100MB。 我使用gopacket加载pcap文件并进行检查。

案例1:使用1个例程进行检查。

情况2:使用40个例程进行检查。

我发现case1和case2消耗的时间相似。 区别是case1的cpu使用率只有200%,而case2可以达到3000%。 我的问题是为什么多个例程不能提高性能? 代码中有一些注释,希望对您有所帮助。

package main

import (
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "strings"
    "sync"

    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
)

func main() {
    var wg sync.WaitGroup

    var dir = flag.String("dir","../pcap","input dir")
    var threadNum = flag.Int("threads",40,"input thread number")
    flag.Parse()
    fmt.Printf("dir=%s,threadNum=%d\n",*dir,*threadNum)

    pcapFileList,err := ioutil.ReadDir(*dir)
    if err != nil {
        panic(err)
    }

    log.Printf("start. file number=%d.",len(pcapFileList))

    fileNumPerRoutine := len(pcapFileList) / *threadNum
    lastFileNum := len(pcapFileList) % *threadNum

    // split files to different routine
    // each routine only process files which belong to itself
    if fileNumPerRoutine > 0 {
        for i := 0; i < *threadNum; i++ {
            start := fileNumPerRoutine * i
            end := fileNumPerRoutine * (i + 1)
            if lastFileNum > 0 && i == (*threadNum-1) {
                end = len(pcapFileList)
            }
            // fmt.Printf("start=%d,end=%d\n",start,end)
            wg.Add(1)
            go checkPcapRoutine(i,&wg,dir,pcapFileList[start:end])
        }
    }

    wg.Wait()
    log.Printf("end.")
}

func checkPcapRoutine(id int,wg *sync.WaitGroup,dir *string,pcapFileList []os.FileInfo) {
    defer wg.Done()

    for _,p := range pcapFileList {
        if !strings.HasSuffix(p.Name(),"pcap") {
            continue
        }
        pcapFile := *dir + "/" + p.Name()
        log.Printf("checkPcapRoutine(%d): process %s.",id,pcapFile)

        handle,err := pcap.OpenOffline(pcapFile)
        if err != nil {
            log.Printf("error=%s.",err)
            return
        }
        defer handle.Close()

        packetSource := gopacket.NewPacketSource(handle,handle.LinkType())

        // Per my test,if I don't parse packets,it is very fast,even use only 1 routine,so IO should not be the bottleneck.
        // What puzzles me is that every routine has their own packets,each routine is independent,but it still seems to be processed serially.
        // This is the first time I use gopacket,maybe used wrong parameter?
        for packet := range packetSource.Packets() {
            gtpLayer := packet.Layer(layers.LayerTypeGTPv1U)
            lays := packet.Layers()
            outerIPLayer := lays[1]
            outerIP := outerIPLayer.(*layers.IPv4)

            if gtpLayer == nil && (outerIP.Flags&layers.IPv4MoreFragments != 0) && outerIP.Length < 56 {
                log.Panicf("file:%s,idx=%d may leakage.",pcapFile,j+1)
                break
            }
        }
    }
}

解决方法

要并行运行两个或多个任务 ,执行这些任务所需的操作必须具有不相互依赖的属性,或者被称为的某些外部资源这些任务共享

在现实世界中,真正且完全独立的任务很少见(如此罕见,甚至有专门的名称来描述此类任务:它们被称为embarrasingly parallel),但是当任务对彼此进度的依赖性以及竞争访问共享资源的阈值都低于某个阈值,添加更多的“工人”(goroutines) 可能会缩短完成一组任务所需的总时间。

此处“可能”通知:例如,您的存储设备及其上的文件系统以及与该文件系统一起工作的内核数据结构和代码是您所有goroutine必须访问的共享介质。这种介质在吞吐量和延迟方面都有一定的限制。基本上,您只能每秒从该介质读取M字节-不管您是单个读取器充分利用了该带宽,还是N个读取器-每个读取器都利用了大约M / N的带宽,这无关紧要:读取速度不能超过M BPS的限制。

此外,在现实世界中最常发现的资源在争夺以下资源时会降低其性能:例如,如果必须locked来访问该资源,则越来越多的访问者主动希望获得您拥有的锁,则在锁定管理代码中花费的CPU时间就越多(当资源变得更加复杂时,例如“存储设备上的FS,全部由内核管理”的复杂内容的集合),则是如何对其进行降级的分析并发访问时变得更加复杂)。

TL; DR

我可以根据需要猜测,由于goroutine必须读取文件,因此您的任务只是受I / O约束。

您可以通过修改代码以首先将所有文件提取到内存中,然后将缓冲区交给解析goroutine进行验证。

在您的案例中,您观察到的大量CPU花费是一个红色的鲱鱼:当代系统喜欢将100%CPU利用率表示为“完全利用单个硬件处理线程” –因此,如果您启用了4个具有HyperThreading™(或AMD为此功能的CPU)的CPU内核,则系统的全部容量为4×2 = 8或800%。
您的系统可能会显示出超出理论容量(我们不知道)的事实,这可能是因为系统显示了那样所谓的“饥饿”:您有许多软件线程需要要执行但要等待其CPU时间,并且系统显示这是疯狂的CPU利用率。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...