高效的两段式循环缓冲区──BipBuffer(2)

原文地址:http://www.codeproject.com/KB/IP/bipbuffer.aspx

 

继续上一篇的内容。。。


4 The Advantages of the Circular Buffer 使用循环缓冲区的优点
There are a number of key advantages to using a circular buffer for the temporary storage of data.When one puts data into a block of memory, one also has to take it out again to make use of it. (Or one can use it in place). It is useful to be able to make use of the data in the buffer while more data is being appended to the buffer. However, as one frees up space at the head of the buffer, that space is no longer usable, unless one copies all of the data in the buffer which has not yet been used to the beginning of the buffer. This frees up space at the end of the buffer, allowing more data to be appended.
使用循环缓冲区存储临时数据的优点不可胜数。当开发者把数据存入一块临时缓冲区以后,他一定会在什么时候为了使用这个数据而把数据读取出来(或者直接使用临时缓冲区中的数据)。在缓冲区中直接操作和使用数据的同时,缓冲区需要支持继续向数据末尾追加数据,这个特性是非常有用的。但是在通常情况下,缓冲区前面部分的数据一旦被释放,这部分缓冲区就不再可用,除非把缓冲区后部的所有没有用到的数据都向前移动到缓冲区的起始位置。
 
There are a couple of ways around this; one can simply copy the data (which is a reasonably expensive proposition), or one can extend the buffer to allow more data to be appended (a massively expensive process).With a circular buffer, the free space in the buffer is always available to have data appended into it; the data is copied, the pointer adjusted, and that's that. No copying, no reallocation, no worries. The buffer is allocated once, and then remains useful for its entire life.
围绕着临时缓冲区的使用方法,有很多种不同的做法;最简单的一种就是直接把数据拷贝出来使用(这当然是一种效率非常低下的做法),或者动态扩展缓冲区的长度(这涉及到系统的内存分配和释放,是效率更加低下的做法)。而使用循环缓冲区,可以总是有可用的内存来写入数据;数据可以任意拷贝,可以直接通过指针来对数据进行存取等等。缓冲区只需要分配一次即可在其整个生存周期中发挥作用,不需要从新分配,不需要拷贝数据(译者,这里的不需要拷贝数据是指,Bip-Buffer是支持直接用指针来写入数据的,因此不需要内存拷贝之类的操作。)
 
5 A Fly In The Ointment 循环缓冲区美中不足的地方
One could simply implement a circular buffer by allocating a chunk of memory, and maintaining pointers. When one walked off the end of the buffer, the pointer would be adjusted - and this operation would be reflected in every operation that is performed, whether copying data into the buffer or removing it. Length calculations are slightly more complicated than normal, but not overly so - simple inline functions take care of that problem with ease, sweeping it under the rug.
Unfortunately, most API calls don't believe in circular buffers. You have to pass them a single contiguous block of memory which they can access, and there is no way for you to modify their write behavior to adjust pointers when they cross the end of the buffer. So what to do? Well, this is where the Bip-Buffer comes in.
人们可以通过简单的分配一块内存,小心地维护指向其的指针来实现一个循环缓冲区。当数据写入到缓冲区的末尾的时候,应该将数据写入指针调整到缓冲区起始,这个看似简单的操作影响到对缓冲区的所有操作,无论是把数据写入还是读出。缓冲区中已用数据的长度的计算要比往常复杂一点,效率会稍微低一点(但是这一点可以通过inline函数很轻易地解决),可以不足为虑。
不幸的是,绝大多数的api调用是不支持循环缓冲区的,很多情况下,人们不得不为这些api传入连续的装满数据的内存区,在api函数使用这块内存区的时候是无法调整其读取或写入指针到循环缓冲区的开头的。那该如何是好?别着急,这就是Bip-Buffer存在的理由。
 
6 Enter The Bip-Buffer 进一步了解Bip-Buffer
If one cannot pass a circular buffer into an API function, then one needs an alternative that will work - preferably with many of the same advantages as the circular buffer. It is possible to build a circular buffer which is split into two regions - or which is bi-partite (and that's how you get the Bip in a Bip-Buffer). Each of the two regions move through the buffer, starting at the left and ending up at the right hand side. When one runs out of space for appending data, if there is only one region, a new one is created at the beginning (if possible). The diagram below shows how it works in more detail.
如果人们不能直接把循环缓冲区直接做为参数传入api函数,那么就需要另外一种解决方案——这种方案是一个既要拥有与循环缓冲区一样的优点,又要没有上述缺点的方案。建立一个循环缓冲区,但是在缓冲区内部将分配的两次内存,内部维护个两个区域;或者简单地来说,将传统的为循环缓冲区分配的一块内存分裂成两块内存缓冲区来使用,这里的分裂(bi-partite)也是Bip-Buffer前面的Bip这个缩写的由来。两块内存区都是从左往右来写入数据的,在申请缓冲区的时候,当一个缓冲区满了,就会马上从另外一个内存区的起始地点开始写入,这样就避免了调整头指针的问题。下面的图例就明确地显示了它是如何工作的:

 

 

The buffer starts out empty, with no regions present (figure 1). (eg. immediately after calling AllocatedBuffer)
整个缓冲区开始的时候是空的,没有分裂成两个区域(如图1所示,是调用AllocatedBuffer()函数以后的样子)

Then, when data is first put into the buffer, a single region (the 'A' region) is created (figure 2). (Say, by calling Reserve followed by Commit)
然后,当第一次将数据放到缓冲区以后(如图2所示,这是调用了Commit()函数以后的样子),创建了第一个区域(图中的'A'区域)

Data is added to the region, extending it to the right (figure 3).
当有数据继续写入的时候,都会被追加到区域A的数据的后面,使其向右不断伸展(如图3所示)

For the first time now, we remove data from the buffer (figure 4). (see the DecommitBlock call described below)
第一次从缓冲区中读出数据的样子(如图4所示,参见下面的关于DecommitBlock()函数的说明)
 
This continues until the region reaches the end of the buffer (figure 4). Once more free space is available to the left of region A than to the right of it, a second region (comically named "region B") is created in that space. The choice to create a new region when more space is available on the left is made to maximize the potential free space available for use in the buffer. The upshot of all this leaves us with something which looks rather like figure 5.
这种形式的写入和读出过程会持续下去,直到区域到达了缓冲区的末尾(如图4所示)。一旦在区域A左侧的可用空闲空间要大于区域A右侧的空闲空间,第二个区域(我们叫做区域B)就被创建出来,从此以后的内存分配就从这个区域B来分配(如图5所示)。

If we now use up more of the buffer space, we end up with figure 6, with new space only being allocated from the end of region B. If we eventually allocate enough data to use up all of the free space between regions A and B (figure 7), we no longer have any usable space in the buffer, and no more reservations can be performed until we read some data out of it.
如果继续使用缓冲区的空间,我们就会看到如图6所示的样子,区域B在不断扩展,将数据追加到区域B的后面。如果用光区域B与区域A之间的空间(如图7所示),那么就真的没有内存可分配了,直到有一些数据从循环缓冲区中读出。

If we then read more data out of the buffer (say the entire remaining contents of region A), we exhaust it entirely. At the point, as region A is completely empty, we no longer need to track two separate regions, and all of region B's internal data is copied over region A's internal data, and region B is entirely cleared. (figure 8)
如果从缓冲区中读取更多的数据出来(假设整个区域A的数据都被读取出来),我们完全把区域A中存储的数据用光了,这时候区域A就完全空了,我们就不再需要维护两个独立的区域了,这时,区域B就获得了整个缓冲区,可以继续追加数据,同时也支持从区域B中向外读取数据了(如图8所示)。

If we read a little more data out of the buffer, we now end up with something a lot like figure 4, and the cycle continues.
如果从缓冲区中读取更多的数据(如图9所示),我们最终又会得到类似图4所示的存储区使用状态,然后继续重复4,5,6,7,8这些状态的切换,完成循环利用缓冲区。
 
(未完待续。。。)

阅读更多
个人分类: c/c++/vc
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭