Line data Source code
1 :
2 : /* Copyright (c) 2006-2014, Stefan Eilemann <eile@equalizergraphics.com>
3 : * 2012, Daniel Nachbaur <danielnachbaur@gmail.com>
4 : *
5 : * This file is part of Collage <https://github.com/Eyescale/Collage>
6 : *
7 : * This library is free software; you can redistribute it and/or modify it under
8 : * the terms of the GNU Lesser General Public License version 2.1 as published
9 : * by the Free Software Foundation.
10 : *
11 : * This library is distributed in the hope that it will be useful, but WITHOUT
12 : * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 : * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14 : * details.
15 : *
16 : * You should have received a copy of the GNU Lesser General Public License
17 : * along with this library; if not, write to the Free Software Foundation, Inc.,
18 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 : */
20 :
21 : #include "bufferCache.h"
22 :
23 : #include "buffer.h"
24 : #include "bufferListener.h"
25 : #include "iCommand.h"
26 : #include "node.h"
27 :
28 : #include <lunchbox/atomic.h>
29 : #include <list>
30 :
31 : //#define PROFILE
32 : // 31300 hits, 35 misses, 297640 lookups, 126976b allocated in 31 buffers
33 : // 31300 hits, 35 misses, 49228 lookups, 135168b allocated in 34 buffers
34 : //
35 : // The buffer cache periodically frees allocated buffers to bound memory usage:
36 : // * 'minFree' buffers (given in ctor) are always kept free
37 : // * above 'size >> _maxFreeShift' free buffers compaction occurs
38 : // * compaction tries to reach '(size >> _maxFreeShift) >> _targetShift' buffers
39 : //
40 : // In other words, using the values below, if more than half of the buffers are
41 : // free, the cache is compacted to until one quarter of the buffers is free.
42 :
43 : namespace co
44 : {
45 : namespace
46 : {
47 : typedef std::list< Buffer* > Data;
48 : typedef Data::const_iterator DataCIter;
49 :
50 : static const uint32_t _maxFreeShift = 1; // _maxFree = size >> shift
51 : static const uint32_t _targetShift = 1; // _targetFree = _maxFree >> shift
52 :
53 : #ifdef PROFILE
54 : static lunchbox::a_int32_t _hits;
55 : static lunchbox::a_int32_t _misses;
56 : static lunchbox::a_int32_t _lookups;
57 : static lunchbox::a_int32_t _allocs;
58 : static lunchbox::a_int32_t _frees;
59 : #endif
60 : }
61 : namespace detail
62 : {
63 : class BufferCache : public BufferListener
64 : {
65 : public:
66 105 : BufferCache( const int32_t minFree )
67 105 : : _minFree( minFree )
68 : {
69 105 : LBASSERT( minFree > 1);
70 105 : flush();
71 105 : }
72 :
73 202 : ~BufferCache()
74 202 : {
75 101 : LBASSERT( _cache.size() == 1 );
76 101 : LBASSERT( _cache.front()->isFree( ));
77 :
78 101 : delete _cache.front();
79 101 : _cache.clear();
80 202 : }
81 :
82 296 : void flush()
83 : {
84 678 : for( DataCIter i = _cache.begin(); i != _cache.end(); ++i )
85 : {
86 382 : co::Buffer* buffer = *i;
87 : //LBASSERTINFO( buffer->isFree(), *buffer );
88 382 : delete buffer;
89 : }
90 296 : LBASSERTINFO( size_t( _free ) == _cache.size(),
91 : size_t( _free ) << " != " << _cache.size() );
92 :
93 296 : _cache.clear();
94 296 : _cache.push_back( new co::Buffer( this ));
95 296 : _free = 1;
96 296 : _maxFree = _minFree;
97 296 : _position = _cache.begin();
98 296 : }
99 :
100 154065 : BufferPtr newBuffer()
101 : {
102 154065 : const uint32_t cacheSize = uint32_t( _cache.size( ));
103 154064 : LBASSERTINFO( size_t( _free ) <= cacheSize,
104 : size_t( _free ) << " > " << cacheSize );
105 :
106 154065 : if( _free > 0 )
107 : {
108 153883 : LBASSERT( cacheSize > 0 );
109 :
110 153883 : const DataCIter end = _position;
111 153883 : DataCIter& i = _position;
112 :
113 183595 : for( ++i; i != end; ++i )
114 : {
115 183595 : if( i == _cache.end( ))
116 36519 : i = _cache.begin();
117 : #ifdef PROFILE
118 : ++_lookups;
119 : #endif
120 183595 : co::Buffer* buffer = *i;
121 183595 : if( !buffer->isFree( ))
122 29712 : continue;
123 :
124 : #ifdef PROFILE
125 : const long hits = ++_hits;
126 : if( (hits%1000) == 0 )
127 : {
128 : size_t size = 0;
129 : for( DataCIter j = _cache.begin(); j != _cache.end(); ++j )
130 : size += (*j)->getMaxSize();
131 :
132 : LBINFO << _hits << "/" << _hits + _misses << " hits, "
133 : << _lookups << " lookups, " << _free << " of "
134 : << _cache.size() << " buffers free (min " << _minFree
135 : << " max " << _maxFree << "), " << _allocs
136 : << " allocs, " << _frees << " frees, "
137 : << size / 1024 << "KB" << std::endl;
138 : }
139 : #endif
140 153883 : --_free;
141 153883 : buffer->setUsed();
142 307766 : return buffer;
143 : }
144 : }
145 :
146 182 : const uint32_t add = (cacheSize >> 3) + 1;
147 376 : for( size_t j = 0; j < add; ++j )
148 194 : _cache.push_back( new co::Buffer( this ));
149 :
150 182 : _free += add - 1;
151 182 : const int32_t num = int32_t( _cache.size() >> _maxFreeShift );
152 182 : _maxFree = LB_MAX( _minFree, num );
153 182 : _position = _cache.begin();
154 :
155 : #ifdef PROFILE
156 : ++_misses;
157 : _allocs += add;
158 : #endif
159 182 : _cache.back()->setUsed();
160 182 : return _cache.back();
161 : }
162 :
163 144380 : void compact()
164 : {
165 144380 : if( _free <= _maxFree )
166 288760 : return;
167 :
168 0 : const int32_t tgt = _maxFree >> _targetShift;
169 0 : const int32_t target = LB_MAX( tgt, _minFree );
170 0 : LBASSERT( target > 0 );
171 :
172 0 : Data::iterator i = _cache.begin();
173 0 : while( i != _cache.end( ))
174 : {
175 0 : const co::Buffer* buffer = *i;
176 0 : if( buffer->isFree( ))
177 : {
178 0 : LBASSERT( _free > 0 );
179 : # ifdef PROFILE
180 : ++_frees;
181 : # endif
182 0 : i = _cache.erase( i );
183 0 : delete buffer;
184 :
185 0 : if( --_free <= target )
186 0 : break;
187 : }
188 : else
189 0 : ++i;
190 : }
191 :
192 0 : const int32_t num = int32_t( _cache.size() >> _maxFreeShift );
193 0 : _maxFree = LB_MAX( _minFree, num );
194 0 : _position = (i == _cache.end( )) ? _cache.begin() : i;
195 : }
196 :
197 : private:
198 : friend std::ostream& co::operator << (std::ostream&,const co::BufferCache&);
199 :
200 : Data _cache;
201 : DataCIter _position; //!< Last lookup position
202 : lunchbox::a_int32_t _free; //!< The current number of free items
203 :
204 : const int32_t _minFree;
205 : int32_t _maxFree; //!< The maximum number of free items
206 :
207 154062 : virtual void notifyFree( co::Buffer* )
208 : {
209 154062 : ++_free;
210 154064 : }
211 : };
212 : }
213 :
214 105 : BufferCache::BufferCache( const int32_t minFree )
215 105 : : _impl( new detail::BufferCache( minFree ))
216 105 : {}
217 :
218 202 : BufferCache::~BufferCache()
219 : {
220 101 : flush();
221 101 : delete _impl;
222 101 : }
223 :
224 191 : void BufferCache::flush()
225 : {
226 191 : _impl->flush();
227 191 : }
228 :
229 154065 : BufferPtr BufferCache::alloc( const uint64_t size )
230 : {
231 154065 : LB_TS_SCOPED( _thread );
232 154065 : LBASSERTINFO( size >= COMMAND_ALLOCSIZE, size );
233 154065 : LBASSERTINFO( size < LB_BIT48,
234 : "Out-of-sync network stream: buffer size " << size << "?" );
235 :
236 154065 : BufferPtr buffer = _impl->newBuffer();
237 154065 : LBASSERT( buffer->getRefCount() == 1 );
238 :
239 154065 : buffer->reserve( size );
240 154065 : buffer->resize( 0 );
241 154065 : return buffer;
242 : }
243 :
244 144380 : void BufferCache::compact()
245 : {
246 144380 : _impl->compact();
247 144380 : }
248 :
249 0 : std::ostream& operator << ( std::ostream& os, const BufferCache& cache )
250 : {
251 0 : const Data& buffers = cache._impl->_cache;
252 0 : os << lunchbox::disableFlush << "Cache has "
253 0 : << buffers.size() - cache._impl->_free << " used buffers:" << std::endl
254 0 : << lunchbox::indent << lunchbox::disableHeader;
255 :
256 0 : for( DataCIter i = buffers.begin(); i != buffers.end(); ++i )
257 : {
258 0 : Buffer* buffer = *i;
259 0 : if( !buffer->isFree( ))
260 0 : os << ICommand( 0, 0, buffer, false /*swap*/ ) << std::endl;
261 : }
262 0 : return os << lunchbox::enableHeader << lunchbox::exdent
263 0 : << lunchbox::enableFlush;
264 : }
265 :
266 60 : }
|