# HG changeset patch # User Chris Cannam # Date 1552400040 0 # Node ID b2048f35090640ddf6453e3e528df21459fc9cec # Parent 73bda079567a6c298636cfd554ca1095b604c91f Switch EventSeries to using a vector for m_events, so as to allow indexed access diff -r 73bda079567a -r b2048f350906 base/EventSeries.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/EventSeries.cpp Tue Mar 12 14:14:00 2019 +0000 @@ -0,0 +1,290 @@ +/* -*- 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 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 "EventSeries.h" + +bool +EventSeries::isEmpty() const +{ + return m_events.empty(); +} + +int +EventSeries::count() const +{ + if (m_events.size() > INT_MAX) { + throw std::runtime_error("too many events"); + } + return int(m_events.size()); +} + +void +EventSeries::add(const Event &p) +{ + bool isUnique = true; + + auto pitr = lower_bound(m_events.begin(), m_events.end(), p); + if (pitr != m_events.end() && *pitr == p) { + isUnique = false; + } + m_events.insert(pitr, p); + + if (p.hasDuration() && isUnique) { + + const sv_frame_t frame = p.getFrame(); + const sv_frame_t endFrame = p.getFrame() + p.getDuration(); + + createSeam(frame); + createSeam(endFrame); + + // These calls must both succeed after calling createSeam above + const auto i0 = m_seams.find(frame); + const auto i1 = m_seams.find(endFrame); + + for (auto i = i0; i != i1; ++i) { + if (i == m_seams.end()) { + SVCERR << "ERROR: EventSeries::add: " + << "reached end of seam map" + << endl; + break; + } + i->second.push_back(p); + } + } + +#ifdef DEBUG_EVENT_SERIES + std::cerr << "after add:" << std::endl; + dumpEvents(); + dumpSeams(); +#endif +} + +void +EventSeries::remove(const Event &p) +{ + // If we are removing the last (unique) example of an event, + // then we also need to remove it from the seam map. If this + // is only one of multiple identical events, then we don't. + bool isUnique = true; + + auto pitr = lower_bound(m_events.begin(), m_events.end(), p); + if (pitr == m_events.end() || *pitr != p) { + // we don't know this event + return; + } else { + auto nitr = pitr; + ++nitr; + if (nitr != m_events.end() && *nitr == p) { + isUnique = false; + } + } + + m_events.erase(pitr); + + if (p.hasDuration() && isUnique) { + + const sv_frame_t frame = p.getFrame(); + const sv_frame_t endFrame = p.getFrame() + p.getDuration(); + + const auto i0 = m_seams.find(frame); + const auto i1 = m_seams.find(endFrame); + +#ifdef DEBUG_EVENT_SERIES + // This should be impossible if we found p in m_events above + if (i0 == m_seams.end() || i1 == m_seams.end()) { + SVCERR << "ERROR: EventSeries::remove: either frame " << frame + << " or endFrame " << endFrame + << " for event not found in seam map: event is " + << p.toXmlString() << endl; + } +#endif + + // Remove any and all instances of p from the seam map; we + // are only supposed to get here if we are removing the + // last instance of p from the series anyway + + for (auto i = i0; i != i1; ++i) { + if (i == m_seams.end()) { + // This can happen only if we have a negative + // duration, which Event forbids + throw std::logic_error("unexpectedly reached end of map"); + } + for (size_t j = 0; j < i->second.size(); ) { + if (i->second[j] == p) { + i->second.erase(i->second.begin() + j); + } else { + ++j; + } + } + } + + // Tidy up by removing any entries that are now identical + // to their predecessors + + std::vector redundant; + + auto pitr = m_seams.end(); + if (i0 != m_seams.begin()) { + pitr = i0; + --pitr; + } + + for (auto i = i0; i != m_seams.end(); ++i) { + if (pitr != m_seams.end() && + seamsEqual(i->second, pitr->second)) { + redundant.push_back(i->first); + } + pitr = i; + if (i == i1) { + break; + } + } + + for (sv_frame_t f: redundant) { + m_seams.erase(f); + } + + // And remove any empty seams from the start of the map + + while (m_seams.begin() != m_seams.end() && + m_seams.begin()->second.empty()) { + m_seams.erase(m_seams.begin()); + } + } + +#ifdef DEBUG_EVENT_SERIES + std::cerr << "after remove:" << std::endl; + dumpEvents(); + dumpSeams(); +#endif +} + +bool +EventSeries::contains(const Event &p) const +{ + return binary_search(m_events.begin(), m_events.end(), p); +} + +void +EventSeries::clear() +{ + m_events.clear(); + m_seams.clear(); +} + +EventVector +EventSeries::getEventsSpanning(sv_frame_t frame, + sv_frame_t duration) const +{ + EventVector span; + + const sv_frame_t start = frame; + const sv_frame_t end = frame + duration; + + // first find any zero-duration events + + auto pitr = lower_bound(m_events.begin(), m_events.end(), + Event(start)); + while (pitr != m_events.end() && pitr->getFrame() < end) { + if (!pitr->hasDuration()) { + span.push_back(*pitr); + } + ++pitr; + } + + // now any non-zero-duration ones from the seam map + + std::set found; + auto sitr = m_seams.lower_bound(start); + if (sitr == m_seams.end() || sitr->first > start) { + if (sitr != m_seams.begin()) { + --sitr; + } + } + while (sitr != m_seams.end() && sitr->first < end) { + for (const auto &p: sitr->second) { + found.insert(p); + } + ++sitr; + } + for (const auto &p: found) { + auto pitr = lower_bound(m_events.begin(), m_events.end(), p); + while (pitr != m_events.end() && *pitr == p) { + span.push_back(p); + ++pitr; + } + } + + return span; +} + +EventVector +EventSeries::getEventsCovering(sv_frame_t frame) const +{ + EventVector cover; + + // first find any zero-duration events + + auto pitr = lower_bound(m_events.begin(), m_events.end(), + Event(frame)); + while (pitr != m_events.end() && pitr->getFrame() == frame) { + if (!pitr->hasDuration()) { + cover.push_back(*pitr); + } + ++pitr; + } + + // now any non-zero-duration ones from the seam map + + std::set found; + auto sitr = m_seams.lower_bound(frame); + if (sitr == m_seams.end() || sitr->first > frame) { + if (sitr != m_seams.begin()) { + --sitr; + } + } + if (sitr != m_seams.end() && sitr->first <= frame) { + for (const auto &p: sitr->second) { + found.insert(p); + } + ++sitr; + } + for (const auto &p: found) { + auto pitr = lower_bound(m_events.begin(), m_events.end(), p); + while (pitr != m_events.end() && *pitr == p) { + cover.push_back(p); + ++pitr; + } + } + + return cover; +} + +void +EventSeries::toXml(QTextStream &out, + QString indent, + QString extraAttributes) const +{ + out << indent << QString("\n") + .arg(getObjectExportId(this)) + .arg(extraAttributes); + + for (const auto &p: m_events) { + p.toXml(out, indent + " "); + } + + out << indent << "\n"; +} + + diff -r 73bda079567a -r b2048f350906 base/EventSeries.h --- a/base/EventSeries.h Tue Mar 12 14:11:06 2019 +0000 +++ b/base/EventSeries.h Tue Mar 12 14:14:00 2019 +0000 @@ -16,6 +16,7 @@ #define SV_EVENT_SERIES_H #include "Event.h" +#include "XmlExportable.h" #include @@ -40,10 +41,10 @@ * the number of events already existing within its extent. Add events * in order of start frame if possible. */ -class EventSeries +class EventSeries : public XmlExportable { public: - EventSeries() : m_count(0) { } + EventSeries() { } ~EventSeries() =default; EventSeries(const EventSeries &) =default; @@ -54,161 +55,12 @@ return m_events == other.m_events; } - void add(const Event &p) { - - bool isUnique = true; - - auto pitr = m_events.find(p); - if (pitr != m_events.end()) { - isUnique = false; - } - - ++m_events[p]; - ++m_count; - - if (p.hasDuration() && isUnique) { - - const sv_frame_t frame = p.getFrame(); - const sv_frame_t endFrame = p.getFrame() + p.getDuration(); - - createSeam(frame); - createSeam(endFrame); - - // These calls must both succeed after calling createSeam above - const auto i0 = m_seams.find(frame); - const auto i1 = m_seams.find(endFrame); - - for (auto i = i0; i != i1; ++i) { - if (i == m_seams.end()) { - SVCERR << "ERROR: EventSeries::add: " - << "reached end of seam map" - << endl; - break; - } - i->second.push_back(p); - } - } - -#ifdef DEBUG_EVENT_SERIES - std::cerr << "after add:" << std::endl; - dumpEvents(); - dumpSeams(); -#endif - } - - void remove(const Event &p) { - - // If we are removing the last (unique) example of an event, - // then we also need to remove it from the seam map. If this - // is only one of multiple identical events, then we don't. - bool isUnique = false; - - auto pitr = m_events.find(p); - if (pitr == m_events.end()) { - return; // we don't know this event - } else { - if (--(pitr->second) == 0) { - isUnique = true; - m_events.erase(pitr); - } - --m_count; - } - - if (p.hasDuration() && isUnique) { - - const sv_frame_t frame = p.getFrame(); - const sv_frame_t endFrame = p.getFrame() + p.getDuration(); - - const auto i0 = m_seams.find(frame); - const auto i1 = m_seams.find(endFrame); - -#ifdef DEBUG_EVENT_SERIES - // This should be impossible if we found p in m_events above - if (i0 == m_seams.end() || i1 == m_seams.end()) { - SVCERR << "ERROR: EventSeries::remove: either frame " << frame - << " or endFrame " << endFrame - << " for event not found in seam map: event is " - << p.toXmlString() << endl; - } -#endif - - // Remove any and all instances of p from the seam map; we - // are only supposed to get here if we are removing the - // last instance of p from the series anyway - - for (auto i = i0; i != i1; ++i) { - if (i == m_seams.end()) { - // This can happen only if we have a negative - // duration, which Event forbids - throw std::logic_error("unexpectedly reached end of map"); - } - for (size_t j = 0; j < i->second.size(); ) { - if (i->second[j] == p) { - i->second.erase(i->second.begin() + j); - } else { - ++j; - } - } - } - - // Tidy up by removing any entries that are now identical - // to their predecessors - - std::vector redundant; - - auto pitr = m_seams.end(); - if (i0 != m_seams.begin()) { - pitr = i0; - --pitr; - } - - for (auto i = i0; i != m_seams.end(); ++i) { - if (pitr != m_seams.end() && - seamsEqual(i->second, pitr->second)) { - redundant.push_back(i->first); - } - pitr = i; - if (i == i1) { - break; - } - } - - for (sv_frame_t f: redundant) { - m_seams.erase(f); - } - - // And remove any empty seams from the start of the map - - while (m_seams.begin() != m_seams.end() && - m_seams.begin()->second.empty()) { - m_seams.erase(m_seams.begin()); - } - } - -#ifdef DEBUG_EVENT_SERIES - std::cerr << "after remove:" << std::endl; - dumpEvents(); - dumpSeams(); -#endif - } - - bool contains(const Event &p) { - return m_events.find(p) != m_events.end(); - } - - int count() const { - return m_count; - } - - bool isEmpty() const { - return m_count == 0; - } - - void clear() { - m_events.clear(); - m_seams.clear(); - m_count = 0; - } + void clear(); + void add(const Event &p); + void remove(const Event &p); + bool contains(const Event &p) const; + bool isEmpty() const; + int count() const; /** * Retrieve all events any part of which falls within the span in @@ -228,51 +80,7 @@ * both of which are excluded from the former. */ EventVector getEventsSpanning(sv_frame_t frame, - sv_frame_t duration) const { - EventVector span; - - const sv_frame_t start = frame; - const sv_frame_t end = frame + duration; - - // first find any zero-duration events - - auto pitr = m_events.lower_bound(Event(start, QString())); - while (pitr != m_events.end() && pitr->first.getFrame() < end) { - if (!pitr->first.hasDuration()) { - for (int i = 0; i < pitr->second; ++i) { - span.push_back(pitr->first); - } - } - ++pitr; - } - - // now any non-zero-duration ones from the seam map - - std::set found; - auto sitr = m_seams.lower_bound(start); - if (sitr == m_seams.end() || sitr->first > start) { - if (sitr != m_seams.begin()) { - --sitr; - } - } - while (sitr != m_seams.end() && sitr->first < end) { - for (const auto &p: sitr->second) { - found.insert(p); - } - ++sitr; - } - for (const auto &p: found) { - int n = m_events.at(p); - if (n < 1) { - throw std::logic_error("event is in seams but not events"); - } - for (int i = 0; i < n; ++i) { - span.push_back(p); - } - } - - return span; - } + sv_frame_t duration) const; /** * Retrieve all events that cover the given frame. An event without @@ -281,73 +89,32 @@ * equal to it and its end frame (start + duration) is greater * than it. */ - EventVector getEventsCovering(sv_frame_t frame) const { - EventVector cover; + EventVector getEventsCovering(sv_frame_t frame) const; - // first find any zero-duration events - - auto pitr = m_events.lower_bound(Event(frame, QString())); - while (pitr != m_events.end() && pitr->first.getFrame() == frame) { - if (!pitr->first.hasDuration()) { - for (int i = 0; i < pitr->second; ++i) { - cover.push_back(pitr->first); - } - } - ++pitr; - } - - // now any non-zero-duration ones from the seam map - - std::set found; - auto sitr = m_seams.lower_bound(frame); - if (sitr == m_seams.end() || sitr->first > frame) { - if (sitr != m_seams.begin()) { - --sitr; - } - } - if (sitr != m_seams.end() && sitr->first <= frame) { - for (const auto &p: sitr->second) { - found.insert(p); - } - ++sitr; - } - for (const auto &p: found) { - int n = m_events.at(p); - if (n < 1) { - throw std::logic_error("event is in seams but not events"); - } - for (int i = 0; i < n; ++i) { - cover.push_back(p); - } - } - - return cover; - } - + /** + * Emit to XML as a dataset element. + */ + void toXml(QTextStream &out, + QString indent, + QString extraAttributes) const override; + private: /** - * Total number of events in the series. Will exceed - * m_events.size() if the series contains duplicate events. + * This vector contains all events in the series, in the normal + * sort order. For backward compatibility we must support series + * containing multiple instances of identical events, so + * consecutive events in this vector will not always be distinct. + * The vector is used in preference to a multiset or map in order to allow indexing by "row number" as well as by + * properties such as frame. + * + * Because events are immutable, we do not have to worry about the + * order changing once an event is inserted - we only add or + * delete them. */ - int m_count; - - /** - * The (ordered) Events map acts as a list of all the events in - * the series. For reasons of backward compatibility, we have to - * handle series containing multiple instances of identical - * events; the int indexed against each event records the number - * of instances of that event. We do this in preference to using a - * multiset, in order to support the case in which we have - * obtained a set of distinct events (from the seam container - * below) and then need to return the correct number of each. - * - * Because events are immutable, we never have to move one to a - * different "key" in this map - we only add or delete them or - * change their counts. - */ - typedef std::map Events; + typedef std::vector Events; Events m_events; - + /** * The FrameEventMap maps from frame number to a set of events. In * the seam map this is used to represent the events that are @@ -423,7 +190,7 @@ void dumpEvents() const { std::cerr << "EVENTS (" << m_events.size() << ") [" << std::endl; for (const auto &i: m_events) { - std::cerr << " " << i.second << "x " << i.first.toXmlString(); + std::cerr << " " << i.toXmlString(); } std::cerr << "]" << std::endl; } diff -r 73bda079567a -r b2048f350906 base/test/StressEventSeries.h --- a/base/test/StressEventSeries.h Tue Mar 12 14:11:06 2019 +0000 +++ b/base/test/StressEventSeries.h Tue Mar 12 14:14:00 2019 +0000 @@ -39,10 +39,14 @@ void short_n(int n) { clock_t start = clock(); + std::set ee; EventSeries s; for (int i = 0; i < n; ++i) { float value = float(rand()) / float(RAND_MAX); Event e(rand(), value, 1000, QString("event %1").arg(i)); + ee.insert(e); + } + for (const Event &e: ee) { s.add(e); } QCOMPARE(s.count(), n); @@ -52,10 +56,14 @@ void longish_n(int n) { clock_t start = clock(); + std::set ee; EventSeries s; for (int i = 0; i < n; ++i) { float value = float(rand()) / float(RAND_MAX); Event e(rand(), value, rand() / 1000, QString("event %1").arg(i)); + ee.insert(e); + } + for (const Event &e: ee) { s.add(e); } QCOMPARE(s.count(), n); @@ -71,73 +79,6 @@ void longish_3() { longish_n(1000); } void longish_4() { longish_n(10000); } void longish_5() { longish_n(100000); } - - /* - -(T540p, Core i5-4330M @ 2.80GHz, 16G) - -cf5196881e3e: - - Time for 1000 short events = 1.169ms - Time for 10000 short events = 20.566ms - Time for 100000 short events = 279.242ms - Time for 1000000 short events = 3925.06ms - Time for 1000 longish events = 1.938ms - Time for 10000 longish events = 72.209ms - Time for 100000 longish events = 6469.26ms - -Totals: 9 passed, 0 failed, 0 skipped, 0 blacklisted, 12785ms - -13.40user 0.37system 0:13.84elapsed 99%CPU (0avgtext+0avgdata 1052000maxresident)k -0inputs+40outputs (0major+260249minor)pagefaults 0swaps - - -dcd510bd89db: - - Time for 1000 short events = 1.824ms - Time for 10000 short events = 19.203ms - Time for 100000 short events = 270.631ms - Time for 1000000 short events = 4425.2ms - Time for 1000 longish events = 2.395ms - Time for 10000 longish events = 83.623ms - Time for 100000 longish events = 5958.28ms - -Totals: 9 passed, 0 failed, 0 skipped, 0 blacklisted, 13116ms - -13.64user 0.26system 0:13.98elapsed 99%CPU (0avgtext+0avgdata 948104maxresident)k -0inputs+40outputs (0major+234387minor)pagefaults 0swaps - -895186c43fce: - - Time for 1000 short events = 1.706ms - Time for 10000 short events = 23.192ms - Time for 100000 short events = 310.605ms - Time for 1000000 short events = 4675.7ms - Time for 1000 longish events = 2.186ms - Time for 10000 longish events = 760.659ms - Time for 100000 longish events = 1335.57ms - -Totals: 9 passed, 0 failed, 0 skipped, 0 blacklisted, 7804ms - -7.97user 0.29system 0:08.31elapsed 99%CPU (0avgtext+0avgdata 706388maxresident)k -0inputs+40outputs (0major+182225minor)pagefaults 0swaps - -1c21ddac220e (with simpler code): - - Time for 1000 short events = 1.12ms - Time for 10000 short events = 14.997ms - Time for 100000 short events = 238.818ms - Time for 1000000 short events = 3765.09ms - Time for 1000 longish events = 1.657ms - Time for 10000 longish events = 1130.59ms - Time for 100000 longish events = 1840.98ms - -Totals: 9 passed, 0 failed, 0 skipped, 0 blacklisted, 8081ms - -7.88user 0.23system 0:08.19elapsed 99%CPU (0avgtext+0avgdata 781688maxresident)k -0inputs+40outputs (0major+200425minor)pagefaults 0swaps - - */ }; #endif diff -r 73bda079567a -r b2048f350906 files.pri --- a/files.pri Tue Mar 12 14:11:06 2019 +0000 +++ b/files.pri Tue Mar 12 14:14:00 2019 +0000 @@ -152,6 +152,7 @@ base/ColumnOp.cpp \ base/Command.cpp \ base/Debug.cpp \ + base/EventSeries.cpp \ base/Exceptions.cpp \ base/HelperExecPath.cpp \ base/LogRange.cpp \