Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
file_cache.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2010 Wildfire Games
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining
4  * a copy of this software and associated documentation files (the
5  * "Software"), to deal in the Software without restriction, including
6  * without limitation the rights to use, copy, modify, merge, publish,
7  * distribute, sublicense, and/or sell copies of the Software, and to
8  * permit persons to whom the Software is furnished to do so, subject to
9  * the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included
12  * in all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24  * cache of file contents (supports zero-copy IO)
25  */
26 
27 #include "precompiled.h"
29 
31 
33 #include "lib/adts/cache_adt.h"
34 #include "lib/bits.h" // round_up
38 #include "lib/sysdep/os_cpu.h" // os_cpu_PageSize
39 #include "lib/posix/posix_mman.h" // mprotect
40 
41 
42 //-----------------------------------------------------------------------------
43 // allocator
44 
45 /*
46 the biggest worry of a file cache is external fragmentation. there are two
47 basic ways to combat this:
48 1) 'defragment' periodically - move blocks around to increase
49 size of available 'holes'.
50 2) prevent fragmentation from occurring at all via
51 deliberate alloc/free policy.
52 
53 file contents are returned directly to the user (zero-copy IO), so only
54 currently unreferenced blocks can be moved. it is believed that this would
55 severely hamper defragmentation; we therefore go with the latter approach.
56 
57 the basic insight is: fragmentation occurs when a block is freed whose
58 neighbors are not free (thus preventing coalescing). this can be prevented by
59 allocating objects of similar lifetimes together. typical workloads
60 (uniform access frequency) already show such behavior: the Landlord cache
61 manager evicts files in an LRU manner, which matches the allocation policy.
62 
63 references:
64 "The Memory Fragmentation Problem - Solved?" (Johnstone and Wilson)
65 "Dynamic Storage Allocation - A Survey and Critical Review" (Johnstone and Wilson)
66 */
67 
68 // shared_ptr<u8>s must own a reference to their allocator to ensure it's extant when
69 // they are freed. it is stored in the shared_ptr deleter.
70 class Allocator;
71 typedef shared_ptr<Allocator> PAllocator;
72 
74 {
75 public:
76  FileCacheDeleter(size_t size, const PAllocator& allocator)
77  : m_size(size), m_allocator(allocator)
78  {
79  }
80 
81  // (this uses Allocator and must come after its definition)
82  void operator()(u8* mem) const;
83 
84 private:
85  size_t m_size;
87 };
88 
89 
90 // adds statistics and AllocatorChecker to a HeaderlessAllocator
91 class Allocator
92 {
93 public:
94  Allocator(size_t maxSize)
95  : m_allocator(maxSize)
96  {
97  }
98 
99  shared_ptr<u8> Allocate(size_t size, const PAllocator& pthis)
100  {
101  const size_t alignedSize = Align<maxSectorSize>(size);
102 
103  u8* mem = (u8*)m_allocator.Allocate(alignedSize);
104  if(!mem)
105  return DummySharedPtr<u8>(0); // (prevent FileCacheDeleter from seeing a null pointer)
106 
107 #ifndef NDEBUG
108  m_checker.OnAllocate(mem, alignedSize);
109 #endif
110 
111  stats_buf_alloc(size, alignedSize);
112  return shared_ptr<u8>(mem, FileCacheDeleter(size, pthis));
113  }
114 
115  void Deallocate(u8* mem, size_t size)
116  {
117  const size_t alignedSize = Align<maxSectorSize>(size);
118 
119  // (re)allow writes in case the buffer was made read-only. it would
120  // be nice to unmap the buffer, but this is not possible because
121  // HeaderlessAllocator needs to affix boundary tags.
122  (void)mprotect(mem, size, PROT_READ|PROT_WRITE);
123 
124 #ifndef NDEBUG
125  m_checker.OnDeallocate(mem, alignedSize);
126 #endif
127  m_allocator.Deallocate(mem, alignedSize);
128 
129  stats_buf_free();
130  }
131 
132 private:
134 
135 #ifndef NDEBUG
137 #endif
138 };
139 
140 
142 {
143  m_allocator->Deallocate(mem, m_size);
144 }
145 
146 
147 //-----------------------------------------------------------------------------
148 // FileCache::Impl
149 //-----------------------------------------------------------------------------
150 
151 // since users are strongly encouraged to only load/process one file at a
152 // time, there won't be many active references to cache entries. we could
153 // take advantage of this with a separate extant list, but the cache's
154 // hash map should be fast enough and this way is less work than maintaining
155 // (possibly disjunct) cached and extant lists.
156 
158 {
159 public:
160  Impl(size_t maxSize)
161  : m_allocator(new Allocator(maxSize))
162  {
163  }
164 
165  shared_ptr<u8> Reserve(size_t size)
166  {
167  // (should never happen because the VFS ensures size != 0.)
168  ENSURE(size != 0);
169 
170  // (300 iterations have been observed when reserving several MB
171  // of space in a full cache)
172  for(;;)
173  {
174  {
175  shared_ptr<u8> data = m_allocator->Allocate(size, m_allocator);
176  if(data)
177  return data;
178  }
179 
180  // remove least valuable entry from cache (if users are holding
181  // references, the contents won't actually be deallocated)
182  {
183  shared_ptr<u8> discardedData; size_t discardedSize;
184  bool removed = m_cache.remove_least_valuable(&discardedData, &discardedSize);
185  // the cache is empty, and allocation still failed.
186  // apparently the cache is full of data that's still
187  // referenced, so we can't reserve any more space.
188  if(!removed)
189  return shared_ptr<u8>();
190  }
191  }
192  }
193 
194  void Add(const VfsPath& pathname, const shared_ptr<u8>& data, size_t size, size_t cost)
195  {
196  // zero-copy cache => all users share the contents => must not
197  // allow changes. this will be reverted when deallocating.
198  (void)mprotect((void*)data.get(), size, PROT_READ);
199 
200  m_cache.add(pathname, data, size, cost);
201  }
202 
203  bool Retrieve(const VfsPath& pathname, shared_ptr<u8>& data, size_t& size)
204  {
205  // (note: don't call stats_cache because we don't know the file size
206  // in case of a cache miss; doing so is left to the caller.)
207  stats_buf_ref();
208 
209  return m_cache.retrieve(pathname, data, &size);
210  }
211 
212  void Remove(const VfsPath& pathname)
213  {
214  m_cache.remove(pathname);
215 
216  // note: we could check if someone is still holding a reference
217  // to the contents, but that currently doesn't matter.
218  }
219 
220 private:
223 
225 };
226 
227 
228 //-----------------------------------------------------------------------------
229 
231  : impl(new Impl(size))
232 {
233 }
234 
235 shared_ptr<u8> FileCache::Reserve(size_t size)
236 {
237  return impl->Reserve(size);
238 }
239 
240 void FileCache::Add(const VfsPath& pathname, const shared_ptr<u8>& data, size_t size, size_t cost)
241 {
242  impl->Add(pathname, data, size, cost);
243 }
244 
245 void FileCache::Remove(const VfsPath& pathname)
246 {
247  impl->Remove(pathname);
248 }
249 
250 bool FileCache::Retrieve(const VfsPath& pathname, shared_ptr<u8>& data, size_t& size)
251 {
252  return impl->Retrieve(pathname, data, size);
253 }
#define PROT_WRITE
Definition: wmman.h:33
#define u8
Definition: types.h:39
bool retrieve(const Key &key, Item &item, size_t *psize=0, bool refill_credit=true)
Definition: cache_adt.h:683
Impl(size_t maxSize)
Definition: file_cache.cpp:160
shared_ptr< Allocator > PAllocator
Definition: file_cache.cpp:70
int mprotect(void *addr, size_t len, int prot)
Definition: wmman.cpp:67
shared_ptr< u8 > Reserve(size_t size)
Definition: file_cache.cpp:165
bool remove_least_valuable(Item *pItem=0, size_t *pSize=0)
Definition: cache_adt.h:706
bool Retrieve(const VfsPath &pathname, shared_ptr< u8 > &data, size_t &size)
Attempt to retrieve a file&#39;s contents from the file cache.
Definition: file_cache.cpp:250
void operator()(u8 *mem) const
Definition: file_cache.cpp:141
void add(const Key &key, const Item &item, size_t size, size_t cost)
Definition: cache_adt.h:662
void Deallocate(u8 *mem, size_t size)
Definition: file_cache.cpp:115
void remove(const Key &key)
Definition: cache_adt.h:671
allocator test rig.
NOTHROW_DECLARE void * Allocate(size_t size)
Definition: headerless.cpp:757
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
CacheType m_cache
Definition: file_cache.cpp:222
PAllocator m_allocator
Definition: file_cache.cpp:86
shared_ptr< Impl > impl
Definition: file_cache.h:104
Definition: path.h:75
bool Retrieve(const VfsPath &pathname, shared_ptr< u8 > &data, size_t &size)
Definition: file_cache.cpp:203
AllocatorChecker m_checker
Definition: file_cache.cpp:136
(header-less) pool-based heap allocator provides Allocate and Deallocate without requiring in-band he...
Definition: headerless.h:49
void OnAllocate(void *p, size_t size)
Allocator(size_t maxSize)
Definition: file_cache.cpp:94
#define PROT_READ
Definition: wmman.h:32
void Remove(const VfsPath &pathname)
Remove a file&#39;s contents from the cache (if it exists).
Definition: file_cache.cpp:245
HeaderlessAllocator m_allocator
Definition: file_cache.cpp:133
#define stats_buf_ref()
Definition: file_stats.h:100
shared_ptr< u8 > Reserve(size_t size)
Reserve a chunk of the cache&#39;s memory region.
Definition: file_cache.cpp:235
#define stats_buf_free()
Definition: file_stats.h:99
Cache< VfsPath, shared_ptr< u8 > > CacheType
Definition: file_cache.cpp:221
void OnDeallocate(void *p, size_t size)
void Deallocate(void *p, size_t size)
deallocate memory.
Definition: headerless.cpp:762
void Remove(const VfsPath &pathname)
Definition: file_cache.cpp:212
shared_ptr< u8 > Allocate(size_t size, const PAllocator &pthis)
Definition: file_cache.cpp:99
void Add(const VfsPath &pathname, const shared_ptr< u8 > &data, size_t size, size_t cost=1)
Add a file&#39;s contents to the cache.
Definition: file_cache.cpp:240
FileCache(size_t size)
Definition: file_cache.cpp:230
FileCacheDeleter(size_t size, const PAllocator &allocator)
Definition: file_cache.cpp:76
#define stats_buf_alloc(size, alignedSize)
Definition: file_stats.h:98
void Add(const VfsPath &pathname, const shared_ptr< u8 > &data, size_t size, size_t cost)
Definition: file_cache.cpp:194
PAllocator m_allocator
Definition: file_cache.cpp:224