2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013, by the GROMACS development team, led by
5 * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6 * others, as listed in the AUTHORS file in the top-level source
7 * directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements classes in datastorage.h and paralleloptions.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_analysisdata
42 #include "datastorage.h"
49 #include "gromacs/analysisdata/abstractdata.h"
50 #include "gromacs/analysisdata/dataframe.h"
51 #include "gromacs/analysisdata/paralleloptions.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/gmxassert.h"
54 #include "gromacs/utility/uniqueptr.h"
59 /********************************************************************
60 * AnalysisDataParallelOptions
63 AnalysisDataParallelOptions::AnalysisDataParallelOptions()
64 : parallelizationFactor_(1)
69 AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFactor)
70 : parallelizationFactor_(parallelizationFactor)
72 GMX_RELEASE_ASSERT(parallelizationFactor >= 1,
73 "Invalid parallelization factor");
77 /********************************************************************
78 * AnalysisDataStorage::Impl declaration
83 //! Smart pointer type for managing a storage frame builder.
84 typedef gmx_unique_ptr<AnalysisDataStorageFrame>::type
85 AnalysisDataFrameBuilderPointer;
86 } // namespace internal
89 * Private implementation class for AnalysisDataStorage.
91 * \ingroup module_analysisdata
93 class AnalysisDataStorage::Impl
96 //! Short-hand for the internal frame data type.
97 typedef internal::AnalysisDataStorageFrameData FrameData;
98 //! Smart pointer type for managing a stored frame.
99 typedef gmx_unique_ptr<FrameData>::type FramePointer;
100 //! Short-hand for a smart pointer type to a storage frame builder.
101 typedef internal::AnalysisDataFrameBuilderPointer FrameBuilderPointer;
103 //! Shorthand for a list of data frames that are currently stored.
104 typedef std::vector<FramePointer> FrameList;
105 //! Shorthand for a list of currently unused storage frame builders.
106 typedef std::vector<FrameBuilderPointer> FrameBuilderList;
110 //! Returns whether the storage is set to use multipoint data.
111 bool isMultipoint() const;
113 * Whether storage of all frames has been requested.
115 * Storage of all frames also works as expected if \a storageLimit_ is
116 * used in comparisons directly, but this method should be used to
117 * check how to manage \a frames_.
119 bool storeAll() const
121 return storageLimit_ == std::numeric_limits<int>::max();
123 //! Returns the index of the oldest frame that may be currently stored.
124 int firstStoredIndex() const;
126 * Computes index into \a frames_ for accessing frame \p index.
128 * \param[in] index Zero-based frame index.
129 * \retval -1 if \p index is not available in \a frames_.
133 int computeStorageLocation(int index) const;
136 * Computes an index into \a frames_ that is one past the last frame
141 size_t endStorageLocation() const;
144 * Extends \a frames_ to a new size.
146 * \throws std::bad_alloc if out of memory.
148 void extendBuffer(size_t newSize);
150 * Remove oldest frame from the storage to make space for a new one.
152 * Increments \a firstFrameLocation_ and reinitializes the frame that
153 * was made unavailable by this operation.
162 * Returns a frame builder object for use with a new frame.
164 * \throws std::bad_alloc if out of memory.
166 FrameBuilderPointer getFrameBuilder();
169 * Returns whether notifications should be immediately fired.
171 * This is used to optimize multipoint handling for non-parallel cases,
172 * where it is not necessary to store even a single frame.
176 bool shouldNotifyImmediately() const
178 return isMultipoint() && storageLimit_ == 0 && pendingLimit_ == 1;
181 * Calls notification method in \a data_.
183 * \throws unspecified Any exception thrown by
184 * AbstractAnalysisData::notifyPointsAdd().
186 void notifyPointSet(const AnalysisDataPointSetRef &points);
188 * Calls notification methods for new frames.
190 * \param[in] firstLocation First frame to consider.
191 * \throws unspecified Any exception thrown by frame notification
192 * methods in AbstractAnalysisData.
194 * Notifies \a data_ of new frames (from \p firstLocation and after
195 * that) if all previous frames have already been notified.
196 * Also rotates the \a frames_ buffer as necessary.
198 void notifyNextFrames(size_t firstLocation);
199 //! Implementation for AnalysisDataStorage::finishFrame().
200 void finishFrame(int index);
203 //! Data object to use for notification calls.
204 AbstractAnalysisData *data_;
206 * Number of past frames that need to be stored.
208 * Always non-negative. If storage of all frames has been requested,
209 * this is set to a large number.
213 * Number of future frames that may need to be started.
215 * Should always be at least one.
217 * \see AnalysisDataStorage::startFrame()
221 * Data frames that are currently stored.
223 * If storage of all frames has been requested, this is simply a vector
224 * of frames up to the latest frame that has been started.
225 * In this case, \a firstFrameLocation_ is always zero.
227 * If storage of all frames is not requested, this is a ring buffer of
228 * frames of size \c n=storageLimit_+pendingLimit_+1. If a frame with
229 * index \c index is currently stored, its location is
230 * \c index%frames_.size().
231 * When at most \a storageLimit_ first frames have been finished,
232 * this contains storage for the first \c n-1 frames.
233 * When more than \a storageLimit_ first frames have been finished,
234 * the oldest stored frame is stored in the location
235 * \a firstFrameLocation_, and \a storageLimit_ frames starting from
236 * this location are the last finished frames. \a pendingLimit_ frames
237 * follow, and some of these may be in progress or finished.
238 * There is always one unused frame in the buffer, which is initialized
239 * such that when \a firstFrameLocation_ is incremented, it becomes
240 * valid. This makes it easier to rotate the buffer in concurrent
241 * access scenarions (which are not yet otherwise implemented).
244 //! Location of oldest frame in \a frames_.
245 size_t firstFrameLocation_;
247 * Currently unused frame builders.
249 * The builders are cached to avoid repeatedly allocating memory for
250 * them. Typically, there are as many builders as there are concurrent
251 * users of the storage object. Whenever a frame is started, a builder
252 * is pulled from this pool by getFrameBuilder() (a new one is created
253 * if none are available), and assigned for that frame. When that
254 * frame is finished, the builder is returned to this pool.
256 FrameBuilderList builders_;
258 * Index of next frame that will be added to \a frames_.
260 * If all frames are not stored, this will be the index of the unused
261 * frame (see \a frames_).
266 /********************************************************************
267 * AnalysisDataStorageFrameImpl declaration
274 * Internal representation for a single stored frame.
276 * It is implemented such that the frame header is always valid, i.e.,
277 * header().isValid() returns always true.
279 * Methods in this class do not throw unless otherwise indicated.
281 * \ingroup module_analysisdata
283 class AnalysisDataStorageFrameData
286 //! Shorthand for a iterator into storage value containers.
287 typedef std::vector<AnalysisDataValue>::const_iterator ValueIterator;
289 //! Indicates what operations have been performed on a frame.
292 eMissing, //!< Frame has not yet been started.
293 eStarted, //!< startFrame() has been called.
294 eFinished, //!< finishFrame() has been called.
295 eNotified //!< Appropriate notifications have been sent.
299 * Create a new storage frame.
301 * \param storageImpl Storage object this frame belongs to.
302 * \param[in] index Zero-based index for the frame.
304 AnalysisDataStorageFrameData(AnalysisDataStorage::Impl *storageImpl,
307 //! Whether the frame has been started with startFrame().
308 bool isStarted() const { return status_ >= eStarted; }
309 //! Whether the frame has been finished with finishFrame().
310 bool isFinished() const { return status_ >= eFinished; }
311 //! Whether all notifications have been sent.
312 bool isNotified() const { return status_ >= eNotified; }
313 //! Whether the frame is ready to be available outside the storage.
314 bool isAvailable() const { return status_ >= eFinished; }
316 //! Marks the frame as notified.
317 void markNotified() { status_ = eNotified; }
319 //! Returns the storage implementation object.
320 AnalysisDataStorage::Impl &storageImpl() const { return storageImpl_; }
321 //! Returns the underlying data object (for data dimensionalities etc.).
322 const AbstractAnalysisData &baseData() const { return *storageImpl().data_; }
324 //! Returns header for the frame.
325 const AnalysisDataFrameHeader &header() const { return header_; }
326 //! Returns zero-based index of the frame.
327 int frameIndex() const { return header().index(); }
328 //! Returns the number of point sets for the frame.
329 int pointSetCount() const { return pointSets_.size(); }
331 //! Clears the frame for reusing as a new frame.
332 void clearFrame(int newIndex);
334 * Initializes the frame during AnalysisDataStorage::startFrame().
336 * \param[in] header Header to use for the new frame.
337 * \param[in] builder Builder object to use.
339 void startFrame(const AnalysisDataFrameHeader &header,
340 AnalysisDataFrameBuilderPointer builder);
341 //! Returns the builder for this frame.
342 AnalysisDataStorageFrame &builder() const
344 GMX_ASSERT(builder_, "Accessing builder for not-in-progress frame");
348 * Adds a new point set to this frame.
350 void addPointSet(int dataSetIndex, int firstColumn,
351 ValueIterator begin, ValueIterator end);
353 * Finalizes the frame during AnalysisDataStorage::finishFrame().
355 * \returns The builder object used by the frame, for reusing it for
358 AnalysisDataFrameBuilderPointer finishFrame(bool bMultipoint);
360 //! Returns frame reference to this frame.
361 AnalysisDataFrameRef frameReference() const
363 return AnalysisDataFrameRef(header_, values_, pointSets_);
365 //! Returns point set reference to a given point set.
366 AnalysisDataPointSetRef pointSet(int index) const;
369 //! Storage object that contains this frame.
370 AnalysisDataStorage::Impl &storageImpl_;
371 //! Header for the frame.
372 AnalysisDataFrameHeader header_;
373 //! Values for the frame.
374 std::vector<AnalysisDataValue> values_;
375 //! Information about each point set in the frame.
376 std::vector<AnalysisDataPointSetInfo> pointSets_;
378 * Builder object for the frame.
380 * Non-NULL when the frame is in progress, i.e., has been started but
383 AnalysisDataFrameBuilderPointer builder_;
384 //! In what state the frame currently is.
387 GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisDataStorageFrameData);
390 } // namespace internal
392 /********************************************************************
393 * AnalysisDataStorage::Impl implementation
396 AnalysisDataStorage::Impl::Impl()
398 storageLimit_(0), pendingLimit_(1), firstFrameLocation_(0), nextIndex_(0)
404 AnalysisDataStorage::Impl::isMultipoint() const
406 GMX_ASSERT(data_ != NULL, "isMultipoint() called too early");
407 return data_->isMultipoint();
412 AnalysisDataStorage::Impl::firstStoredIndex() const
414 return frames_[firstFrameLocation_]->frameIndex();
419 AnalysisDataStorage::Impl::computeStorageLocation(int index) const
421 if (index < firstStoredIndex() || index >= nextIndex_)
425 return index % frames_.size();
430 AnalysisDataStorage::Impl::endStorageLocation() const
434 return frames_.size();
436 if (frames_[0]->frameIndex() == 0 || firstFrameLocation_ == 0)
438 return frames_.size() - 1;
440 return firstFrameLocation_ - 1;
445 AnalysisDataStorage::Impl::extendBuffer(size_t newSize)
447 frames_.reserve(newSize);
448 while (frames_.size() < newSize)
450 frames_.push_back(FramePointer(new FrameData(this, nextIndex_)));
453 // The unused frame should not be included in the count.
462 AnalysisDataStorage::Impl::rotateBuffer()
464 GMX_ASSERT(!storeAll(),
465 "No need to rotate internal buffer if everything is stored");
466 size_t prevFirst = firstFrameLocation_;
467 size_t nextFirst = prevFirst + 1;
468 if (nextFirst == frames_.size())
472 firstFrameLocation_ = nextFirst;
473 frames_[prevFirst]->clearFrame(nextIndex_ + 1);
478 internal::AnalysisDataFrameBuilderPointer
479 AnalysisDataStorage::Impl::getFrameBuilder()
481 if (builders_.empty())
483 return FrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
485 FrameBuilderPointer builder(move(builders_.back()));
486 builders_.pop_back();
487 return move(builder);
492 AnalysisDataStorage::Impl::notifyPointSet(const AnalysisDataPointSetRef &points)
494 data_->notifyPointsAdd(points);
499 AnalysisDataStorage::Impl::notifyNextFrames(size_t firstLocation)
501 if (firstLocation != firstFrameLocation_)
503 // firstLocation can only be zero here if !storeAll() because
504 // firstFrameLocation_ is always zero for storeAll()
506 (firstLocation == 0 ? frames_.size() - 1 : firstLocation - 1);
507 if (!frames_[prevIndex]->isNotified())
512 size_t i = firstLocation;
513 size_t end = endStorageLocation();
516 Impl::FrameData &storedFrame = *frames_[i];
517 if (!storedFrame.isFinished())
521 if (!storedFrame.isNotified())
523 data_->notifyFrameStart(storedFrame.header());
524 for (int j = 0; j < storedFrame.pointSetCount(); ++j)
526 data_->notifyPointsAdd(storedFrame.pointSet(j));
528 data_->notifyFrameFinish(storedFrame.header());
529 storedFrame.markNotified();
530 if (storedFrame.frameIndex() >= storageLimit_)
536 if (!storeAll() && i >= frames_.size())
545 AnalysisDataStorage::Impl::finishFrame(int index)
547 int storageIndex = computeStorageLocation(index);
548 GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
549 Impl::FrameData &storedFrame = *frames_[storageIndex];
550 GMX_RELEASE_ASSERT(storedFrame.isStarted(),
551 "finishFrame() called for frame before startFrame()");
552 GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
553 "finishFrame() called twice for the same frame");
554 GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
555 "Inconsistent internal frame indexing");
556 builders_.push_back(storedFrame.finishFrame(isMultipoint()));
557 if (shouldNotifyImmediately())
559 data_->notifyFrameFinish(storedFrame.header());
560 if (storedFrame.frameIndex() >= storageLimit_)
567 notifyNextFrames(storageIndex);
572 /********************************************************************
573 * AnalysisDataStorageFrame implementation
579 AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
580 AnalysisDataStorage::Impl *storageImpl,
582 : storageImpl_(*storageImpl), header_(index, 0.0, 0.0), status_(eMissing)
584 GMX_RELEASE_ASSERT(storageImpl->data_ != NULL,
585 "Storage frame constructed before data started");
586 // With non-multipoint data, the point set structure is static,
587 // so initialize it only once here.
588 if (!baseData().isMultipoint())
591 for (int i = 0; i < baseData().dataSetCount(); ++i)
593 int columnCount = baseData().columnCount(i);
594 pointSets_.push_back(
595 AnalysisDataPointSetInfo(offset, columnCount, i, 0));
596 offset += columnCount;
603 AnalysisDataStorageFrameData::clearFrame(int newIndex)
605 GMX_RELEASE_ASSERT(!builder_, "Should not clear an in-progress frame");
607 header_ = AnalysisDataFrameHeader(newIndex, 0.0, 0.0);
609 if (baseData().isMultipoint())
617 AnalysisDataStorageFrameData::startFrame(
618 const AnalysisDataFrameHeader &header,
619 AnalysisDataFrameBuilderPointer builder)
623 builder_ = move(builder);
624 builder_->data_ = this;
625 builder_->selectDataSet(0);
630 AnalysisDataStorageFrameData::addPointSet(int dataSetIndex, int firstColumn,
631 ValueIterator begin, ValueIterator end)
633 const int valueCount = end - begin;
634 if (storageImpl().shouldNotifyImmediately())
636 AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
637 dataSetIndex, firstColumn);
638 storageImpl().notifyPointSet(
639 AnalysisDataPointSetRef(header(), pointSetInfo,
640 AnalysisDataValuesRef(begin, end)));
644 pointSets_.push_back(
645 AnalysisDataPointSetInfo(values_.size(), valueCount,
646 dataSetIndex, firstColumn));
647 std::copy(begin, end, std::back_inserter(values_));
652 AnalysisDataFrameBuilderPointer
653 AnalysisDataStorageFrameData::finishFrame(bool bMultipoint)
658 GMX_RELEASE_ASSERT(static_cast<int>(pointSets_.size()) == baseData().dataSetCount(),
659 "Point sets created for non-multipoint data");
660 values_ = builder_->values_;
661 builder_->clearValues();
665 GMX_RELEASE_ASSERT(!builder_->bPointSetInProgress_,
666 "Unfinished point set");
668 AnalysisDataFrameBuilderPointer builder(move(builder_));
670 return move(builder);
674 AnalysisDataPointSetRef
675 AnalysisDataStorageFrameData::pointSet(int index) const
677 GMX_ASSERT(index >= 0 && index < pointSetCount(),
678 "Invalid point set index");
679 return AnalysisDataPointSetRef(
680 header_, pointSets_[index],
681 AnalysisDataValuesRef(values_.begin(), values_.end()));
684 } // namespace internal
686 /********************************************************************
687 * AnalysisDataStorageFrame
690 AnalysisDataStorageFrame::AnalysisDataStorageFrame(
691 const AbstractAnalysisData &data)
692 : data_(NULL), currentDataSet_(0), currentOffset_(0),
693 columnCount_(data.columnCount(0)), bPointSetInProgress_(false)
695 int totalColumnCount = 0;
696 for (int i = 0; i < data.dataSetCount(); ++i)
698 totalColumnCount += data.columnCount(i);
700 values_.resize(totalColumnCount);
704 AnalysisDataStorageFrame::~AnalysisDataStorageFrame()
710 AnalysisDataStorageFrame::clearValues()
712 if (bPointSetInProgress_)
714 std::vector<AnalysisDataValue>::iterator i;
715 for (i = values_.begin(); i != values_.end(); ++i)
720 bPointSetInProgress_ = false;
725 AnalysisDataStorageFrame::selectDataSet(int index)
727 GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
728 const AbstractAnalysisData &baseData = data_->baseData();
729 GMX_RELEASE_ASSERT(index >= 0 && index < baseData.dataSetCount(),
730 "Out of range data set index");
731 GMX_RELEASE_ASSERT(!baseData.isMultipoint() || !bPointSetInProgress_,
732 "Point sets in multipoint data cannot span data sets");
733 currentDataSet_ = index;
735 // TODO: Consider precalculating.
736 for (int i = 0; i < index; ++i)
738 currentOffset_ += baseData.columnCount(i);
740 columnCount_ = baseData.columnCount(index);
745 AnalysisDataStorageFrame::finishPointSet()
747 GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
748 GMX_RELEASE_ASSERT(data_->baseData().isMultipoint(),
749 "Should not be called for non-multipoint data");
750 if (bPointSetInProgress_)
752 std::vector<AnalysisDataValue>::const_iterator begin
753 = values_.begin() + currentOffset_;
754 std::vector<AnalysisDataValue>::const_iterator end
755 = begin + columnCount_;
757 while (begin != end && !begin->isSet())
762 while (end != begin && !(end-1)->isSet())
770 data_->addPointSet(currentDataSet_, firstColumn, begin, end);
777 AnalysisDataStorageFrame::finishFrame()
779 GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
780 data_->storageImpl().finishFrame(data_->frameIndex());
784 /********************************************************************
785 * AnalysisDataStorage
788 AnalysisDataStorage::AnalysisDataStorage()
794 AnalysisDataStorage::~AnalysisDataStorage()
800 AnalysisDataStorage::setParallelOptions(const AnalysisDataParallelOptions &opt)
802 impl_->pendingLimit_ = 2 * opt.parallelizationFactor() - 1;
807 AnalysisDataStorage::tryGetDataFrame(int index) const
809 int storageIndex = impl_->computeStorageLocation(index);
810 if (storageIndex == -1)
812 return AnalysisDataFrameRef();
814 const Impl::FrameData &storedFrame = *impl_->frames_[storageIndex];
815 if (!storedFrame.isAvailable())
817 return AnalysisDataFrameRef();
819 return storedFrame.frameReference();
824 AnalysisDataStorage::requestStorage(int nframes)
826 // Handle the case when everything needs to be stored.
829 impl_->storageLimit_ = std::numeric_limits<int>::max();
832 // Check whether an earlier call has requested more storage.
833 if (nframes < impl_->storageLimit_)
837 impl_->storageLimit_ = nframes;
843 AnalysisDataStorage::startDataStorage(AbstractAnalysisData *data)
845 // Data needs to be set before calling extendBuffer()
847 if (!impl_->storeAll())
849 impl_->extendBuffer(impl_->storageLimit_ + impl_->pendingLimit_ + 1);
854 AnalysisDataStorageFrame &
855 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
857 GMX_ASSERT(header.isValid(), "Invalid header");
858 Impl::FrameData *storedFrame;
859 if (impl_->storeAll())
861 size_t size = header.index() + 1;
862 if (impl_->frames_.size() < size)
864 impl_->extendBuffer(size);
866 storedFrame = impl_->frames_[header.index()].get();
870 int storageIndex = impl_->computeStorageLocation(header.index());
871 if (storageIndex == -1)
873 GMX_THROW(APIError("Out of bounds frame index"));
875 storedFrame = impl_->frames_[storageIndex].get();
877 GMX_RELEASE_ASSERT(!storedFrame->isStarted(),
878 "startFrame() called twice for the same frame");
879 GMX_RELEASE_ASSERT(storedFrame->frameIndex() == header.index(),
880 "Inconsistent internal frame indexing");
881 storedFrame->startFrame(header, impl_->getFrameBuilder());
882 if (impl_->shouldNotifyImmediately())
884 impl_->data_->notifyFrameStart(header);
886 return storedFrame->builder();
890 AnalysisDataStorageFrame &
891 AnalysisDataStorage::startFrame(int index, real x, real dx)
893 return startFrame(AnalysisDataFrameHeader(index, x, dx));
897 AnalysisDataStorageFrame &
898 AnalysisDataStorage::currentFrame(int index)
900 int storageIndex = impl_->computeStorageLocation(index);
901 GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
902 Impl::FrameData &storedFrame = *impl_->frames_[storageIndex];
903 GMX_RELEASE_ASSERT(storedFrame.isStarted(),
904 "currentFrame() called for frame before startFrame()");
905 GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
906 "currentFrame() called for frame after finishFrame()");
907 GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
908 "Inconsistent internal frame indexing");
909 return storedFrame.builder();
914 AnalysisDataStorage::finishFrame(int index)
916 impl_->finishFrame(index);