你使用的Go版本是什么( go version
)?
$ go version
go version go1.13.5 linux/amd64
这个问题在最新版本的发布中是否重现?
是的。
你正在使用什么操作系统和处理器架构( go env
)?
go env
输出
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/terasa/.cache/go-build"
GOENV="/home/terasa/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/terasa/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go-1.13"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.13/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build094455032=/tmp/go-build -gno-record-gcc-switches"
GOROOT/bin/go version: go version go1.13.5 linux/amd64
GOROOT/bin/go tool compile -V: compile version go1.13.5
uname -sr: Linux 5.3.0-3-amd64
Distributor ID: Debian
Description: Debian GNU/Linux bullseye/sid
Release: testing
Codename: bullseye
/lib/x86_64-linux-gnu/libc.so.6: GNU C Library (Debian GLIBC 2.29-7) stable release version 2.29.
gdb --version: GNU gdb (Debian 8.3.1-1) 8.3.1
你做了什么?
我只是想打印数组的格式化元素,在这个例子中,我想在我的数组的每个字节前加上一个 0x
。习惯性的C方法大致如下:print.c
:
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
unsigned char n[100000] = { 0 };
for(i=0; i<100000; i++) {
printf(" 0x%02x", n[i]);
}
printf("\n");
return 0;
}
仅在我机器上运行 time ./print
所测量的性能是
real 0m0.053s
user 0m0.018s
sys 0m0.005s
在go中采用相同的方法导致了灾难性的性能:print_loop.go
:
package main
import "fmt"
func main() {
var n [100000]byte
for _, v := range n {
fmt.Printf(" 0x%02x", v)
}
fmt.Printf("\n")
}
我尝试过 go build
,然后用 time ./print_loop
运行生成的二进制文件:
real 0m0.373s
user 0m0.142s
sys 0m0.207s
我意识到可以通过使用 "% x"
作为格式字符串,并将整个数组传递给它,就像 fmt.Printf("0x% x", n)
一样来打印字节数组的所有元素,但这不是我想要的,因为它不会在每个字节前加上 0x
。
我“碰巧”通过一些谷歌搜索( https://stackoverflow.com/questions/13422381/idiomatically-buffer-os-stdout , https://grokbase.com/t/gg/golang-nuts/158cgb3rfd/go-nuts-os-stdout-is-not-buffered= )发现 go中的 os.Stdout
是无缓冲的,所以我立即尝试使用一个 bufio.Writer
,一切都“正常”。这段代码显然要冗长得多,而且非常重要的是不要忘记刷新缓冲区,因为它显然不会在离开 main
(并因此退出)时自动发生:print_buffer.go
:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
var n [100000]byte
bufStdout := bufio.NewWriter(os.Stdout)
defer bufStdout.Flush()
for _, v := range n {
fmt.Fprintf(bufStdout, " 0x%02x", v)
}
fmt.Fprintf(bufStdout, "\n")
}
这导致:
real 0m0.046s
user 0m0.025s
sys 0m0.000s
问题在于,无论是关于 fmt.Print*
的文档,还是关于 os.Stdout
的文档,都没有提到 I/O 是无缓冲的,而那些使用习惯性的C风格打印格式化输出的人将会大吃一惊。我当然对为什么我的简单的go代码比等效的C代码表现得更差感到困惑。
你期望看到什么?
我希望文档能提到 fmt
函数中I/O操作的缓冲行为,以及对于 os.Stdin
和 os.Stdout
的文档。关于 fmt
的文档也可以链接到 os
的文档,即明确提到使用了 无缓冲 的 os.Stdout
。
你看到了什么?
什么都没有。
7条答案
按热度按时间qni6mghb1#
感谢kohlrabi的撰写。
...我立即尝试使用bufio.Writer,一切都很顺利。这段代码显然更加冗长,最重要的是不要忘记刷新缓冲区,因为它显然不会在离开主函数时自动发生:
刷新行为在
bufio
中有记录:在所有数据都被写入后,客户端应该调用Flush方法以确保所有数据都已转发到底层的io.Writer。
我认为这种程度的控制与Go的API设计一致,即通常要求程序员显式地进行操作,并包括诸如
defer
之类的工具,以使控制更加容易。我不确定是否同意明确指出
*os.File
是非缓冲的。并非所有的Go用户都来自C语言,因此他们可能不期望fmt.Printf
是缓冲的。/cc @ianlancetaylor
jw5wzhpr2#
我无法理解为什么在fmt包中添加有关缓冲的注解是有意义的。对于os包,在某个地方简短地写一两句话可能是可以的。
amrnrhlw3#
同意,浪费了大量时间在这个调试上...
xxslljrj4#
它看起来并不像无缓冲的那样工作。我用另一种语言编写的程序中的popen从一个程序中启动Go程序,并看到它随机地放置
\n
,甚至混合了顺序。浪费了几个小时。我做了Println
JSON字符串,它只是被切成两半,所以当我读取这一行时,它不完整,JSON是无效的。更重要的是,如果我做另一个Println
,它会在刚刚被毫无理由地分割的JSON的两半之间。ecfsfe2w5#
无缓冲输出意味着来自多个goroutine的并发输出可以交错。
egdjgwm86#
@ianlancetaylor 啊,好的,我以为它意味着它会立即被冲刷掉,防止混乱。
但在我的情况下,我没有做任何并发操作,我的程序很简单。它只是累积JSON字符串,然后在需要时打印出来。在这次编辑后出现了问题:
起初我是解析JSON字符串(用于其他需求),将它们存储在
[]interface{}
中,但后来我决定不需要存储解析后的它们,开始将字节序列直接存储在[][]byte
中,就像它们从stdin := bufio.NewReader(os.Stdin); stdin.ReadLine()
中来的一样。现在每当我运行这个程序时,它会在特定行的同一位置插入\n
而不是另外三个字节(两个逗号和一个数字)。如果我添加调试打印,它甚至会在中间打印出被破坏的\n
。尝试使用
os.Stdout.Write
代替,但结果完全相同。我想知道如何按顺序打印到stdout。
**更新:**其他观察结果--被破坏的行缺少前两个字节,而且只有在
array
有多个元素时才发生这种情况。rmbxnbpk7#
@Nakilon 这个问题最好在论坛上讨论,而不是在这里。请查看 https://golang.org/wiki/Questions 。谢谢。