Mercurial > hg > svcore
view 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 |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This file copyright 2006 Chris Cannam. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ #include "MatrixFile.h" #include "base/TempDirectory.h" #include "base/System.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <iostream> #include <cstdio> #include <QFileInfo> #include <QDir> std::map<QString, int> MatrixFile::m_refcount; QMutex MatrixFile::m_refcountMutex; MatrixFile::MatrixFile(QString fileBase, Mode mode) : m_fd(-1), m_mode(mode), m_width(0), m_height(0), m_headerSize(2 * sizeof(size_t)), m_defaultCacheWidth(512), m_prevX(0), m_requestToken(-1), m_spareData(0) { m_cache.data = 0; QDir tempDir(TempDirectory::instance()->getPath()); QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase))); bool newFile = !QFileInfo(fileName).exists(); if (newFile && mode == ReadOnly) { std::cerr << "ERROR: MatrixFile::MatrixFile: Read-only mode " << "specified, but cache file does not exist" << std::endl; return; } int flags = 0; mode_t fmode = S_IRUSR | S_IWUSR; if (mode == ReadWrite) { flags = O_RDWR | O_CREAT; } else { flags = O_RDONLY; } if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) { ::perror("Open failed"); std::cerr << "ERROR: MatrixFile::MatrixFile: " << "Failed to open cache file \"" << fileName.toStdString() << "\""; if (mode == ReadWrite) std::cerr << " for writing"; std::cerr << std::endl; return; } if (newFile) { resize(0, 0); // write header } else { size_t header[2]; if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) { perror("Read failed"); std::cerr << "ERROR: MatrixFile::MatrixFile: " << "Failed to read header (fd " << m_fd << ", file \"" << fileName.toStdString() << "\")" << std::endl; return; } m_width = header[0]; m_height = header[1]; seekTo(0, 0); } m_fileName = fileName; m_readThread.start(); QMutexLocker locker(&m_refcountMutex); ++m_refcount[fileName]; std::cerr << "MatrixFile::MatrixFile: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl; } MatrixFile::~MatrixFile() { float *requestData = 0; if (m_requestToken >= 0) { FileReadThread::Request request; if (m_readThread.getRequest(m_requestToken, request)) { requestData = (float *)request.data; } } m_readThread.finish(); m_readThread.wait(); if (requestData) free(requestData); if (m_cache.data) free(m_cache.data); if (m_spareData) free(m_spareData); if (m_fd >= 0) { if (::close(m_fd) < 0) { ::perror("MatrixFile::~MatrixFile: close failed"); } } if (m_fileName != "") { QMutexLocker locker(&m_refcountMutex); if (--m_refcount[m_fileName] == 0) { if (::unlink(m_fileName.toLocal8Bit())) { ::perror("Unlink failed"); std::cerr << "WARNING: MatrixFile::~MatrixFile: reference count reached 0, but failed to unlink file \"" << m_fileName.toStdString() << "\"" << std::endl; } else { std::cerr << "deleted " << m_fileName.toStdString() << std::endl; } } } } size_t MatrixFile::getWidth() const { return m_width; } size_t MatrixFile::getHeight() const { return m_height; } void MatrixFile::resize(size_t w, size_t h) { if (m_mode != ReadWrite) { std::cerr << "ERROR: MatrixFile::resize called on read-only cache" << std::endl; return; } QMutexLocker locker(&m_fdMutex); off_t off = m_headerSize + (w * h * sizeof(float)); if (w * h > m_width * m_height) { if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) { ::perror("Seek failed"); std::cerr << "ERROR: MatrixFile::resize(" << w << ", " << h << "): seek failed, cannot resize" << std::endl; return; } // guess this requires efficient support for sparse files float f(0); if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) { ::perror("WARNING: MatrixFile::resize: write failed"); } } else { if (::ftruncate(m_fd, off) < 0) { ::perror("WARNING: MatrixFile::resize: ftruncate failed"); } } m_width = 0; m_height = 0; if (::lseek(m_fd, 0, SEEK_SET) == (off_t)-1) { ::perror("ERROR: MatrixFile::resize: Seek to write header failed"); return; } size_t header[2]; header[0] = w; header[1] = h; if (::write(m_fd, header, 2 * sizeof(size_t)) != 2 * sizeof(size_t)) { ::perror("ERROR: MatrixFile::resize: Failed to write header"); return; } m_width = w; m_height = h; seekTo(0, 0); } void MatrixFile::reset() { if (m_mode != ReadWrite) { std::cerr << "ERROR: MatrixFile::reset called on read-only cache" << std::endl; return; } QMutexLocker locker(&m_fdMutex); float *emptyCol = new float[m_height]; for (size_t y = 0; y < m_height; ++y) emptyCol[y] = 0.f; seekTo(0, 0); for (size_t x = 0; x < m_width; ++x) setColumnAt(x, emptyCol); delete[] emptyCol; } float MatrixFile::getValueAt(size_t x, size_t y) { float value = 0.f; if (getValuesFromCache(x, y, 1, &value)) return value; ssize_t r = 0; // std::cout << "MatrixFile::getValueAt(" << x << ", " << y << ")" // << ": reading the slow way" << std::endl; m_fdMutex.lock(); if (seekTo(x, y)) { r = ::read(m_fd, &value, sizeof(float)); } m_fdMutex.unlock(); if (r < 0) { ::perror("MatrixFile::getValueAt: Read failed"); } if (r != sizeof(float)) { value = 0.f; } return value; } void MatrixFile::getColumnAt(size_t x, float *values) { if (getValuesFromCache(x, 0, m_height, values)) return; ssize_t r = 0; std::cout << "MatrixFile::getColumnAt(" << x << ")" << ": reading the slow way" << std::endl; m_fdMutex.lock(); if (seekTo(x, 0)) { r = ::read(m_fd, values, m_height * sizeof(float)); } m_fdMutex.unlock(); if (r < 0) { ::perror("MatrixFile::getColumnAt: read failed"); } } bool MatrixFile::getValuesFromCache(size_t x, size_t ystart, size_t ycount, float *values) { m_cacheMutex.lock(); if (!m_cache.data || x < m_cache.x || x >= m_cache.x + m_cache.width) { bool left = (m_cache.data && x < m_cache.x); m_cacheMutex.unlock(); primeCache(x, left); // this doesn't take effect until a later callback m_prevX = x; return false; } for (size_t y = ystart; y < ystart + ycount; ++y) { values[y - ystart] = m_cache.data[(x - m_cache.x) * m_height + y]; } m_cacheMutex.unlock(); if (m_cache.x > 0 && x < m_prevX && x < m_cache.x + m_cache.width/4) { primeCache(x, true); } if (m_cache.x + m_cache.width < m_width && x > m_prevX && x > m_cache.x + (m_cache.width * 3) / 4) { primeCache(x, false); } m_prevX = x; return true; } void MatrixFile::setValueAt(size_t x, size_t y, float value) { if (m_mode != ReadWrite) { std::cerr << "ERROR: MatrixFile::setValueAt called on read-only cache" << std::endl; return; } ssize_t w = 0; bool seekFailed = false; m_fdMutex.lock(); if (seekTo(x, y)) { w = ::write(m_fd, &value, sizeof(float)); } else { seekFailed = true; } m_fdMutex.unlock(); if (!seekFailed && w != sizeof(float)) { ::perror("WARNING: MatrixFile::setValueAt: write failed"); } //... update cache as appropriate } void MatrixFile::setColumnAt(size_t x, float *values) { if (m_mode != ReadWrite) { std::cerr << "ERROR: MatrixFile::setColumnAt called on read-only cache" << std::endl; return; } ssize_t w = 0; bool seekFailed = false; m_fdMutex.lock(); if (seekTo(x, 0)) { w = ::write(m_fd, values, m_height * sizeof(float)); } else { seekFailed = true; } m_fdMutex.unlock(); if (!seekFailed && w != ssize_t(m_height * sizeof(float))) { ::perror("WARNING: MatrixFile::setColumnAt: write failed"); } //... update cache as appropriate } void MatrixFile::primeCache(size_t x, bool goingLeft) { // std::cerr << "MatrixFile::primeCache(" << x << ", " << goingLeft << ")" << std::endl; size_t rx = x; size_t rw = m_defaultCacheWidth; size_t left = rw / 3; if (goingLeft) left = (rw * 2) / 3; if (rx > left) rx -= left; else rx = 0; if (rx + rw > m_width) rw = m_width - rx; QMutexLocker locker(&m_cacheMutex); FileReadThread::Request request; if (m_requestToken >= 0 && m_readThread.getRequest(m_requestToken, request)) { if (x >= m_requestingX && x < m_requestingX + m_requestingWidth) { if (m_readThread.isReady(m_requestToken)) { std::cerr << "last request is ready! (" << m_requestingX << ", "<< m_requestingWidth << ")" << std::endl; m_cache.x = (request.start - m_headerSize) / (m_height * sizeof(float)); m_cache.width = request.size / (m_height * sizeof(float)); std::cerr << "actual: " << m_cache.x << ", " << m_cache.width << std::endl; if (m_cache.data) { if (m_spareData) free(m_spareData); m_spareData = (char *)m_cache.data; } m_cache.data = (float *)request.data; m_readThread.done(m_requestToken); m_requestToken = -1; } // already requested something covering this area; wait for it return; } // the current request is no longer of any use m_readThread.cancel(m_requestToken); // crude way to avoid leaking the data while (!m_readThread.isCancelled(m_requestToken)) { usleep(10000); } if (m_spareData) free(m_spareData); m_spareData = request.data; m_readThread.done(m_requestToken); m_requestToken = -1; } request.fd = m_fd; request.mutex = &m_fdMutex; request.start = m_headerSize + rx * m_height * sizeof(float); request.size = rw * m_height * sizeof(float); request.data = (char *)realloc(m_spareData, rw * m_height * sizeof(float)); MUNLOCK(request.data, rw * m_height * sizeof(float)); m_spareData = 0; m_requestingX = rx; m_requestingWidth = rw; int token = m_readThread.request(request); std::cerr << "MatrixFile::primeCache: request token is " << token << " (x = " << rx << ", w = " << rw << ", left = " << goingLeft << ")" << std::endl; m_requestToken = token; } bool MatrixFile::seekTo(size_t x, size_t y) { off_t off = m_headerSize + (x * m_height + y) * sizeof(float); if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) { ::perror("Seek failed"); std::cerr << "ERROR: MatrixFile::seekTo(" << x << ", " << y << ") failed" << std::endl; return false; } return true; }