Skip to content

Commit d76e5d4

Browse files
authored
Merge pull request #2578 from opensim-org/stopwatch
Add class Stopwatch
2 parents 2476317 + 8193991 commit d76e5d4

File tree

6 files changed

+150
-73
lines changed

6 files changed

+150
-73
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ v4.1
1616
- Reading DataTables from files has been simplified. Reading one table from a file typically uses the Table constructor except when the data-source/file contains multiple tables. (In these cases e.g. C3D files, use C3DFileAdapter.read method, then use functions in C3DFileAdapter to get the individual TimeSeriesTable(s)). Writing tables to files has not changed.
1717
- Exposed convertMillimeters2Meters() in osimC3D.m. This function converts COP and moment data from mm to m and now must be invoked prior to writing force data to file. Previously, this was automatically performed during writing forces to file.
1818
- Methods that operate on SimTK::Vec<n> are now available through Java/Matlab and python bindings to add/subtract/divide/multiply vec<n> contents with a scalar (PR #2558)
19+
- The new Stopwatch class allows C++ API users to easily measure the runtime of their code.
1920

2021
Converting from v4.0 to v4.1
2122
----------------------------

OpenSim/Common/Stopwatch.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#ifndef OPENSIM_STOPWATCH_H_
2+
#define OPENSIM_STOPWATCH_H_
3+
/* -------------------------------------------------------------------------- *
4+
* OpenSim: Stopwatch.h *
5+
* -------------------------------------------------------------------------- *
6+
* The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
7+
* See http://opensim.stanford.edu and the NOTICE file for more information. *
8+
* OpenSim is developed at Stanford University and supported by the US *
9+
* National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
10+
* through the Warrior Web program. *
11+
* *
12+
* Copyright (c) 2005-2019 Stanford University and the Authors *
13+
* Author(s): Christopher Dembia *
14+
* *
15+
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
16+
* not use this file except in compliance with the License. You may obtain a *
17+
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
18+
* *
19+
* Unless required by applicable law or agreed to in writing, software *
20+
* distributed under the License is distributed on an "AS IS" BASIS, *
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
22+
* See the License for the specific language governing permissions and *
23+
* limitations under the License. *
24+
* -------------------------------------------------------------------------- */
25+
26+
/// Record and report elapsed real time ("clock" or "wall" time) in seconds.
27+
class Stopwatch {
28+
public:
29+
/// This stores the start time as the current time.
30+
Stopwatch() { reset(); }
31+
/// Reset the start time to the current time.
32+
void reset() { m_startTime = SimTK::realTimeInNs(); }
33+
/// Return the amount of time that has elapsed since this object was
34+
/// constructed or since reset() has been called.
35+
double getElapsedTime() const {
36+
return SimTK::realTime() - SimTK::nsToSec(m_startTime);
37+
}
38+
/// Get elapsed time in nanoseconds. See SimTK::realTimeInNs() for more
39+
/// information.
40+
long long getElapsedTimeInNs() const {
41+
return SimTK::realTimeInNs() - m_startTime;
42+
}
43+
/// This provides the elapsed time as a formatted string (using format()).
44+
std::string getElapsedTimeFormatted() const {
45+
return formatNs(getElapsedTimeInNs());
46+
}
47+
/// Format the provided elapsed time in nanoseconds into a string.
48+
/// The time may be converted into seconds, milliseconds, or microseconds.
49+
/// Additionally, if the time is greater or equal to 60 seconds, the time in
50+
/// hours and/or minutes is also added to the string.
51+
/// Usually, you can call getElapsedTimeFormatted() instead of calling this
52+
/// function directly. If you call this function directly, use
53+
/// getElapsedTimeInNs() to get a time in nanoseconds (rather than
54+
/// getElapsedTime()).
55+
static std::string formatNs(const long long& nanoseconds) {
56+
std::stringstream ss;
57+
double seconds = SimTK::nsToSec(nanoseconds);
58+
int secRounded = (int)std::round(seconds);
59+
if (seconds > 1)
60+
ss << secRounded << " second(s)";
61+
else if (nanoseconds >= 1000000)
62+
ss << nanoseconds / 1000000 << " millisecond(s)";
63+
else if (nanoseconds >= 1000)
64+
ss << nanoseconds / 1000 << " microsecond(s)";
65+
else
66+
ss << nanoseconds << " nanosecond(s)";
67+
int minutes = secRounded / 60;
68+
int hours = minutes / 60;
69+
if (minutes || hours) {
70+
ss << " (";
71+
if (hours) {
72+
ss << hours << " hour(s), ";
73+
ss << minutes % 60 << " minute(s), ";
74+
ss << secRounded % 60 << " second(s)";
75+
} else {
76+
ss << minutes % 60 << " minute(s), ";
77+
ss << secRounded % 60 << " second(s)";
78+
}
79+
ss << ")";
80+
}
81+
return ss.str();
82+
}
83+
84+
private:
85+
long long m_startTime;
86+
};
87+
88+
89+
#endif // OPENSIM_STOPWATCH_H_

OpenSim/Common/Test/testC3DFileAdapter.cpp

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@
2121
* -------------------------------------------------------------------------- */
2222

2323
#include "OpenSim/Common/C3DFileAdapter.h"
24-
#include "OpenSim/Common/TRCFileAdapter.h"
2524
#include "OpenSim/Common/STOFileAdapter.h"
26-
#include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
27-
28-
#include <vector>
29-
#include <unordered_map>
30-
#include <cstdlib>
25+
#include "OpenSim/Common/TRCFileAdapter.h"
3126
#include <chrono>
32-
#include <thread>
3327
#include <cmath>
28+
#include <cstdlib>
29+
#include <thread>
30+
#include <unordered_map>
31+
#include <vector>
32+
33+
#include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
34+
#include <OpenSim/Common/Stopwatch.h>
3435

3536
template<typename ETY = SimTK::Real>
3637
void compare_tables(const OpenSim::TimeSeriesTable_<ETY>& table1,
@@ -77,26 +78,25 @@ void test(const std::string filename) {
7778
using namespace std;
7879

7980
// The walking C3D files included in this test should not take more
80-
// than 40ms on most hardware. We make the max time 100ms to account
81+
// than 40ms on most hardware. We make the max time 200ms to account
8182
// for potentially slower CI machines.
82-
const double MaximumLoadTimeInMS = 100;
83+
const long long MaximumLoadTimeInNs = SimTK::secToNs(0.200);
8384

84-
std::clock_t startTime = std::clock();
85+
Stopwatch watch;
8586
C3DFileAdapter c3dFileAdapter{};
8687
auto tables = c3dFileAdapter.read(filename);
87-
88-
double loadTime = 1.e3*(std::clock() - startTime) / CLOCKS_PER_SEC;
88+
long long loadTime = watch.getElapsedTimeInNs();
8989

9090
cout << "\tC3DFileAdapter '" << filename << "' loaded in "
91-
<< loadTime << "ms" << endl;
91+
<< watch.formatNs(loadTime) << endl;
9292

9393
/* Disabled performance test because Travis CI is consistently unable to
9494
meet this timing requirement. Consider PR#2221 to address this issue
9595
longer term.
9696
#ifdef NDEBUG
97-
ASSERT(loadTime < MaximumLoadTimeInMS, __FILE__, __LINE__,
97+
ASSERT(loadTime < MaximumLoadTimeInNs, __FILE__, __LINE__,
9898
"Unable to load '" + filename + "' within " +
99-
to_string(MaximumLoadTimeInMS) + "ms.");
99+
to_string(MaximumLoadTimeInNs) + "ns.");
100100
#endif
101101
*/
102102

@@ -117,28 +117,28 @@ void test(const std::string filename) {
117117
marker_table->updTableMetaData().setValueForKey("Units",
118118
std::string{"mm"});
119119
TRCFileAdapter trc_adapter{};
120-
std::clock_t t0 = std::clock();
120+
watch.reset();
121121
trc_adapter.write(*marker_table, marker_file);
122122
cout << "\tWrote '" << marker_file << "' in "
123-
<< 1.e3*(std::clock() - t0) / CLOCKS_PER_SEC << "ms" << endl;
123+
<< watch.getElapsedTimeFormatted() << endl;
124124

125125
ASSERT(force_table->getNumRows() > 0, __FILE__, __LINE__,
126126
"Failed to read forces data from " + filename);
127127

128128
force_table->updTableMetaData().setValueForKey("Units",
129129
std::string{"mm"});
130130
STOFileAdapter sto_adapter{};
131-
t0 = std::clock();
131+
watch.reset();
132132
sto_adapter.write((force_table->flatten()), forces_file);
133133
cout << "\tWrote'" << forces_file << "' in "
134-
<< 1.e3*(std::clock() - t0) / CLOCKS_PER_SEC << "ms" << endl;
134+
<< watch.getElapsedTimeFormatted() << endl;
135135

136136
// Verify that marker data was written out and can be read in
137-
t0 = std::clock();
137+
watch.reset();
138138
TimeSeriesTable_<SimTK::Vec3> markers(marker_file);
139139
TimeSeriesTable_<SimTK::Vec3> std_markers("std_" + marker_file);
140140
cout << "\tRead'" << marker_file << "' and its standard in "
141-
<< 1.e3*(std::clock() - t0) / CLOCKS_PER_SEC << "ms" << endl;
141+
<< watch.getElapsedTimeFormatted() << endl;
142142

143143
// Compare C3DFileAdapter read-in and written marker data
144144
compare_tables<SimTK::Vec3>(markers, *marker_table);
@@ -161,22 +161,24 @@ void test(const std::string filename) {
161161

162162
cout << "\tForces " << forces_file << " equivalent to standard." << endl;
163163

164-
t0 = std::clock();
165-
// Reread in C3D file with forces resolved to the COP
164+
watch.reset();
165+
// Reread in C3D file with forces resolved to the COP
166166
auto c3dFileAdapter2 = C3DFileAdapter{};
167-
c3dFileAdapter2.setLocationForForceExpression(C3DFileAdapter::ForceLocation::CenterOfPressure);
167+
c3dFileAdapter2.setLocationForForceExpression(
168+
C3DFileAdapter::ForceLocation::CenterOfPressure);
168169
auto tables2 = c3dFileAdapter2.read(filename);
169170

170-
loadTime = 1.e3*(std::clock() - t0) / CLOCKS_PER_SEC;
171+
loadTime = watch.getElapsedTimeInNs();
171172
cout << "\tC3DFileAdapter '" << filename << "' read with forces at COP in "
172-
<< loadTime << "ms" << endl;
173+
<< watch.formatNs(loadTime) << endl;
173174
// on ci-biulds will define SKIP_TIMING as it is unpredictably slow on some machines
174175
#if defined(NDEBUG) && !defined(SKIP_TIMING)
175-
ASSERT(loadTime < MaximumLoadTimeInMS, __FILE__, __LINE__,
176+
ASSERT(loadTime < MaximumLoadTimeInNs, __FILE__, __LINE__,
176177
"Unable to load '" + filename + "' within " +
177-
to_string(MaximumLoadTimeInMS) + "ms.");
178+
to_string(MaximumLoadTimeInNs) + "ns.");
178179
#endif
179-
std::shared_ptr<TimeSeriesTableVec3> force_table_cop = c3dFileAdapter.getForcesTable(tables2);
180+
std::shared_ptr<TimeSeriesTableVec3> force_table_cop =
181+
c3dFileAdapter.getForcesTable(tables2);
180182
downsample_table(*force_table_cop, 100);
181183

182184
sto_adapter.write(force_table_cop->flatten(), "cop_"+ forces_file);
@@ -189,29 +191,11 @@ void test(const std::string filename) {
189191
SimTK::SqrtEps);
190192

191193
cout << "\tcop_" << forces_file << " is equivalent to its standard."<< endl;
192-
193-
cout << "\ttestC3DFileAdapter '" << filename << "' completed in "
194-
<< 1.e3*(std::clock() - startTime) / CLOCKS_PER_SEC << "ms" << endl;
195-
196194
}
197195

198196
int main() {
199-
std::vector<std::string> filenames{};
200-
filenames.push_back("walking2.c3d");
201-
filenames.push_back("walking5.c3d");
202-
203-
for(const auto& filename : filenames) {
204-
std::cout << "\nTest reading '" + filename + "'." << std::endl;
205-
try {
206-
test(filename);
207-
}
208-
catch (const std::exception& ex) {
209-
std::cout << "testC3DFileAdapter FAILED: " << ex.what() << std::endl;
210-
return 1;
211-
}
212-
}
213-
214-
std::cout << "\nAll testC3DFileAdapter cases passed." << std::endl;
215-
216-
return 0;
197+
SimTK_START_TEST("testC3DFileAdapter");
198+
SimTK_SUBTEST1(test, "walking2.c3d");
199+
SimTK_SUBTEST1(test, "walking5.c3d");
200+
SimTK_END_TEST();
217201
}

OpenSim/Common/osimCommon.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
#include "GCVSpline.h"
3434
#include "IO.h"
3535

36+
#include "Stopwatch.h"
37+
3638
#include "Scale.h"
3739
#include "SimmSpline.h"
3840
#include "Constant.h"

OpenSim/Tools/InverseDynamicsTool.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@
2525
// INCLUDES
2626
//=============================================================================
2727
#include "InverseDynamicsTool.h"
28-
#include <OpenSim/Simulation/Model/Model.h>
29-
#include <OpenSim/Simulation/InverseDynamicsSolver.h>
30-
#include <OpenSim/Common/XMLDocument.h>
31-
#include <OpenSim/Common/IO.h>
32-
#include <OpenSim/Common/FunctionSet.h>
33-
#include <OpenSim/Common/GCVSplineSet.h>
28+
3429
#include <OpenSim/Common/Constant.h>
30+
#include <OpenSim/Common/FunctionSet.h>
31+
#include <OpenSim/Common/GCVSplineSet.h>
32+
#include <OpenSim/Common/IO.h>
33+
#include <OpenSim/Common/Stopwatch.h>
34+
#include <OpenSim/Common/XMLDocument.h>
35+
#include <OpenSim/Simulation/InverseDynamicsSolver.h>
36+
#include <OpenSim/Simulation/Model/Model.h>
3537

3638
using namespace OpenSim;
3739
using namespace std;
@@ -319,7 +321,7 @@ bool InverseDynamicsTool::run()
319321
// create the solver given the input data
320322
InverseDynamicsSolver ivdSolver(*_model);
321323

322-
const clock_t start = clock();
324+
Stopwatch watch;
323325

324326
int nt = final_index-start_index+1;
325327

@@ -337,7 +339,7 @@ bool InverseDynamicsTool::run()
337339
success = true;
338340

339341
cout << "InverseDynamicsTool: " << nt << " time frames in "
340-
<< (double)(clock()-start)/CLOCKS_PER_SEC << "s\n" <<endl;
342+
<< watch.getElapsedTimeFormatted() << "\n" <<endl;
341343

342344
JointSet jointsForEquivalentBodyForces;
343345
getJointsByName(*_model, _jointsForReportingBodyForces, jointsForEquivalentBodyForces);

OpenSim/Tools/InverseKinematicsTool.cpp

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,21 @@
2424
// INCLUDES
2525
//=============================================================================
2626
#include "InverseKinematicsTool.h"
27-
#include <OpenSim/Simulation/Model/Model.h>
28-
#include <OpenSim/Simulation/InverseKinematicsSolver.h>
29-
30-
#include <OpenSim/Common/IO.h>
31-
#include <OpenSim/Common/Storage.h>
32-
#include <OpenSim/Common/FunctionSet.h>
33-
#include <OpenSim/Common/GCVSplineSet.h>
34-
#include <OpenSim/Common/Constant.h>
35-
#include <OpenSim/Common/XMLDocument.h>
36-
37-
#include <OpenSim/Analyses/Kinematics.h>
3827

39-
#include "IKTaskSet.h"
4028
#include "IKCoordinateTask.h"
4129
#include "IKMarkerTask.h"
30+
#include "IKTaskSet.h"
4231

32+
#include <OpenSim/Analyses/Kinematics.h>
33+
#include <OpenSim/Common/Constant.h>
34+
#include <OpenSim/Common/FunctionSet.h>
35+
#include <OpenSim/Common/GCVSplineSet.h>
36+
#include <OpenSim/Common/IO.h>
37+
#include <OpenSim/Common/Stopwatch.h>
38+
#include <OpenSim/Common/Storage.h>
39+
#include <OpenSim/Common/XMLDocument.h>
40+
#include <OpenSim/Simulation/InverseKinematicsSolver.h>
41+
#include <OpenSim/Simulation/Model/Model.h>
4342

4443
using namespace OpenSim;
4544
using namespace std;
@@ -358,7 +357,7 @@ bool InverseKinematicsTool::run()
358357
Storage *modelMarkerErrors = _reportErrors ?
359358
new Storage(Nframes, "ModelMarkerErrors") : nullptr;
360359

361-
const clock_t start = clock();
360+
Stopwatch watch;
362361

363362
for (int i = start_ix; i <= final_ix; ++i) {
364363
s.updTime() = times[i];
@@ -460,7 +459,7 @@ bool InverseKinematicsTool::run()
460459
success = true;
461460

462461
cout << "InverseKinematicsTool completed " << Nframes << " frames in "
463-
<<(double)(clock()-start)/CLOCKS_PER_SEC << "s\n" <<endl;
462+
<< watch.getElapsedTimeFormatted() << "\n" <<endl;
464463
}
465464
catch (const std::exception& ex) {
466465
std::cout << "InverseKinematicsTool Failed: " << ex.what() << std::endl;

0 commit comments

Comments
 (0)