输出流缓冲的意义 何时缓冲 Stdout Buffering
**From : https://eklitzke.org/stdout-buffering **
**译者:李秋豪**
大多数编程语言默认提供了i/o缓冲特性,因为这会使得输出更加有效率。这些缓冲功能大都是默默工作“Just work out of the box”(译者注:参考out of box.)——直到某天他们不在正常工作,“不正常工作”是说该输出的数据不立即显示出来。这些问题大多可以调用fflush函数解决。例如sys.stdout.flush() in Python, fflush(3) in C, or std::flush in C++。
人们经常搞不清楚缓冲的规则,于是他们频繁的使用flush,这正是 cargo-cult programming.(译者注:直译是货物崇拜编程,看看挺有意思的,RMS先使用的)。在这篇文章中我会解释输出流的规则,你看过后就不会再感到困惑了;)
缓冲为什么存在?
正如上面谈到的,缓冲区可能导致输出延迟,那么它为什么存在呢?
在系统底层调用这个层次,数据是用write+文件描述符写入的,这种方法将数据写入到文件描述符对应的一个字节缓冲中。
大多数语言有着非常快的函数调用,在C/C++这种语言中调用一个函数可能只需要几个cpu周期,时间开销几乎可以忽略不计(只有在近端的情况下才会使用inline.)。然而,一个系统调用时间开销是非常可观的。在Linux上的一个系统调用可能会花费几千个cpu周期并掺杂着上下文转化.所以系统调用比用户空间里的函数调用花的时间多得多。
缓冲存在的主要目的就是为用户空间函数抵消调用系统函数的开销。当函数做很多写入操作时这非常重要——否则系统调用的时间会占程序运行时间的主要部分。
让我们考虑一下当你使用 grep在一个文件或者stdin中搜索字段。假设你在nginx日志中找一个IP地址,而在nginx日志里一行大概有100个字符。如果不使用缓冲的话,就意味着只要grep遇到了想要的ip地址,它就会调用write()
.,而这会接连不断的发生,并且每次写入的字符大概都在100字节。如果使用默认缓冲4096字节的话,grep会等到缓冲区满以后再调用write()
清除缓冲,这大概会读入40行才发生一次(译者注:缓冲策略是写入流/文件的充分条件,不是必要的。缓冲区存在的意义就是使用“Stream-level I/O”时从缓冲区进行异步块写入/读出,这样可以在设备堵塞或者大量的写入操作的时候加快效率。如果一次只写入少量数据,内核一看没有堵塞,“干脆”就把缓冲区的内容写入了,反正放着也是放着。满缓冲在gnu library c 中定义为以任意大小的块写入流。),所以会减少大概40倍在系统调用上花的时间。不错!
如果grep程序向标准输出写入大量的数据的话,你可能都不会注意到缓冲区造成的延迟。并且如果grep只是找一个简单的字段,通常它在输出上花的时间会比寻找该字段画的时间更多。但是假如grep匹配字段发生的非常缓慢,例如每十秒钟才发生一次,那么我们可能要等400秒才能得到一个输出!(即便在开始的时候已经匹配到几个字段)
缓冲产生的问题
缓冲可能会在不知觉中造成一些问题,特别是在unix下使用管道的时候,例如,假设我们想要打印出在一个日志文件中匹配到的第一个字段,命令可能是这样的:
# BAD: grep will buffer output before sending it to head
grep RAREPATTERN /var/log/mylog.txt | head -n 1
依据前面举得例子,我们只是想要grep立即输出它第一次匹配到的字段,但是由于缓冲区的存在,grep可能要等缓冲区满后才输出给head,这会花费更多的时间。
在很多情况下,如果输出是间断的,那么缓冲区的存在可能会严重影响程序的性能,更不用说提升效率了。
什么时候程序使用缓冲
There are typically three modes for buffering:一般有三种缓冲策略:
- 无缓冲(unbuffered),任何读写都会立刻发生(并且会产生阻塞)。
- 满缓冲(fully-buffered),会使用固定大小的缓冲区,读写都是从缓冲区发生的。缓冲区不会被清除直到它被填满。
- 行缓冲,缓冲数据直到遇见一个换行符或者达到缓冲区的固定大小。
GNU libc (glibc) 使用以下的缓冲规则:
Stream | Type | Behavior |
---|---|---|
stdin | input | line-buffered |
stdout (TTY) | output | line-buffered |
stdout (not a TTY) | output | fully-buffered |
stderr | output | unbuffered |
正如你所看到的,stdout缓冲的策略有些特别:取决于流是不是一个交互设备(tty)。理由在于,如果stdout是一个终端的话,用户通常希望看着命令运行并等待结果输出,因此及时输出数据是必要的。另一方面,如果输出流不是终端,意味着输出可以后期输出,因此效率更加重要(满缓冲)。
其他的编程语言大多有着相同的规则,要么是因为这些语言是用了libc命令,或者它们遵循了相同的逻辑。
更多关于grep的例子
Grep对于缓冲来说是一个特殊的例子,因为它能够将一个大量数据转化为缓慢并且小规模的输出。因此grep对于缓冲非常敏感。
****正如上面所说,如果grep的输出是一个交互设备(例如tty),那么输出流将会是行缓冲,如果输出到一个文件或者一个管道,那么将会是满缓冲的。
下面这个grep命令的输出将会是行缓冲,因为输出是一个交互设备(终端):
# line-buffered
grep RAREPATTERN /var/log/mylog.txt
如果stdout被重定向到一个文件而非交互设备,那么输出将会是满缓冲的。通常情况下这都是可行的:
# fully-buffered
grep RAREPATTERN /var/log/mylog.txt >output.txt
在一种情况下前面的例子不会显得理想:如果你在另一个终端输出中尝试使用tail -f
检测输出文件。
假设我们想要逆序通过管道+grep匹配子段,因为grep是最后一个命令,所以它的输出还是一个交互设备:
(译者注:tac - concatenate and print files in reverse
)
# line-buffered
tac /var/log/mylog.txt | grep RAREPATTERN
但是如果我们想要过滤grep的输出会怎样?如果我们使用一个管道,这会使得grep输出变为满缓冲。例如:
# fully-buffered
grep RAREPATTERN /var/log/mylog.txt | cut -f1
在这种情况下,grep的输出将变为pipe的文件描述符。管道不是ttys, 所以输出将变为满缓冲。
For the grep command the solution is to use the --line-buffered
option to force line-buffering:
# forced line-buffering
grep --line-buffered RAREPATTERN /var/log/mylog.txt | cut -f1
如前面提到的,你可能需要使用line-buffered参数当你重定向grep到一个文件并在另一个会话使用 tail -f
的时候。
Setbuf
如果你在写C,你可以通过使用setbuf(3)(译者注:推荐setvbuf,参见 C语言 流缓冲.)强制改变标准流的缓冲策略,你也可以在磁盘文件上使用这个函数,这样你就可以在使用像fprintf
这样的函数的时候自动是行缓冲。
Stdbuf
GNU的源代码包中有一个 stdbuf 程序,这个程序允许你改变那些你无法控制源代码的程序的缓冲策略,但是也有几个限制:这个程序必须使用的是C的文件指针流即FILE*
,并且这个程序不能使用了专门的缓冲策略函数例如上面提到的Stdbuf
.
C++ IOStreams
在C++程序里经常会看到另外一个问题,很多C++程序员习惯于使用 std::endl 来输出新的行,例如:
// Two ways to print output with a newline ending.
std::cout << "Hello, world!\n";
std::cout << "Hello, world!" << std::endl;
这两行是不一样的。关键在于std::endl
会自动强制清除输出流缓冲,不管之前它采取的是什么缓冲策略。
// Subject to normal buffering rules.
std::cout << "Hello, world!\n";
// These are equivalent and are *always* line-buffered.
std::cout << "Hello, world!\n" << std::flush;
std::cout << "Hello, world!" << std::endl;
因此,如果你大量使用 std::endl
,那么实际上缓冲策略并没有起作用—— std::endl
每次都在强制清除缓冲区!如果你在写一个很注重性能的程序这可能会很重要,因为这会不经意间导致缓冲不起作用。
我的建议是,仅仅在你真的需要清除缓冲区输出数据的时候才使用 std::endl
,如果你不认为必须清除缓冲区,那么就使用正常的输出和一个\n
吧。
输出流缓冲的意义 何时缓冲 Stdout Buffering的更多相关文章
- mysql的缓冲查询和非缓冲查询
最近在开发一个PHP程序时遇到了下面的错误: PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted 错误信息显示允许的 ...
- Golang并发编程有缓冲通道和无缓冲通道(channel)
无缓冲通道 是指在接收前没有能力保存任何值得通道.这种类型的通道要求发送goroutine和接收goroutine同时准备好,才能完成发送和接收操作.如果两个goroutine没有同时准备好,通道会导 ...
- golang的缓冲channel和无缓冲channel的区别
话说golang的channel同步的定义真是让人无力吐槽,码农的用户体验就这么难搞么,超耐磨阿,无缓冲和缓冲居然有这么大区别....靠 转载一段网上的资料 --------------------- ...
- Go语言中的有缓冲channel和无缓冲channel区别
Go语言中的有缓冲channel和无缓冲channel区别 结论 ch1:=make(chan int)// 无缓冲 ch2:=make(chan int,1)// 有缓冲 无缓冲: 当向ch1中存值 ...
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_5_BufferedWriter_字符缓冲输出流
使用newLine来换行 同样的效果 println的源码里面其实就用的就是newLine()
- 10.4 缓冲流 BufferedReader & BufferedWriter & 缓冲流特殊功能readLine
缓冲流和正常流的使用大致相同,缓冲流效率更高. package day10_io_fileWrite_Read.buffer_stream; import java.io.*; /* * Buffer ...
- Java字节缓冲流和字符缓冲流学习
1.字节缓冲流 首先要明确一个概念:对文件或其他目标频繁的读写操作,效率低,性能差. 使用缓冲流的好处是,能够高效的读写信息,原理是将数据先缓冲起来,然后一起写入或者读取出来. BufferedInp ...
- Go 缓冲信道和非缓冲信道
非缓冲信道是一个进一个出,再一个进再一个出,信道内是不保存数据的 缓冲信道是可以很多个依次进去,存储在信道里,然后一个一个的按次序取出来. 不过缓冲信道如果超过了预期的存入个数,会发生信道阻塞,只有把 ...
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_3_BufferedInputStream_字节缓冲
内容改成abc 来个数组缓冲
随机推荐
- Python中的矩阵、多维数组:Numpy
Numpy 是Python中科学计算的核心库.它提供一个高性能多维数据对象,以及操作这个对象的工具.部分功能如下: ndarray, 具有矢量算术运算和复杂广播能力的快速且节省空间的多维数组. 用于对 ...
- 洛谷P3070 [USACO13JAN]岛游记Island Travels
P3070 [USACO13JAN]岛游记Island Travels 题目描述 Farmer John has taken the cows to a vacation out on the oce ...
- Unity---DOTween插件学习(2)---设置参数、Ease曲线、回调函数、动画控制函数
目录 6.Set设置参数 7.Ease曲线 8.回调函数 9.动画控制函数 本文及系列参考于Andy老师的DOTween系列 欢迎大家关注Andy老师 6.Set设置参数 在Unity中添加一个Cub ...
- 根据从redis缓存的数据查询出来,在从数据库中取出所有的数据,俩个数据进行比较,去掉重复,剩下库中新插入的数据,取出新数据,然后把redis中的缓存数据清空把从数据库中查出来的所有数据放到redis缓存中
参考代码: public String getNewCenter(HttpServletRequest request,HttpServletResponse resonse){ JSONObject ...
- abp架构中加载公共css样式表和公共js的文件目录位置
src\shared\helpers\LocalizedResourcesHelper.ts
- 我在B站学习 清华大学教授带你学习c++(进阶)构造函数
B站av11459203的一系列视频,跳过了基础篇直接进入进阶,从此难度开始加大.这里做出一些笔记分享一下. 我是1.25速度看的..对应分P 37-38 构造函数的作用 将对象初始化为一个特定的初始 ...
- Java local 转UTC时间
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; impor ...
- DevOps的工程化
孙敬云 --Worktile高级系统架构师,WTC成员 1.研发的困境 互联网的环境 互联网这个环境比较特别,包括现在不只是互联网,就算是被互联网赋能的这些“互联网+”的企业也在改变,用户在发生变化, ...
- 使用fastcgi_cache加速你的Nginx网站
很久以前在TW上挖了个坑,说nginx的fastcgi_cache是被大家忽视的一大金矿,今天把这个坑填上. 对于变化不太频繁的数据,大家都比较喜欢存Memcached以减少数据库的读取,但还是会有语 ...
- NewStar 信息分发系统设计
目录 NewStar 信息分发系统设计 我想要怎么做 系统流程图 解释和初步的模板展示 NewStar 信息分发系统设计 我们在全世界发布网站本质就是向全世界分发我们的信息给客户/潜在客户,然后希望促 ...