annotate base/MatrixFile.cpp @ 97:22494cc28c9f

* Reduce number of allocations and deallocations by keeping a spare buffer around (we were generally deallocating and then immediately allocating again, so it's much better not to have to bother as very large allocations can tie up the system)
author Chris Cannam
date Thu, 04 May 2006 20:17:28 +0000
parents 1aebdc68ec6d
children
rev   line source
Chris@87 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@87 2
Chris@87 3 /*
Chris@87 4 Sonic Visualiser
Chris@87 5 An audio file viewer and annotation editor.
Chris@87 6 Centre for Digital Music, Queen Mary, University of London.
Chris@87 7 This file copyright 2006 Chris Cannam.
Chris@87 8
Chris@87 9 This program is free software; you can redistribute it and/or
Chris@87 10 modify it under the terms of the GNU General Public License as
Chris@87 11 published by the Free Software Foundation; either version 2 of the
Chris@87 12 License, or (at your option) any later version. See the file
Chris@87 13 COPYING included with this distribution for more information.
Chris@87 14 */
Chris@87 15
Chris@96 16 #include "MatrixFile.h"
Chris@87 17 #include "base/TempDirectory.h"
Chris@90 18 #include "base/System.h"
Chris@87 19
Chris@87 20 #include <sys/types.h>
Chris@87 21 #include <sys/stat.h>
Chris@87 22 #include <fcntl.h>
Chris@87 23 #include <unistd.h>
Chris@87 24
Chris@87 25 #include <iostream>
Chris@87 26
Chris@87 27 #include <cstdio>
Chris@87 28
Chris@87 29 #include <QFileInfo>
Chris@87 30 #include <QDir>
Chris@87 31
Chris@96 32 std::map<QString, int> MatrixFile::m_refcount;
Chris@96 33 QMutex MatrixFile::m_refcountMutex;
Chris@91 34
Chris@96 35 MatrixFile::MatrixFile(QString fileBase, Mode mode) :
Chris@87 36 m_fd(-1),
Chris@87 37 m_mode(mode),
Chris@87 38 m_width(0),
Chris@87 39 m_height(0),
Chris@90 40 m_headerSize(2 * sizeof(size_t)),
Chris@97 41 m_defaultCacheWidth(512),
Chris@95 42 m_prevX(0),
Chris@97 43 m_requestToken(-1),
Chris@97 44 m_spareData(0)
Chris@87 45 {
Chris@95 46 m_cache.data = 0;
Chris@90 47
Chris@87 48 QDir tempDir(TempDirectory::instance()->getPath());
Chris@87 49 QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
Chris@87 50 bool newFile = !QFileInfo(fileName).exists();
Chris@87 51
Chris@87 52 if (newFile && mode == ReadOnly) {
Chris@96 53 std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode "
Chris@87 54 << "specified, but cache file does not exist" << std::endl;
Chris@87 55 return;
Chris@87 56 }
Chris@87 57
Chris@87 58 int flags = 0;
Chris@87 59 mode_t fmode = S_IRUSR | S_IWUSR;
Chris@87 60
Chris@87 61 if (mode == ReadWrite) {
Chris@87 62 flags = O_RDWR | O_CREAT;
Chris@87 63 } else {
Chris@87 64 flags = O_RDONLY;
Chris@87 65 }
Chris@87 66
Chris@90 67 if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
Chris@87 68 ::perror("Open failed");
Chris@96 69 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@87 70 << "Failed to open cache file \""
Chris@87 71 << fileName.toStdString() << "\"";
Chris@87 72 if (mode == ReadWrite) std::cerr << " for writing";
Chris@87 73 std::cerr << std::endl;
Chris@91 74 return;
Chris@87 75 }
Chris@87 76
Chris@87 77 if (newFile) {
Chris@87 78 resize(0, 0); // write header
Chris@87 79 } else {
Chris@87 80 size_t header[2];
Chris@90 81 if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
Chris@87 82 perror("Read failed");
Chris@96 83 std::cerr << "ERROR: MatrixFile::MatrixFile: "
Chris@90 84 << "Failed to read header (fd " << m_fd << ", file \""
Chris@90 85 << fileName.toStdString() << "\")" << std::endl;
Chris@87 86 return;
Chris@87 87 }
Chris@87 88 m_width = header[0];
Chris@87 89 m_height = header[1];
Chris@87 90 seekTo(0, 0);
Chris@87 91 }
Chris@87 92
Chris@91 93 m_fileName = fileName;
Chris@95 94
Chris@95 95 m_readThread.start();
Chris@95 96
Chris@91 97 QMutexLocker locker(&m_refcountMutex);
Chris@91 98 ++m_refcount[fileName];
Chris@91 99
Chris@96 100 std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
Chris@87 101
Chris@87 102 }
Chris@87 103
Chris@96 104 MatrixFile::~MatrixFile()
Chris@87 105 {
Chris@95 106 float *requestData = 0;
Chris@95 107
Chris@95 108 if (m_requestToken >= 0) {
Chris@95 109 FileReadThread::Request request;
Chris@95 110 if (m_readThread.getRequest(m_requestToken, request)) {
Chris@95 111 requestData = (float *)request.data;
Chris@95 112 }
Chris@90 113 }
Chris@90 114
Chris@95 115 m_readThread.finish();
Chris@95 116 m_readThread.wait();
Chris@95 117
Chris@97 118 if (requestData) free(requestData);
Chris@97 119 if (m_cache.data) free(m_cache.data);
Chris@97 120 if (m_spareData) free(m_spareData);
Chris@95 121
Chris@87 122 if (m_fd >= 0) {
Chris@87 123 if (::close(m_fd) < 0) {
Chris@96 124 ::perror("MatrixFile::~MatrixFile: close failed");
Chris@87 125 }
Chris@87 126 }
Chris@90 127
Chris@91 128 if (m_fileName != "") {
Chris@91 129 QMutexLocker locker(&m_refcountMutex);
Chris@91 130 if (--m_refcount[m_fileName] == 0) {
Chris@95 131 if (::unlink(m_fileName.toLocal8Bit())) {
Chris@95 132 ::perror("Unlink failed");
Chris@96 133 std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl;
Chris@91 134 } else {
Chris@91 135 std::cerr << "deleted " << m_fileName.toStdString() << std::endl;
Chris@91 136 }
Chris@91 137 }
Chris@91 138 }
Chris@87 139 }
Chris@87 140
Chris@87 141 size_t
Chris@96 142 MatrixFile::getWidth() const
Chris@87 143 {
Chris@87 144 return m_width;
Chris@87 145 }
Chris@87 146
Chris@87 147 size_t
Chris@96 148 MatrixFile::getHeight() const
Chris@87 149 {
Chris@87 150 return m_height;
Chris@87 151 }
Chris@87 152
Chris@87 153 void
Chris@96 154 MatrixFile::resize(size_t w, size_t h)
Chris@87 155 {
Chris@87 156 if (m_mode != ReadWrite) {
Chris@96 157 std::cerr << "ERROR: MatrixFile::resize called on read-only cache"
Chris@87 158 << std::endl;
Chris@87 159 return;
Chris@87 160 }
Chris@87 161
Chris@95 162 QMutexLocker locker(&m_fdMutex);
Chris@95 163
Chris@87 164 off_t off = m_headerSize + (w * h * sizeof(float));
Chris@87 165
Chris@87 166 if (w * h > m_width * m_height) {
Chris@87 167
Chris@95 168 if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
Chris@95 169 ::perror("Seek failed");
Chris@96 170 std::cerr << "ERROR: MatrixFile::resize(" << w << ", "
Chris@95 171 << h << "): seek failed, cannot resize" << std::endl;
Chris@95 172 return;
Chris@95 173 }
Chris@95 174
Chris@95 175 // guess this requires efficient support for sparse files
Chris@90 176
Chris@95 177 float f(0);
Chris@95 178 if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
Chris@96 179 ::perror("WARNING: MatrixFile::resize: write failed");
Chris@87 180 }
Chris@95 181
Chris@87 182 } else {
Chris@87 183
Chris@87 184 if (::ftruncate(m_fd, off) < 0) {
Chris@96 185 ::perror("WARNING: MatrixFile::resize: ftruncate failed");
Chris@87 186 }
Chris@87 187 }
Chris@87 188
Chris@87 189 m_width = 0;
Chris@87 190 m_height = 0;
Chris@87 191
Chris@87 192 if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) {
Chris@96 193 ::perror("ERROR: MatrixFile::resize: Seek to write header failed");
Chris@87 194 return;
Chris@87 195 }
Chris@87 196
Chris@87 197 size_t header[2];
Chris@87 198 header[0] = w;
Chris@87 199 header[1] = h;
Chris@87 200 if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) {
Chris@96 201 ::perror("ERROR: MatrixFile::resize: Failed to write header");
Chris@87 202 return;
Chris@87 203 }
Chris@87 204
Chris@87 205 m_width = w;
Chris@87 206 m_height = h;
Chris@87 207
Chris@87 208 seekTo(0, 0);
Chris@87 209 }
Chris@87 210
Chris@87 211 void
Chris@96 212 MatrixFile::reset()
Chris@87 213 {
Chris@87 214 if (m_mode != ReadWrite) {
Chris@96 215 std::cerr << "ERROR: MatrixFile::reset called on read-only cache"
Chris@87 216 << std::endl;
Chris@87 217 return;
Chris@87 218 }
Chris@87 219
Chris@95 220 QMutexLocker locker(&m_fdMutex);
Chris@95 221
Chris@90 222 float *emptyCol = new float[m_height];
Chris@90 223 for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f;
Chris@90 224
Chris@90 225 seekTo(0, 0);
Chris@90 226 for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol);
Chris@90 227
Chris@90 228 delete[] emptyCol;
Chris@87 229 }
Chris@87 230
Chris@95 231 float
Chris@96 232 MatrixFile::getValueAt(size_t x, size_t y)
Chris@87 233 {
Chris@95 234 float value = 0.f;
Chris@95 235 if (getValuesFromCache(x, y, 1, &value)) return value;
Chris@90 236
Chris@95 237 ssize_t r = 0;
Chris@87 238
Chris@96 239 // std::cout << "MatrixFile::getValueAt(" << x << ", " << y << ")"
Chris@95 240 // << ": reading the slow way" << std::endl;
Chris@95 241
Chris@95 242 m_fdMutex.lock();
Chris@95 243
Chris@95 244 if (seekTo(x, y)) {
Chris@95 245 r = ::read(m_fd, &value, sizeof(float));
Chris@87 246 }
Chris@87 247
Chris@95 248 m_fdMutex.unlock();
Chris@95 249
Chris@91 250 if (r < 0) {
Chris@96 251 ::perror("MatrixFile::getValueAt: Read failed");
Chris@91 252 }
Chris@90 253 if (r != sizeof(float)) {
Chris@90 254 value = 0.f;
Chris@87 255 }
Chris@95 256
Chris@87 257 return value;
Chris@87 258 }
Chris@87 259
Chris@87 260 void
Chris@96 261 MatrixFile::getColumnAt(size_t x, float *values)
Chris@87 262 {
Chris@95 263 if (getValuesFromCache(x, 0, m_height, values)) return;
Chris@95 264
Chris@95 265 ssize_t r = 0;
Chris@95 266
Chris@96 267 std::cout << "MatrixFile::getColumnAt(" << x << ")"
Chris@95 268 << ": reading the slow way" << std::endl;
Chris@95 269
Chris@95 270 m_fdMutex.lock();
Chris@95 271
Chris@95 272 if (seekTo(x, 0)) {
Chris@95 273 r = ::read(m_fd, values, m_height * sizeof(float));
Chris@87 274 }
Chris@87 275
Chris@95 276 m_fdMutex.unlock();
Chris@95 277
Chris@91 278 if (r < 0) {
Chris@96 279 ::perror("MatrixFile::getColumnAt: read failed");
Chris@87 280 }
Chris@95 281 }
Chris@95 282
Chris@95 283 bool
Chris@96 284 MatrixFile::getValuesFromCache(size_t x, size_t ystart, size_t ycount,
Chris@95 285 float *values)
Chris@95 286 {
Chris@95 287 m_cacheMutex.lock();
Chris@95 288
Chris@95 289 if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) {
Chris@95 290 bool left = (m_cache.data && x < m_cache.x);
Chris@95 291 m_cacheMutex.unlock();
Chris@95 292 primeCache(x, left); // this doesn't take effect until a later callback
Chris@95 293 m_prevX = x;
Chris@95 294 return false;
Chris@95 295 }
Chris@95 296
Chris@95 297 for (size_t y = ystart; y < ystart + ycount; ++y) {
Chris@95 298 values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y];
Chris@95 299 }
Chris@95 300 m_cacheMutex.unlock();
Chris@95 301
Chris@95 302 if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) {
Chris@95 303 primeCache(x, true);
Chris@95 304 }
Chris@95 305
Chris@95 306 if (m_cache.x + m_cache.width < m_width &&
Chris@95 307 x > m_prevX &&
Chris@95 308 x > m_cache.x + (m_cache.width * 3) / 4) {
Chris@95 309 primeCache(x, false);
Chris@95 310 }
Chris@95 311
Chris@95 312 m_prevX = x;
Chris@95 313 return true;
Chris@87 314 }
Chris@87 315
Chris@87 316 void
Chris@96 317 MatrixFile::setValueAt(size_t x, size_t y, float value)
Chris@87 318 {
Chris@87 319 if (m_mode != ReadWrite) {
Chris@96 320 std::cerr << "ERROR: MatrixFile::setValueAt called on read-only cache"
Chris@87 321 << std::endl;
Chris@87 322 return;
Chris@87 323 }
Chris@87 324
Chris@95 325 ssize_t w = 0;
Chris@95 326 bool seekFailed = false;
Chris@95 327
Chris@95 328 m_fdMutex.lock();
Chris@95 329
Chris@95 330 if (seekTo(x, y)) {
Chris@95 331 w = ::write(m_fd, &value, sizeof(float));
Chris@95 332 } else {
Chris@95 333 seekFailed = true;
Chris@95 334 }
Chris@95 335
Chris@95 336 m_fdMutex.unlock();
Chris@95 337
Chris@95 338 if (!seekFailed && w != sizeof(float)) {
Chris@96 339 ::perror("WARNING: MatrixFile::setValueAt: write failed");
Chris@87 340 }
Chris@87 341
Chris@95 342 //... update cache as appropriate
Chris@87 343 }
Chris@87 344
Chris@87 345 void
Chris@96 346 MatrixFile::setColumnAt(size_t x, float *values)
Chris@87 347 {
Chris@87 348 if (m_mode != ReadWrite) {
Chris@96 349 std::cerr << "ERROR: MatrixFile::setColumnAt called on read-only cache"
Chris@87 350 << std::endl;
Chris@87 351 return;
Chris@87 352 }
Chris@87 353
Chris@95 354 ssize_t w = 0;
Chris@95 355 bool seekFailed = false;
Chris@95 356
Chris@95 357 m_fdMutex.lock();
Chris@95 358
Chris@95 359 if (seekTo(x, 0)) {
Chris@95 360 w = ::write(m_fd, values, m_height * sizeof(float));
Chris@95 361 } else {
Chris@95 362 seekFailed = true;
Chris@95 363 }
Chris@95 364
Chris@95 365 m_fdMutex.unlock();
Chris@95 366
Chris@95 367 if (!seekFailed && w != ssize_t(m_height * sizeof(float))) {
Chris@96 368 ::perror("WARNING: MatrixFile::setColumnAt: write failed");
Chris@87 369 }
Chris@87 370
Chris@95 371 //... update cache as appropriate
Chris@90 372 }
Chris@90 373
Chris@95 374 void
Chris@96 375 MatrixFile::primeCache(size_t x, bool goingLeft)
Chris@90 376 {
Chris@96 377 // std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl;
Chris@90 378
Chris@95 379 size_t rx = x;
Chris@95 380 size_t rw = m_defaultCacheWidth;
Chris@90 381
Chris@95 382 size_t left = rw / 3;
Chris@95 383 if (goingLeft) left = (rw * 2) / 3;
Chris@95 384
Chris@95 385 if (rx > left) rx -= left;
Chris@95 386 else rx = 0;
Chris@95 387
Chris@95 388 if (rx + rw > m_width) rw = m_width - rx;
Chris@95 389
Chris@95 390 QMutexLocker locker(&m_cacheMutex);
Chris@95 391
Chris@96 392 FileReadThread::Request request;
Chris@96 393
Chris@96 394 if (m_requestToken >= 0 &&
Chris@96 395 m_readThread.getRequest(m_requestToken, request)) {
Chris@95 396
Chris@95 397 if (x >= m_requestingX &&
Chris@95 398 x < m_requestingX + m_requestingWidth) {
Chris@95 399
Chris@95 400 if (m_readThread.isReady(m_requestToken)) {
Chris@95 401
Chris@95 402 std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl;
Chris@95 403
Chris@96 404 m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float));
Chris@96 405 m_cache.width = request.size / (m_height * sizeof(float));
Chris@96 406
Chris@96 407 std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl;
Chris@95 408
Chris@97 409 if (m_cache.data) {
Chris@97 410 if (m_spareData) free(m_spareData);
Chris@97 411 m_spareData = (char *)m_cache.data;
Chris@97 412 }
Chris@96 413 m_cache.data = (float *)request.data;
Chris@95 414
Chris@95 415 m_readThread.done(m_requestToken);
Chris@95 416 m_requestToken = -1;
Chris@95 417 }
Chris@95 418
Chris@96 419 // already requested something covering this area; wait for it
Chris@95 420 return;
Chris@95 421 }
Chris@95 422
Chris@96 423 // the current request is no longer of any use
Chris@95 424 m_readThread.cancel(m_requestToken);
Chris@96 425
Chris@96 426 // crude way to avoid leaking the data
Chris@96 427 while (!m_readThread.isCancelled(m_requestToken)) {
Chris@96 428 usleep(10000);
Chris@96 429 }
Chris@96 430
Chris@97 431 if (m_spareData) free(m_spareData);
Chris@97 432 m_spareData = request.data;
Chris@96 433 m_readThread.done(m_requestToken);
Chris@96 434
Chris@95 435 m_requestToken = -1;
Chris@95 436 }
Chris@95 437
Chris@95 438 request.fd = m_fd;
Chris@95 439 request.mutex = &m_fdMutex;
Chris@95 440 request.start = m_headerSize + rx * m_height * sizeof(float);
Chris@95 441 request.size = rw * m_height * sizeof(float);
Chris@97 442 request.data = (char *)realloc(m_spareData, rw * m_height * sizeof(float));
Chris@96 443 MUNLOCK(request.data, rw * m_height * sizeof(float));
Chris@97 444 m_spareData = 0;
Chris@95 445
Chris@95 446 m_requestingX = rx;
Chris@95 447 m_requestingWidth = rw;
Chris@95 448
Chris@95 449 int token = m_readThread.request(request);
Chris@96 450 std::cerr << "MatrixFile::primeCache: request token is "
Chris@96 451 << token << " (x = " << rx << ", w = " << rw << ", left = " << goingLeft << ")" << std::endl;
Chris@95 452
Chris@95 453 m_requestToken = token;
Chris@95 454 }
Chris@95 455
Chris@90 456 bool
Chris@96 457 MatrixFile::seekTo(size_t x, size_t y)
Chris@87 458 {
Chris@87 459 off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
Chris@90 460
Chris@87 461 if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
Chris@87 462 ::perror("Seek failed");
Chris@96 463 std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y
Chris@87 464 << ") failed" << std::endl;
Chris@87 465 return false;
Chris@87 466 }
Chris@87 467
Chris@87 468 return true;
Chris@87 469 }
Chris@87 470