diff 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
line wrap: on
line diff
--- a/base/MatrixFileCache.cpp	Wed May 03 11:15:46 2006 +0000
+++ b/base/MatrixFileCache.cpp	Wed May 03 14:26:26 2006 +0000
@@ -15,6 +15,7 @@
 
 #include "MatrixFileCache.h"
 #include "base/TempDirectory.h"
+#include "base/System.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -28,17 +29,38 @@
 #include <QFileInfo>
 #include <QDir>
 
+#define HAVE_MMAP 1
+
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+//!!! This class is a work in progress -- it does only as much as we
+// need for the current SpectrogramLayer.  Slated for substantial
+// refactoring and extension.
+
 MatrixFileCache::MatrixFileCache(QString fileBase, Mode mode) :
     m_fd(-1),
-    m_off(-1),
     m_mode(mode),
     m_width(0),
     m_height(0),
+    m_headerSize(2 * sizeof(size_t)),
+    m_autoRegionWidth(2048),
+    m_off(-1),
     m_rx(0),
     m_rw(0),
-    m_range(0),
-    m_headerSize(2 * sizeof(size_t))
+    m_userRegion(false),
+    m_region(0),
+    m_mmapped(false),
+    m_mmapSize(0),
+    m_mmapOff(0),
+    m_preferMmap(true)
 {
+    // Ensure header size is a multiple of the size of our data (for
+    // alignment purposes)
+    size_t hs = ((m_headerSize / sizeof(float)) * sizeof(float));
+    if (hs != m_headerSize) m_headerSize = hs + sizeof(float);
+
     QDir tempDir(TempDirectory::instance()->getPath());
     QString fileName(tempDir.filePath(QString("%1.mfc").arg(fileBase)));
     bool newFile = !QFileInfo(fileName).exists();
@@ -58,7 +80,7 @@
         flags = O_RDONLY;
     }
 
-    if ((m_fd = ::open(fileName.toLocal8Bit(), flags, mode)) < 0) {
+    if ((m_fd = ::open(fileName.toLocal8Bit(), flags, fmode)) < 0) {
         ::perror("Open failed");
         std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
                   << "Failed to open cache file \""
@@ -71,10 +93,11 @@
         resize(0, 0); // write header
     } else {
         size_t header[2];
-        if (::read(m_fd, header, 2 * sizeof(size_t))) {
+        if (::read(m_fd, header, 2 * sizeof(size_t)) < 0) {
             perror("Read failed");
             std::cerr << "ERROR: MatrixFileCache::MatrixFileCache: "
-                      << "Failed to read header" << std::endl;
+                      << "Failed to read header (fd " << m_fd << ", file \""
+                      << fileName.toStdString() << "\")" << std::endl;
             return;
         }
         m_width = header[0];
@@ -82,17 +105,29 @@
         seekTo(0, 0);
     }
 
-    std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << m_width << "x" << m_height << std::endl;
+    std::cerr << "MatrixFileCache::MatrixFileCache: Done, size is " << "(" << m_width << ", " << m_height << ")" << std::endl;
 
 }
 
 MatrixFileCache::~MatrixFileCache()
 {
+    if (m_rw > 0) {
+        if (m_mmapped) {
+#ifdef HAVE_MMAP
+            ::munmap(m_region, m_mmapSize);
+#endif
+        } else {
+            delete[] m_region;
+        }
+    }
+
     if (m_fd >= 0) {
         if (::close(m_fd) < 0) {
             ::perror("MatrixFileCache::~MatrixFileCache: close failed");
         }
     }
+
+    //!!! refcount and unlink
 }
 
 size_t 
@@ -120,24 +155,32 @@
 
     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: MatrixFileCache::resize(" << w << ", "
-                      << h << "): seek failed, cannot resize" << std::endl;
-            return;
+#ifdef HAVE_MMAP
+        // If we're going to mmap the file, we need to ensure it's long
+        // enough beforehand
+        
+        if (m_preferMmap) {
+        
+            if (::lseek(m_fd, off - sizeof(float), SEEK_SET) == (off_t)-1) {
+                ::perror("Seek failed");
+                std::cerr << "ERROR: MatrixFileCache::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: MatrixFileCache::resize: write failed");
+            }
         }
-
-        // guess this requires efficient support for sparse files
-        
-        float f(0);
-        if (::write(m_fd, &f, sizeof(float)) != sizeof(float)) {
-            ::perror("WARNING: MatrixFileCache::resize: write failed");
-        }
+#endif
 
     } else {
         
         if (::ftruncate(m_fd, off) < 0) {
-            ::perror("MatrixFileCache::resize: ftruncate failed");
+            ::perror("WARNING: MatrixFileCache::resize: ftruncate failed");
         }
     }
 
@@ -173,27 +216,48 @@
         return;
     }
     
-    //...
+    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;
 }
 
 void
-MatrixFileCache::setRangeOfInterest(size_t x, size_t width)
+MatrixFileCache::setRegionOfInterest(size_t x, size_t width)
 {
+    setRegion(x, width, true);
+}
+
+void
+MatrixFileCache::clearRegionOfInterest()
+{
+    m_userRegion = false;
 }
 
 float
 MatrixFileCache::getValueAt(size_t x, size_t y) const
 {
     if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
-        return m_range[x - m_rx][y];
+        float *rp = getRegionPtr(x, y);
+        if (rp) return *rp;
+    } else if (!m_userRegion) {
+        if (autoSetRegion(x)) {
+            float *rp = getRegionPtr(x, y);
+            if (rp) return *rp;
+        }
     }
 
     if (!seekTo(x, y)) return 0.f;
     float value;
-    if (::read(m_fd, &value, sizeof(float)) != sizeof(float)) {
+    ssize_t r = ::read(m_fd, &value, sizeof(float));
+    if (r != sizeof(float)) {
         ::perror("MatrixFileCache::getValueAt: read failed");
-        return 0.f;
+        value = 0.f;
     }
+    if (r > 0) m_off += r;
     return value;
 }
 
@@ -201,16 +265,31 @@
 MatrixFileCache::getColumnAt(size_t x, float *values) const
 {
     if (m_rw > 0 && x >= m_rx && x < m_rx + m_rw) {
-        for (size_t y = 0; y < m_height; ++y) {
-            values[y] = m_range[x - m_rx][y];
+        float *rp = getRegionPtr(x, 0);
+        if (rp) {
+            for (size_t y = 0; y < m_height; ++y) {
+                values[y] = rp[y];
+            }
+            return;
+        }
+    } else if (!m_userRegion) {
+        if (autoSetRegion(x)) {
+            float *rp = getRegionPtr(x, 0);
+            if (rp) {
+                for (size_t y = 0; y < m_height; ++y) {
+                    values[y] = rp[y];
+                }
+                return;
+            }
         }
     }
 
     if (!seekTo(x, 0)) return;
-    if (::read(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) {
+    ssize_t r = ::read(m_fd, values, m_height * sizeof(float));
+    if (r != m_height * sizeof(float)) {
         ::perror("MatrixFileCache::getColumnAt: read failed");
     }
-    return;
+    if (r > 0) m_off += r;
 }
 
 void
@@ -223,11 +302,13 @@
     }
 
     if (!seekTo(x, y)) return;
-    if (::write(m_fd, &value, sizeof(float)) != sizeof(float)) {
+    ssize_t w = ::write(m_fd, &value, sizeof(float));
+    if (w != sizeof(float)) {
         ::perror("WARNING: MatrixFileCache::setValueAt: write failed");
     }
+    if (w > 0) m_off += w;
 
-    //... update range as appropriate
+    //... update region as appropriate
 }
 
 void
@@ -240,11 +321,149 @@
     }
 
     if (!seekTo(x, 0)) return;
-    if (::write(m_fd, values, m_height * sizeof(float)) != m_height * sizeof(float)) {
+    ssize_t w = ::write(m_fd, values, m_height * sizeof(float));
+    if (w != m_height * sizeof(float)) {
         ::perror("WARNING: MatrixFileCache::setColumnAt: write failed");
     }
+    if (w > 0) m_off += w;
 
-    //... update range as appropriate
+    //... update region as appropriate
+}
+
+float *
+MatrixFileCache::getRegionPtr(size_t x, size_t y) const
+{
+    if (m_rw == 0) return 0;
+
+    float *region = m_region;
+
+    if (m_mmapOff > 0) {
+        char *cr = (char *)m_region;
+        cr += m_mmapOff;
+        region = (float *)cr;
+    }
+
+    float *ptr = &(region[(x - m_rx) * m_height + y]);
+    
+//    std::cerr << "getRegionPtr(" << x << "," << y << "): region is " << m_region << ", returning " << ptr << std::endl;
+    return ptr;
+}
+
+bool
+MatrixFileCache::autoSetRegion(size_t x) const
+{
+    size_t rx = x;
+    size_t rw = m_autoRegionWidth;
+    size_t left = rw / 4;
+    if (x < m_rx) left = (rw * 3) / 4;
+    if (rx > left) rx -= left;
+    else rx = 0;
+    if (rx + rw > m_width) rw = m_width - rx;
+    return setRegion(rx, rw, false);
+}
+
+bool
+MatrixFileCache::setRegion(size_t x, size_t width, bool user) const
+{
+    if (!user && m_userRegion) return false;
+    if (m_rw > 0 && x >= m_rx && x + width <= m_rx + m_rw) return true;
+
+    if (m_rw > 0) {
+        if (m_mmapped) {
+#ifdef HAVE_MMAP
+            ::munmap(m_region, m_mmapSize);
+            std::cerr << "unmapped " << m_mmapSize << " at " << m_region << std::endl;
+#endif
+        } else {
+            delete[] m_region;
+        }
+        m_region = 0;
+        m_mmapped = false;
+        m_mmapSize = 0;
+        m_mmapOff = 0;
+        m_rw = 0;
+    }
+
+    if (width == 0) {
+        return true;
+    }
+
+#ifdef HAVE_MMAP
+
+    if (m_preferMmap) {
+
+        size_t mmapSize = m_height * width * sizeof(float);
+        off_t offset = m_headerSize + (x * m_height) * sizeof(float);
+        int pagesize = getpagesize();
+        off_t aligned = (offset / pagesize) * pagesize;
+        size_t mmapOff = offset - aligned;
+        mmapSize += mmapOff;
+        
+        m_region = (float *)
+            ::mmap(0, mmapSize, PROT_READ, MAP_PRIVATE, m_fd, aligned);
+        
+        if (m_region == MAP_FAILED) {
+            
+            ::perror("Mmap failed");
+            std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", "
+                      << width << "): Mmap(0, " << mmapSize
+                      << ", " << PROT_READ << ", " << MAP_SHARED << ", " << m_fd 
+                      << ", " << aligned << ") failed, falling back to "
+                      << "non-mmapping code for this cache" << std::endl;
+            m_preferMmap = false;
+            
+        } else {
+
+            std::cerr << "mmap succeeded (offset " << aligned << ", size " << mmapSize << ", m_mmapOff " << mmapOff << ") = " << m_region << std::endl;
+            
+            m_mmapped = true;
+            m_mmapSize = mmapSize;
+            m_mmapOff = mmapOff;
+            m_rx = x;
+            m_rw = width;
+            if (user) m_userRegion = true;
+//            MUNLOCK(m_region, m_mmapSize);
+            return true;
+        }
+    }
+#endif
+
+    if (!seekTo(x, 0)) return false;
+
+    m_region = new float[width * m_height];
+
+    ssize_t r = ::read(m_fd, m_region, width * m_height * sizeof(float));
+    if (r < 0) {
+        ::perror("Read failed");
+        std::cerr << "ERROR: MatrixFileCache::setRegion(" << x << ", " << width
+                  << ") failed" << std::endl;
+        delete[] m_region;
+        m_region = 0;
+        return false;
+    }
+    
+    m_off += r;
+
+    if (r < width * m_height * sizeof(float)) {
+        // didn't manage to read the whole thing, but did get something
+        std::cerr << "WARNING: MatrixFileCache::setRegion(" << x << ", " << width
+                  << "): ";
+        width = r / (m_height * sizeof(float));
+        std::cerr << "Only got " << width << " columns" << std::endl;
+    }
+
+    m_rx = x;
+    m_rw = width;
+    if (m_rw == 0) {
+        delete[] m_region;
+        m_region = 0;
+    }
+
+    std::cerr << "MatrixFileCache::setRegion: set region to " << x << ", " << width << std::endl;
+
+    if (user) m_userRegion = true;
+    if (m_rw > 0) MUNLOCK(m_region, m_rw * m_height);
+    return true;
 }
 
 bool
@@ -253,6 +472,11 @@
     off_t off = m_headerSize + (x * m_height + y) * sizeof(float);
     if (off == m_off) return true;
 
+    if (m_mode == ReadWrite) {
+        std::cerr << "writer: ";
+        std::cerr << "seek required (from " << m_off << " to " << off << ")" << std::endl;
+    }
+
     if (::lseek(m_fd, off, SEEK_SET) == (off_t)-1) {
         ::perror("Seek failed");
         std::cerr << "ERROR: MatrixFileCache::seekTo(" << x << ", " << y