comparison base/MatrixFileCache.cpp @ 90:c4e163f911dd

* Switch spectrogram layer over to using the new rudimentary disk-backed FFT cache
author Chris Cannam
date Wed, 03 May 2006 14:26:26 +0000
parents 7de62a884810
children 1dcf41ed3863
comparison
equal deleted inserted replaced
89:6a1803d578e0 90:c4e163f911dd
13 COPYING included with this distribution for more information. 13 COPYING included with this distribution for more information.
14 */ 14 */
15 15
16 #include "MatrixFileCache.h" 16 #include "MatrixFileCache.h"
17 #include "base/TempDirectory.h" 17 #include "base/TempDirectory.h"
18 #include "base/System.h"
18 19
19 #include <sys/types.h> 20 #include <sys/types.h>
20 #include <sys/stat.h> 21 #include <sys/stat.h>
21 #include <fcntl.h> 22 #include <fcntl.h>
22 #include <unistd.h> 23 #include <unistd.h>
26 #include <cstdio> 27 #include <cstdio>
27 28
28 #include <QFileInfo> 29 #include <QFileInfo>
29 #include <QDir> 30 #include <QDir>
30 31
32 #define HAVE_MMAP 1
33
34 #ifdef HAVE_MMAP
35 #include <sys/mman.h>
36 #endif
37
38 //!!! This class is a work in progress -- it does only as much as we
39 // need for the current SpectrogramLayer. Slated for substantial
40 // refactoring and extension.
41
31 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) : 42 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
32 m_fd(-1), 43 m_fd(-1),
33 m_off(-1),
34 m_mode(mode), 44 m_mode(mode),
35 m_width(0), 45 m_width(0),
36 m_height(0), 46 m_height(0),
47 m_headerSize(2 * sizeof(size_t)),
48 m_autoRegionWidth(2048),
49 m_off(-1),
37 m_rx(0), 50 m_rx(0),
38 m_rw(0), 51 m_rw(0),
39 m_range(0), 52 m_userRegion(false),
40 m_headerSize(2 * sizeof(size_t)) 53 m_region(0),
41 { 54 m_mmapped(false),
55 m_mmapSize(0),
56 m_mmapOff(0),
57 m_preferMmap(true)
58 {
59 // Ensure header size is a multiple of the size of our data (for
60 // alignment purposes)
61 size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
62 if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
63
42 QDir tempDir(TempDirectory::instance()->getPath()); 64 QDir tempDir(TempDirectory::instance()->getPath());
43 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); 65 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
44 bool newFile = !QFileInfo(fileName).exists(); 66 bool newFile = !QFileInfo(fileName).exists();
45 67
46 if (newFile && mode == ReadOnly) { 68 if (newFile && mode == ReadOnly) {
56 flags = O_RDWR | O_CREAT; 78 flags = O_RDWR | O_CREAT;
57 } else { 79 } else {
58 flags = O_RDONLY; 80 flags = O_RDONLY;
59 } 81 }
60 82
61 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, mode)) < 0) { 83 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
62 ::perror("Open failed"); 84 ::perror("Open failed");
63 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: " 85 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
64 << "Failed to open cache file \"" 86 << "Failed to open cache file \""
65 << fileName.toStdString() << "\""; 87 << fileName.toStdString() << "\"";
66 if (mode == ReadWrite) std::cerr << " for writing"; 88 if (mode == ReadWrite) std::cerr << " for writing";
69 91
70 if (newFile) { 92 if (newFile) {
71 resize(0, 0); // write header 93 resize(0, 0); // write header
72 } else { 94 } else {
73 size_t header[2]; 95 size_t header[2];
74 if (::read(m_fd, header, 2 * sizeof(size_t))) { 96 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
75 perror("Read failed"); 97 perror("Read failed");
76 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: " 98 std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
77 << "Failed to read header" << std::endl; 99 << "Failed to read header (fd " << m_fd << ", file \""
100 << fileName.toStdString() << "\")" << std::endl;
78 return; 101 return;
79 } 102 }
80 m_width = header[0]; 103 m_width = header[0];
81 m_height = header[1]; 104 m_height = header[1];
82 seekTo(0, 0); 105 seekTo(0, 0);
83 } 106 }
84 107
85 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << m_width << "x" << m_height << std::endl; 108 std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
86 109
87 } 110 }
88 111
89 MatrixFileCache::~MatrixFileCache() 112 MatrixFileCache::~MatrixFileCache()
90 { 113 {
114 if (m_rw > 0) {
115 if (m_mmapped) {
116 #ifdef HAVE_MMAP
117 ::munmap(m_region, m_mmapSize);
118 #endif
119 } else {
120 delete[] m_region;
121 }
122 }
123
91 if (m_fd >= 0) { 124 if (m_fd >= 0) {
92 if (::close(m_fd) < 0) { 125 if (::close(m_fd) < 0) {
93 ::perror("MatrixFileCache::~MatrixFileCache: close failed"); 126 ::perror("MatrixFileCache::~MatrixFileCache: close failed");
94 } 127 }
95 } 128 }
129
130 //!!! refcount and unlink
96 } 131 }
97 132
98 size_t 133 size_t
99 MatrixFileCache::getWidth() const 134 MatrixFileCache::getWidth() const
100 { 135 {
118 153
119 off_t off = m_headerSize + (w * h * sizeof(float)); 154 off_t off = m_headerSize + (w * h * sizeof(float));
120 155
121 if (w * h > m_width * m_height) { 156 if (w * h > m_width * m_height) {
122 157
123 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) { 158 #ifdef HAVE_MMAP
124 ::perror("Seek failed"); 159 // If we're going to mmap the file, we need to ensure it's long
125 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", " 160 // enough beforehand
126 << h << "): seek failed, cannot resize" << std::endl;
127 return;
128 }
129
130 // guess this requires efficient support for sparse files
131 161
132 float f(0); 162 if (m_preferMmap) {
133 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) { 163
134 ::perror("WARNING: MatrixFileCache::resize: write failed"); 164 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
135 } 165 ::perror("Seek failed");
166 std::cerr << "ERROR: MatrixFileCache::resize(" << w << ", "
167 << h << "): seek failed, cannot resize" << std::endl;
168 return;
169 }
170
171 // guess this requires efficient support for sparse files
172
173 float f(0);
174 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
175 ::perror("WARNING: MatrixFileCache::resize: write failed");
176 }
177 }
178 #endif
136 179
137 } else { 180 } else {
138 181
139 if (::ftruncate(m_fd, off) < 0) { 182 if (::ftruncate(m_fd, off) < 0) {
140 ::perror("MatrixFileCache::resize: ftruncate failed"); 183 ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
141 } 184 }
142 } 185 }
143 186
144 m_width = 0; 187 m_width = 0;
145 m_height = 0; 188 m_height = 0;
171 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache" 214 std::cerr << "ERROR: MatrixFileCache::reset called on read-only cache"
172 << std::endl; 215 << std::endl;
173 return; 216 return;
174 } 217 }
175 218
176 //... 219 float *emptyCol = new float[m_height];
177 } 220 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
178 221
179 void 222 seekTo(0, 0);
180 MatrixFileCache::setRangeOfInterest(size_t x, size_t width) 223 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
181 { 224
225 delete[] emptyCol;
226 }
227
228 void
229 MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
230 {
231 setRegion(x, width, true);
232 }
233
234 void
235 MatrixFileCache::clearRegionOfInterest()
236 {
237 m_userRegion = false;
182 } 238 }
183 239
184 float 240 float
185 MatrixFileCache::getValueAt(size_t x, size_t y) const 241 MatrixFileCache::getValueAt(size_t x, size_t y) const
186 { 242 {
187 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { 243 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
188 return m_range[x - m_rx][y]; 244 float *rp = getRegionPtr(x, y);
245 if (rp) return *rp;
246 } else if (!m_userRegion) {
247 if (autoSetRegion(x)) {
248 float *rp = getRegionPtr(x, y);
249 if (rp) return *rp;
250 }
189 } 251 }
190 252
191 if (!seekTo(x, y)) return 0.f; 253 if (!seekTo(x, y)) return 0.f;
192 float value; 254 float value;
193 if (::read(m_fd, &value, sizeof(float)) != sizeof(float)) { 255 ssize_t r = ::read(m_fd, &value, sizeof(float));
256 if (r != sizeof(float)) {
194 ::perror("MatrixFileCache::getValueAt: read failed"); 257 ::perror("MatrixFileCache::getValueAt: read failed");
195 return 0.f; 258 value = 0.f;
196 } 259 }
260 if (r > 0) m_off += r;
197 return value; 261 return value;
198 } 262 }
199 263
200 void 264 void
201 MatrixFileCache::getColumnAt(size_t x, float *values) const 265 MatrixFileCache::getColumnAt(size_t x, float *values) const
202 { 266 {
203 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) { 267 if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
204 for (size_t y = 0; y < m_height; ++y) { 268 float *rp = getRegionPtr(x, 0);
205 values[y] = m_range[x - m_rx][y]; 269 if (rp) {
270 for (size_t y = 0; y < m_height; ++y) {
271 values[y] = rp[y];
272 }
273 return;
274 }
275 } else if (!m_userRegion) {
276 if (autoSetRegion(x)) {
277 float *rp = getRegionPtr(x, 0);
278 if (rp) {
279 for (size_t y = 0; y < m_height; ++y) {
280 values[y] = rp[y];
281 }
282 return;
283 }
206 } 284 }
207 } 285 }
208 286
209 if (!seekTo(x, 0)) return; 287 if (!seekTo(x, 0)) return;
210 if (::read(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) { 288 ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
289 if (r != m_height * sizeof(float)) {
211 ::perror("MatrixFileCache::getColumnAt: read failed"); 290 ::perror("MatrixFileCache::getColumnAt: read failed");
212 } 291 }
213 return; 292 if (r > 0) m_off += r;
214 } 293 }
215 294
216 void 295 void
217 MatrixFileCache::setValueAt(size_t x, size_t y, float value) 296 MatrixFileCache::setValueAt(size_t x, size_t y, float value)
218 { 297 {
221 << std::endl; 300 << std::endl;
222 return; 301 return;
223 } 302 }
224 303
225 if (!seekTo(x, y)) return; 304 if (!seekTo(x, y)) return;
226 if (::write(m_fd, &value, sizeof(float)) != sizeof(float)) { 305 ssize_t w = ::write(m_fd, &value, sizeof(float));
306 if (w != sizeof(float)) {
227 ::perror("WARNING: MatrixFileCache::setValueAt: write failed"); 307 ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
228 } 308 }
229 309 if (w > 0) m_off += w;
230 //... update range as appropriate 310
311 //... update region as appropriate
231 } 312 }
232 313
233 void 314 void
234 MatrixFileCache::setColumnAt(size_t x, float *values) 315 MatrixFileCache::setColumnAt(size_t x, float *values)
235 { 316 {
238 << std::endl; 319 << std::endl;
239 return; 320 return;
240 } 321 }
241 322
242 if (!seekTo(x, 0)) return; 323 if (!seekTo(x, 0)) return;
243 if (::write(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) { 324 ssize_t w = ::write(m_fd, values, m_height * sizeof(float));
325 if (w != m_height * sizeof(float)) {
244 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed"); 326 ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
245 } 327 }
246 328 if (w > 0) m_off += w;
247 //... update range as appropriate 329
330 //... update region as appropriate
331 }
332
333 float *
334 MatrixFileCache::getRegionPtr(size_t x, size_t y) const
335 {
336 if (m_rw == 0) return 0;
337
338 float *region = m_region;
339
340 if (m_mmapOff > 0) {
341 char *cr = (char *)m_region;
342 cr += m_mmapOff;
343 region = (float *)cr;
344 }
345
346 float *ptr = &(region[(x - m_rx) * m_height + y]);
347
348 // std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
349 return ptr;
350 }
351
352 bool
353 MatrixFileCache::autoSetRegion(size_t x) const
354 {
355 size_t rx = x;
356 size_t rw = m_autoRegionWidth;
357 size_t left = rw / 4;
358 if (x < m_rx) left = (rw * 3) / 4;
359 if (rx > left) rx -= left;
360 else rx = 0;
361 if (rx + rw > m_width) rw = m_width - rx;
362 return setRegion(rx, rw, false);
363 }
364
365 bool
366 MatrixFileCache::setRegion(size_t x, size_t width, bool user) const
367 {
368 if (!user && m_userRegion) return false;
369 if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
370
371 if (m_rw > 0) {
372 if (m_mmapped) {
373 #ifdef HAVE_MMAP
374 ::munmap(m_region, m_mmapSize);
375 std::cerr << "unmapped " << m_mmapSize << " at " << m_region << std::endl;
376 #endif
377 } else {
378 delete[] m_region;
379 }
380 m_region = 0;
381 m_mmapped = false;
382 m_mmapSize = 0;
383 m_mmapOff = 0;
384 m_rw = 0;
385 }
386
387 if (width == 0) {
388 return true;
389 }
390
391 #ifdef HAVE_MMAP
392
393 if (m_preferMmap) {
394
395 size_t mmapSize = m_height * width * sizeof(float);
396 off_t offset = m_headerSize + (x * m_height) * sizeof(float);
397 int pagesize = getpagesize();
398 off_t aligned = (offset / pagesize) * pagesize;
399 size_t mmapOff = offset - aligned;
400 mmapSize += mmapOff;
401
402 m_region = (float *)
403 ::mmap(0, mmapSize, PROT_READ, MAP_PRIVATE, m_fd, aligned);
404
405 if (m_region == MAP_FAILED) {
406
407 ::perror("Mmap failed");
408 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", "
409 << width << "): Mmap(0, " << mmapSize
410 << ", " << PROT_READ << ", " << MAP_SHARED << ", " << m_fd
411 << ", " << aligned << ") failed, falling back to "
412 << "non-mmapping code for this cache" << std::endl;
413 m_preferMmap = false;
414
415 } else {
416
417 std::cerr << "mmap succeeded (offset " << aligned << ", size " << mmapSize << ", m_mmapOff " << mmapOff << ") = " << m_region << std::endl;
418
419 m_mmapped = true;
420 m_mmapSize = mmapSize;
421 m_mmapOff = mmapOff;
422 m_rx = x;
423 m_rw = width;
424 if (user) m_userRegion = true;
425 // MUNLOCK(m_region, m_mmapSize);
426 return true;
427 }
428 }
429 #endif
430
431 if (!seekTo(x, 0)) return false;
432
433 m_region = new float[width * m_height];
434
435 ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
436 if (r < 0) {
437 ::perror("Read failed");
438 std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
439 << ") failed" << std::endl;
440 delete[] m_region;
441 m_region = 0;
442 return false;
443 }
444
445 m_off += r;
446
447 if (r < width * m_height * sizeof(float)) {
448 // didn't manage to read the whole thing, but did get something
449 std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
450 << "): ";
451 width = r / (m_height * sizeof(float));
452 std::cerr << "Only got " << width << " columns" << std::endl;
453 }
454
455 m_rx = x;
456 m_rw = width;
457 if (m_rw == 0) {
458 delete[] m_region;
459 m_region = 0;
460 }
461
462 std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
463
464 if (user) m_userRegion = true;
465 if (m_rw > 0) MUNLOCK(m_region, m_rw * m_height);
466 return true;
248 } 467 }
249 468
250 bool 469 bool
251 MatrixFileCache::seekTo(size_t x, size_t y) const 470 MatrixFileCache::seekTo(size_t x, size_t y) const
252 { 471 {
253 off_t off = m_headerSize + (x * m_height + y) * sizeof(float); 472 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
254 if (off == m_off) return true; 473 if (off == m_off) return true;
474
475 if (m_mode == ReadWrite) {
476 std::cerr << "writer: ";
477 std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
478 }
255 479
256 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { 480 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
257 ::perror("Seek failed"); 481 ::perror("Seek failed");
258 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y 482 std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y
259 << ") failed" << std::endl; 483 << ") failed" << std::endl;