Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix slow C3DFileAdpaterReading issue #1698 #1730

Merged
merged 10 commits into from
May 25, 2017

Conversation

aseth1
Copy link
Member

@aseth1 aseth1 commented May 25, 2017

Fixes issue #1698
DataTable::appendRow() is slow because it is resizing the underlying SimTK::Matrix on each call, which requires reallocation and copying of the entire matrix.

Brief summary of changes

Added a protected DataTable_ constructor that takes the independent vector, data matrix and labels so that appendRow() can be avoided.

TimeSeriesTable_ exposes the convenience constructor and performs the validation and cleanup should validation fail.

C3DFileAdpater::extendRead() allocates the full time vector and data matrix first and then updates their elements and the uses the new constructor to create the TimeSeriesTable avoiding

Testing I've completed

Profiled testC3DFileAdapter prior to change the bottle neck was inside DataTable_::appendRow():
87.1 % -> _depData.resizeKeep(_depData.nrow() + 1, _depData.ncol()); (~1600ms)

After the change, the creation and updating of the matrix and construction of the full TimeSeriesTable in C3DFileAdpater::extendRead() now only uses 3.1 % of the total runtime (~34ms).

And testC3DFileAdapter passes.

Looking for feedback on...

CHANGELOG.md (choose one)

  • no need to update because change required an update to implementation details.

The Doxygen for this PR can be viewed at http://myosin.sourceforge.net/?C=N;O=D; click the folder whose name is this PR's number.

Ajay Seth added 5 commits May 24, 2017 17:49
…e data and labels supplied.

Derived classes can use this to avoid appendRow(), which resized the internal matrix.
…rom already populated time vector and data matrix.
…Table constructor and populating a pre-allocated SimTK::Matrix without resizing.
Copy link
Member

@chrisdembia chrisdembia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice speedup. See my comments; all are minor.

@@ -141,6 +141,29 @@ class TimeSeriesTable_ : public DataTable_<double, ETY> {
TimeSeriesTable_& operator=(const TimeSeriesTable_&) = default;
TimeSeriesTable_& operator=(TimeSeriesTable_&&) = default;
~TimeSeriesTable_() = default;

/** Convenience Constructor to efficiently populate a time series table
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructor -> constructor.

// wipe out the data loaded if any
_indData.clear();
_depData.clear();
removeDependentsMetaDataForKey("labels");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to rethrow?

DataTable_<double, ETY>(indVec, depData, labels) {
try {
// Perform the validation of the data a TimeSeriesTable
this->validateDependentsMetaData();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you added a comment in the base class about validation. Could you add a little more detail, either in the doxygen description for this constructor or for the one in the base class, explaining the situation with calling virtual functions from constructors?

const SimTK::Matrix_<ETY>& depData,
const std::vector<std::string>& labels) {
OPENSIM_THROW_IF(indVec.size() != depData.nrow(), InvalidArgument,
"Length of independent column does not match number of rows of dependent data.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

80 columns?

marker_table.appendRow(0 + f * time_step, row);

marker_matrix.updRow(f) = row;
marker_times[f] = 0 + f * time_step; // pt->GetValues();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you either provide more detail about the comment or remove it?


// Create the data
auto& marker_table = *new
TimeSeriesTableVec3 (marker_times, marker_matrix, marker_labels);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space between Vec3 and (

for(int f = 0;
f < fp_force_pts->GetFrontItem()->GetFrameNumber();
++f) {
for(int f = 0; f < force_times.size(); ++f) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using nf instead of force_times.size().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A straight replacement would cause a compiler warning (type mismatch). Would need to do

for (int f = 0; f < static_cast<int>(nf); ++f)

et->GetFrame(),
et->GetDescription() });
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason for moving this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it read better to get the events first, which are then added to both the marker and force tables that are created in the two code blocks below. I also thought I might be able to extract the start time for the acquisition at some point.

Copy link
Member

@tkuchida tkuchida left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM upon addressing @chrisdembia's comments |-o-|

for(int f = 0;
f < fp_force_pts->GetFrontItem()->GetFrameNumber();
++f) {
for(int f = 0; f < force_times.size(); ++f) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A straight replacement would cause a compiler warning (type mismatch). Would need to do

for (int f = 0; f < static_cast<int>(nf); ++f)

Ajay Seth added 5 commits May 24, 2017 22:54
…ience constructor must perform their own validation because virtual validateRow() will not invoke the derived class override during construction.

Also do not exceed 80chars.
Copy link
Member

@chrisdembia chrisdembia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a new issue about the initial time of the file (#1732).

@aseth1
Copy link
Member Author

aseth1 commented May 25, 2017

Thank you @chrisdembia and @tkuchida. Merging.

@aseth1 aseth1 merged commit 0470655 into master May 25, 2017
@aseth1 aseth1 deleted the fix_slow_C3DFileAdpater_reading_1698 branch May 25, 2017 23:25
@aseth1
Copy link
Member Author

aseth1 commented May 25, 2017

@nickbianco and @jimmyDunne can you check that you are getting reasonable performance now on your big C3D files? Thanks!

@nickbianco
Copy link
Member

I re-ran my test case and I'm getting reasonable performance now.

@aseth1
Copy link
Member Author

aseth1 commented May 31, 2017

I re-ran my test case and I'm getting reasonable performance now.

Curious how it compares to using BTK directly, can you post the comparison as you did before in MATLAB?

@nickbianco
Copy link
Member

Pretty much comparable with the update:

btk in MATLAB: 0.287 +/- 0.0329 seconds
C3DFileAdapter: 0.315 +/- 0.0067 seconds

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants