Line data Source code
1 :
2 : /* Copyright (c) 2006-2017, Stefan Eilemann <eile@equalizergraphics.com>
3 : * 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 <list>
29 : #include <lunchbox/atomic.h>
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 109 : explicit BufferCache(const int32_t minFree)
67 109 : : _minFree(minFree)
68 : {
69 109 : LBASSERT(minFree > 1);
70 109 : flush();
71 109 : }
72 :
73 210 : ~BufferCache()
74 210 : {
75 105 : LBASSERT(_cache.size() == 1);
76 105 : LBASSERT(_cache.front()->isFree());
77 :
78 105 : delete _cache.front();
79 105 : _cache.clear();
80 210 : }
81 :
82 308 : void flush()
83 : {
84 850 : for (DataCIter i = _cache.begin(); i != _cache.end(); ++i)
85 : {
86 542 : co::Buffer* buffer = *i;
87 : // LBASSERTINFO( buffer->isFree(), *buffer );
88 542 : delete buffer;
89 : }
90 308 : LBASSERTINFO(size_t(_free) == _cache.size(),
91 : size_t(_free) << " != " << _cache.size());
92 :
93 308 : _cache.clear();
94 308 : _cache.push_back(new co::Buffer(this));
95 308 : _free = 1;
96 308 : _maxFree = _minFree;
97 308 : _position = _cache.begin();
98 308 : }
99 :
100 116073 : BufferPtr newBuffer()
101 : {
102 116073 : const uint32_t cacheSize = uint32_t(_cache.size());
103 116073 : LBASSERTINFO(size_t(_free) <= cacheSize, size_t(_free) << " > "
104 : << cacheSize);
105 :
106 116073 : if (_free > 0)
107 : {
108 115877 : LBASSERT(cacheSize > 0);
109 :
110 115877 : const DataCIter end = _position;
111 115877 : DataCIter& i = _position;
112 :
113 141750 : for (++i; i != end; ++i)
114 : {
115 141750 : if (i == _cache.end())
116 26052 : i = _cache.begin();
117 : #ifdef PROFILE
118 : ++_lookups;
119 : #endif
120 141750 : co::Buffer* buffer = *i;
121 141750 : if (!buffer->isFree())
122 25873 : 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, " << size / 1024
137 : << "KB" << std::endl;
138 : }
139 : #endif
140 115877 : --_free;
141 115877 : buffer->setUsed();
142 231754 : return buffer;
143 : }
144 : }
145 :
146 196 : const uint32_t add = (cacheSize >> 3) + 1;
147 541 : for (size_t j = 0; j < add; ++j)
148 345 : _cache.push_back(new co::Buffer(this));
149 :
150 196 : _free += add - 1;
151 196 : const int32_t num = int32_t(_cache.size() >> _maxFreeShift);
152 196 : _maxFree = LB_MAX(_minFree, num);
153 196 : _position = _cache.begin();
154 :
155 : #ifdef PROFILE
156 : ++_misses;
157 : _allocs += add;
158 : #endif
159 196 : _cache.back()->setUsed();
160 196 : return _cache.back();
161 : }
162 :
163 144274 : void compact()
164 : {
165 144274 : if (_free <= _maxFree)
166 144274 : 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 116072 : virtual void notifyFree(co::Buffer*) { ++_free; }
208 : };
209 : }
210 :
211 109 : BufferCache::BufferCache(const int32_t minFree)
212 109 : : _impl(new detail::BufferCache(minFree))
213 : {
214 109 : }
215 :
216 210 : BufferCache::~BufferCache()
217 : {
218 105 : flush();
219 105 : delete _impl;
220 105 : }
221 :
222 199 : void BufferCache::flush()
223 : {
224 199 : _impl->flush();
225 199 : }
226 :
227 116073 : BufferPtr BufferCache::alloc(const uint64_t size)
228 : {
229 232146 : LB_TS_SCOPED(_thread);
230 116073 : LBASSERTINFO(size >= COMMAND_ALLOCSIZE, size);
231 116073 : LBASSERTINFO(size < LB_BIT48, "Out-of-sync network stream: buffer size "
232 : << size << "?");
233 :
234 116073 : BufferPtr buffer = _impl->newBuffer();
235 116073 : LBASSERT(buffer->getRefCount() == 1);
236 :
237 116073 : buffer->reserve(size);
238 116073 : buffer->resize(0);
239 232146 : return buffer;
240 : }
241 :
242 144274 : void BufferCache::compact()
243 : {
244 144274 : _impl->compact();
245 144274 : }
246 :
247 0 : std::ostream& operator<<(std::ostream& os, const BufferCache& cache)
248 : {
249 0 : const Data& buffers = cache._impl->_cache;
250 0 : os << lunchbox::disableFlush << "Cache has "
251 0 : << buffers.size() - cache._impl->_free << " used buffers:" << std::endl
252 0 : << lunchbox::indent << lunchbox::disableHeader;
253 :
254 0 : for (DataCIter i = buffers.begin(); i != buffers.end(); ++i)
255 : {
256 0 : Buffer* buffer = *i;
257 0 : if (!buffer->isFree())
258 0 : os << ICommand(0, 0, buffer) << std::endl;
259 : }
260 0 : return os << lunchbox::enableHeader << lunchbox::exdent
261 0 : << lunchbox::enableFlush;
262 : }
263 63 : }
|