计算Go中切片中字符的出现次数

时间:2015-02-17 04:45:43

标签: c performance go

好的,所以我碰到了一堵砖墙。

修改 在我的bytes.IndexByte()函数中使用count()使其运行速度几乎快两倍。 bytes.IndexByte()是用汇编而不是Go编写的。仍然不是C速度,但更接近。

我有两个程序,一个在C中,一个在Go中,它们都计算文件中的换行符。超级简单。 C程序在大约1.5秒内运行,在2.4GB文件上运行~4.25秒。

我是否达到Go的速度限制?如果是这样,究竟是什么导致了这个?我可以阅读C,但是我无法读取汇编,所以比较C的asm和Go的asm对我没什么用,除了表明Go还有大约400行(忽略.ascii部分)。

虽然我知道Go不能与C步进匹配,但我不会假设4x减速。

想法?

以下是Go的cpuprofile: enter image description here

这是C(编译为w gcc -Wall -pedantic -O9

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#define BUFFER_SIZE (16 * 1024)

int
main()
{

    const char *file = "big.txt";
    int fd = open (file, O_RDONLY);
    char buf[BUFFER_SIZE + 1];
    uintmax_t bytes;
    size_t bytes_read;
    size_t lines;

    posix_fadvise (fd, 0, 0, POSIX_FADV_SEQUENTIAL);
    while ((bytes_read = safe_read (fd, buf, BUFFER_SIZE)) > 0)
    {
        char *p = buf;

        // error checking

        while ((p = memchr (p, '\n', (buf + bytes_read) - p)))
          {
            ++p;
            ++lines;
          }
        bytes += bytes_read;
    }
    printf("%zu\n", bytes);
    printf("%zu\n", lines);
    return 0;
}

Go:

package main

import (
    "flag"
    "fmt"
    "io"
    "os"
    "runtime/pprof"
    "syscall"
)

const (
    POSIX_FADV_SEQUENTIAL = 2

    NewLineByte = '\n' // or 10
    BufferSize  = (16 * 1024) + 1
)

var Buffer = make([]byte, BufferSize)

func fadvise(file *os.File, off, length int, advice uint32) error {
    _, _, errno := syscall.Syscall6(syscall.SYS_FADVISE64, file.Fd(), uintptr(off), uintptr(length), uintptr(advice), 0, 0)
    if errno != 0 {
        return errno
    }
    return nil
}

func count(s []byte) int64 {
    count := int64(0)
    for i := 0; i < len(s); i++ {
        if s[i] == NewLineByte {
            count++
        }
    }
    return count
}

func main() {

    file, err := os.Open("big.txt")
    if err != nil {
        panic(err)
    }

    var lines int64
    var bytes int64

    fadvise(file, 0, 0, POSIX_FADV_SEQUENTIAL)
    for {

        n, err := file.Read(Buffer)
        if err != nil && err != io.EOF {
            panic(err)
        }

        lines += count(Buffer[:n])
        bytes += int64(n)

        if err == io.EOF {
            break
        }
    }

    fmt.Printf("%d\n", bytes)
    fmt.Printf("%d\n", lines)
}

2 个答案:

答案 0 :(得分:1)

这是关于在文件中计算'\n',这个代码在Zorin VM(VMWare播放器),6 GB RAM,4个核心(&amp;)上以~1.26秒(并且大多数更快)运行。电源插入;因为电源管理器有时会阻止CPU过快消耗电池),主机操作系统是Windows 8.我在一些现实世界的项目中使用Go不到6个月,我也是Linux noob。但是我认为问题是从Go调用C并且比纯Go慢得多 - 我在调用一些C代码时遇到过这种情况,dll并且编译为{{1} }。

cgo

答案 1 :(得分:1)

Here's使用bytes.IndexByte(因为你发现Go的asm实现有帮助)和syscall.Mmap

,这不太难,也不太慢。
package main

import (
    "bytes"
    "fmt"
    "log"
    "os"
    "syscall"
)

func main() {
    if len(os.Args) < 2 {
        log.Fatal("pass filename on command line")
    }
    f, err := os.Open(os.Args[1])
    if err != nil {
        log.Fatal("open: ", err)
    }
    stat, err := f.Stat()
    if err != nil {
        log.Fatal("stat: ", err)

    }
    data, err := syscall.Mmap(int(f.Fd()), 0, int(stat.Size()), syscall.PROT_READ, syscall.MAP_SHARED)
    if err != nil {
        log.Fatal("mmap: ", err)
    }
    newlines := 0
    for {
        i := bytes.IndexByte(data, 10)
        if i == -1 {
            break
        }
        newlines++
        data = data[i+1:]
    }
    fmt.Println(newlines)
}

Mmap看起来很奇怪,但在这里它就好像你将文件读入片中一样,除非由于操作系统的帮助而资源消耗较少。

You can parallelize the counting没有太多工作,但我不确定这是值得的。 (如果amd64的增益为零或为负,例如单核计数受内存带宽限制,但我不能快速测试,那就不会让我感到震惊。)